在特定场景中构建 SpriteKit/GameKit 排行榜

Posted

技术标签:

【中文标题】在特定场景中构建 SpriteKit/GameKit 排行榜【英文标题】:Building a SpriteKit/GameKit leaderboard within a specific scene 【发布时间】:2016-08-25 15:37:18 【问题描述】:

我对 Swift 很陌生,在我的游戏中实现排行榜时遇到了一些麻烦。我刚刚看了一个教程:'Game Center 排行榜! (Xcode 中的 Swift 2)',其中 GameCenter 信息全部通过应用程序的一个视图。在我的游戏中,我希望用户能够玩游戏,然后只有当他们在特定的SKScene 上时,他们才能访问 GameCenter。

例如,在GameOverScene 上,他们将通过用户身份验证,并且还能够上传他们的高分。我想我还遗漏了GameViewController(所有教程逻辑所在的位置)与我制作的众多场景之一之间的一些差异。

这是我尝试在GameOverScene 上使用GKGameCenterControllerDelegate 并创建各种功能以访问GameCenter 的代码。当用户在视图中点击某个标签时进行调用:(这显然不起作用,因为我试图访问这样的行上的场景:self.presentViewController(view!, animated:true, completion: nil)


class GameOverScene: SKScene, GKGameCenterControllerDelegate  

    init(size: CGSize, theScore:Int) 
        score = theScore
        super.init(size: size)
    
    ...

    override func didMoveToView(view: SKView) 

        authPlayer()

        leaderboardLabel.text = "Tap for Leaderboard"
        leaderboardLabel.fontSize = 12
        leaderboardLabel.fontColor = SKColor.redColor()
        leaderboardLabel.position = CGPoint(x: size.width*0.85, y: size.height*0.1)
        addChild(leaderboardLabel)

        ...

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) 

        for touch : AnyObject in touches 
            let location = touch.locationInNode(self)

            if(CGRectContainsPoint(leaderBoardLabel.frame, location))
                saveHighScore(score)
                showLeaderBoard()
            
        
    


    func authPlayer()

        //Create a play
        let localPlayer = GKLocalPlayer.localPlayer()

        //See if signed in or not
        localPlayer.authenticateHandler = 
            //A view controller and an error handler
            (view,error) in

            //If there is a view to work with
            if view != nil 
                self.presentViewController(view!, animated:true, completion: nil) //we dont want a completion handler
            

            else
                print(GKLocalPlayer.localPlayer().authenticated)
            
        
    


    //Call this when ur highscore should be saved
    func saveHighScore(number:Int)

        if(GKLocalPlayer.localPlayer().authenticated)

            let scoreReporter = GKScore(leaderboardIdentifier: "scoreBoard")
            scoreReporter.value = Int64(number)

            let scoreArray: [GKScore] = [scoreReporter]

            GKScore.reportScores(scoreArray, withCompletionHandler: nil)

        

    


    func showLeaderBoard()

        let viewController = self.view.window?.rootViewController
        let gcvc = GKGameCenterViewController()

        gcvc.gameCenterDelegate = self

        viewController?.presentViewController(gcvc, animated: true, completion: nil)


    


    func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController) 
        gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
    

任何关于如何解决此问题的建议都会很棒,我认为我可能会将整个场景/视图控制器弄混并导致问题。谢谢!

【问题讨论】:

我很困惑..问题到底出在哪里?您是否在进入 GameOver 场景时遇到问题,或者在加载高分时遇到问题,或者两者兼而有之? @Sam,如果这是 Jared 的代码,那么我猜它可以编译,,,我无法测试它,因为你有一些剪掉它出现 @fluidity 这是 Jared 的代码。如果有帮助,我将添加剩余的代码,我的问题是我认为我不完全理解在 GameViewController 中加载和存储 GameCenter 信息(就像在 Jared 的单视图教程中一样)和放置逻辑之间的区别在众多SKScene 之一。除非这必须通过GameViewController 并且管理所有场景?最终,当用户在给定场景 (GameOverScene) 中点击标签时,我会尝试保存和检索 GC 数据。 好的,我几乎和你在一起(我还没有使用过 GC)。如果您的GameViewController 中有 GC 代码,那么您也需要发布它。我认为这可能只是一个范围问题,但如果您将函数正确放入标签中,它应该可以工作。 我现在去看视频,如果你能把你所有的代码贴出来#^^ 我下载了他的项目,现在正在为你做一些事情。确保 iTunes 中的所有步骤都已连接 【参考方案1】:

这个答案部分延续了我们在 cmets 中停止的地方,因为你没有发布你的整个代码我不能准确地告诉你挂断的确切位置,但这是我与一个单独的指南一起整理的(它与您发布的代码版本略有不同):

大部分代码的作者:

https://www.reddit.com/r/swift/comments/3q5owv/how_to_add_a_leaderboard_in_spritekit_and_swift_20/

GameViewController.swift:

import UIKit
import SpriteKit
import GameKit

class GameViewController: UIViewController 

    func authenticateLocalPlayer() 
        let localPlayer = GKLocalPlayer.localPlayer()
        localPlayer.authenticateHandler = (viewController, error) -> Void in

            if (viewController != nil) 
                self.presentViewController(viewController!, animated: true, completion: nil)
            
            else 
                print((GKLocalPlayer.localPlayer().authenticated))
            
        
    

    override func viewDidLoad() 
        super.viewDidLoad()

        /////authentication//////
        authenticateLocalPlayer()

        //... The rest of the default code
    

    //... The rest of the default code


GameScene.swift(或任何你想使用 GC 的场景):


import SpriteKit
import GameKit
import UIKit

// Global scope (I generally put these in a new file called Global.swift)
var score = 0


//sends the highest score to leaderboard
func saveHighscore(gameScore: Int) 
    print ("You have a high score!")
    print("\n Attempting to authenticating with GC...")

    if GKLocalPlayer.localPlayer().authenticated 
        print("\n Success! Sending highscore of \(score) to leaderboard")

        //---------PUT YOUR ID HERE:
        //                          |
        //                          |
        //                          V
        let my_leaderboard_id = "YOUR_LEADERBOARD_ID"
        let scoreReporter = GKScore(leaderboardIdentifier: my_leaderboard_id)

        scoreReporter.value = Int64(gameScore)
        let scoreArray: [GKScore] = [scoreReporter]

        GKScore.reportScores(scoreArray, withCompletionHandler: error -> Void in
            if error != nil 
                print("An error has occured:")
                print("\n \(error) \n")
            
        )
    


// Your scene:
class GameScene: SKScene, GKGameCenterControllerDelegate 

    // Local scope variables (for this scene):

    // Declare a new node, then initialize it
    let call_gc_node   = SKLabelNode(fontNamed:"Chalkduster")
    let add_score_node = SKLabelNode(fontNamed: "Helvetica")


    override func didMoveToView(view: SKView) 

        // Give our GameCenter node some stuff
        initGCNode: do 

            // Set the name of the node (we will reference this later)
            call_gc_node.name = "callGC"

            // Default inits
            call_gc_node.text = "Send your HighScore of \(score) into Game Center"
            call_gc_node.fontSize = 25
            call_gc_node.position = CGPoint(
                x:CGRectGetMidX(self.frame),
                y:CGRectGetMidY(self.frame))

            // Self here is the instance (object) of our class, GameScene
            // This adds it to our view
            self.addChild(call_gc_node)
        

        // Give our Add label some stuff
        initADDLabel: do 

            // Set the name of the node (we will reference this later)
            add_score_node.name = "addGC"

            // Basic inits
            add_score_node.text = "ADD TO SCORE!"
            add_score_node.fontSize = 25
            add_score_node.position = call_gc_node.position

            // Align our label some
            add_score_node.runAction(SKAction.moveByX(0, y: 50, duration: 0.01))

            // Add it to the view
            self.addChild(add_score_node)
        

    


    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) 
        for touch in touches 

            // Get the position of our click
            let TPOINT = touch.locationInNode(self)

            // Get the name (string) of the node that was touched
            let
                node_that_was_touched: String?
                                                = nodeAtPoint(TPOINT).name


            // Prepare for switch statement, when we unwrap the optional, we don't want nil
            guard (node_that_was_touched != nil)
                else  print("-> before switch: found nil--not entering Switch");
                    return
            


            // Find out which node we clicked based on node.name?, then do stuff:
            switch node_that_was_touched! 

                case "callGC":
                    // We clicked the GC label:

                    GameOver: do 

                        print("GAME OVER!")

                        // If we have a high-score, send it to leaderboard:
                        overrideHighestScore(score)

                        // Reset our score (for the next playthrough)
                        score = 0

                        // Show us our stuff!
                        showLeader()
                    

                case "addGC":
                    // we clicked the Add label:

                    // Update our *current score*
                    score += 1


                default: print("no matches found")
            

        

    


    override func update(currentTime: CFTimeInterval) 
        /* Called before each frame is rendered */
        call_gc_node.text = "Send your HighScore of \(score) into Game Center"

    


    // Gamecenter
    func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController) 
        gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
    

    //shows leaderboard screen
    func showLeader() 
        let viewControllerVar = self.view?.window?.rootViewController
        let gKGCViewController = GKGameCenterViewController()
        gKGCViewController.gameCenterDelegate = self
        viewControllerVar?.presentViewController(gKGCViewController, animated: true, completion: nil)
    

    // Your "game over" function call
    func overrideHighestScore(gameScore: Int) 
        NSUserDefaults.standardUserDefaults().integerForKey("highscore")
        if gameScore > NSUserDefaults.standardUserDefaults().integerForKey("highscore")
        
            NSUserDefaults.standardUserDefaults().setInteger(gameScore, forKey: "highscore")
            NSUserDefaults.standardUserDefaults().synchronize()

            saveHighscore(gameScore)
        
    


特别注意

29:let my_leaderboard_id = "YOUR_LEADERBOARD_ID"

我在里面放了一些愚蠢的 ASCII 艺术作品,以确保你不会错过它。您必须输入您在 GameCenter 设置中的实际排行榜 ID。

您还必须添加 GameCenter 库,并连接 iTunes 才能在弹出窗口中实际看到您的高分。

我认为您最初的问题是不了解 SpriteKit 甚至 ios 视图如何工作的一些后端(这完全没问题,因为 Apple 让参与进来变得非常容易)。但是,如您所见,遵循指南/教程可能会很困难,因为您的实施会有所不同。

这里有一些很好的信息:

SK 中每一帧发生的情况示意图:


所以你看,SKScene 是一个包含所有有趣的东西的类,比如节点和动作,并且是所有(对你很重要的)事情发生的地方。您可以通过编辑器生成这些场景,但是您可能需要创建一个新的 .swift 文件来配合它(因为每个场景都可以有自己的逻辑)。

编辑器只是初始化一堆东西的“捷径”,老实说,你可以用很少的代码制作完整的游戏(但你很快就会发现你想要更多) p>

因此,在这段代码中,您声明 GameScene 或 PauseScreen(它们基本上只是类声明,从 SKScene 继承),您很快就会发现这行在谈论 ISNT 场景:

override func didMoveToView(view: SKView) .. 它正在调用 SKView... 那是什么,它是从哪里来的?

(在此处了解 SKView,并查看其继承):

https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKView/index.html#//apple_ref/occ/cl/SKView


我们在GameViewController 文件中找到了这个 SKView 声明(它只是一个类),请注意它与大多数常规 iOS 应用程序相同,因为它继承了 UIViewController:

override func viewDidLoad() 
    super.viewDidLoad()
    if let scene = GameScene(fileNamed:"GameScene") 
        // Configure the view.
        let skView = self.view as! SKView
        skView.showsFPS = true
        skView.showsNodeCount = true

        /* Sprite Kit applies additional optimizations to improve               rendering performance */
        skView.ignoresSiblingOrder = true

        /* Set the scale mode to scale to fit the window */
        scene.scaleMode = .AspectFill

        skView.presentScene(scene)
    

同样,该方法在 GameViewController.swift 中声明,基本上就是这样: class GameViewController: UIViewController


那么所有这些与 iOS 应用程序和 SpriteKit 有什么关系呢?好吧,它们都被捣碎了:

IOS 应用剖析:

基本上,从右到左,你有一个窗口,它是(如果错了,请纠正我)AppDelegate,然后是 ViewController,然后是你的视图,其中包含所有很酷的东西(故事板位于视图内,就像 SKScenes 位于 View 内部一样......标签、节点或按钮,都位于它们各自的类中((视图)))

这都是继承的大三明治。


查看 Apple 网站了解更多信息。

https://developer.apple.com/library/safari/documentation/UserExperience/Conceptual/MobileHIG/ContentViews.html#//apple_ref/doc/uid/TP40006556-CH13-SW1

https://developer.apple.com/spritekit/

https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SpriteKitFramework_Ref/

https://developer.apple.com/library/safari/documentation/UserExperience/Conceptual/MobileHIG/Anatomy.html

基本上,一切都是从类继承的类,等等,等等......它可能会变得混乱。您还可以通过 CMD+单击它们在 Xcode 中查看这些继承,这将跳转到源文件。

祝你在 SpriteKit 的学习和冒险顺利:)

【讨论】:

这非常有效。非常感谢 - 我现在正尝试在游戏而不是游戏中心中显示高分信息,但这确实很有帮助。 @Sam np :D developer.apple.com/library/ios/documentation/…【参考方案2】:

在我不久前写的 Swift 2.2 和部分 Xcode 7.1 的游戏中仍然有效的详细答案。详细的答案,但只是跳到底部。基本上回答你的问题,只要你想调用它,就会调用 showLeaderboard(),只需将特定函数放在正确的 SKScene 类中。

iTunes 连接:

1) 登录您的iTunes Connect account。转到我的应用程序,然后选择您想要用于排行榜的应用程序。

2)Go to Features, and then Game Center。单击加号以创建排行榜。如果要制作一组排行榜(分组排行榜,然后向右点击“更多”。

3) 点击加号后,按照您想要的排行榜的说明进行操作。首先,如果您不确定,请做一个排行榜。您分配给它的“排行榜 ID”将在您的代码中用作访问它时的字符串,因此请确保您输入的内容不错。

现在在 xCode 中:

1) Include the GameKit.framework library by choosing the "+" sign.

2)Add the string "GameKit" into your info.plist

3a) 在 GameViewController.swift 文件的顶部添加以下代码以及其他导入代码。

import GameKit

3b) 在同一个 swift 文件的类中添加以下函数。

    func authenticateLocalPlayer() 
    let localPlayer = GKLocalPlayer.localPlayer()
    localPlayer.authenticateHandler = (viewController, error) -> Void in

        if (viewController != nil) 
            self.presentViewController(viewController!, animated: true, completion: nil)
        
        else 
            print((GKLocalPlayer.localPlayer().authenticated))
        
    

4) 从 viewDidLoad() 函数内部调用“authenticateLocalPlayer”函数。

5a) 现在,转到 GameScene.swift 文件(或执行将发生的任何地方)。并在顶部添加以下内容。

import GameKit

5b) 在类函数中添加以下代码。

//shows leaderboard screen
func showLeader() 
    let viewControllerVar = self.view?.window?.rootViewController
    let gKGCViewController = GKGameCenterViewController()
    gKGCViewController.gameCenterDelegate = self
    viewControllerVar?.presentViewController(gKGCViewController, animated: true, completion: nil)

func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController) 
    gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)

在我的游戏中,我有一个显示排行榜的按钮,所以无论在哪里,只要调用“showLeader”函数即可显示排行榜。

6) 您必须具有将分数发送到排行榜的功能。在整个类声明之外,我有以下函数,它接受用户的游戏分数作为参数并将其发送到排行榜。上面写着“YOUR_LEADERBOARD_ID”的地方,就是我之前提到的排行榜 ID 所在的位置。As pictured here。

//sends the highest score to leaderboard
func saveHighscore(gameScore: Int) 

    print("Player has been authenticated.")

    if GKLocalPlayer.localPlayer().authenticated 

        let scoreReporter = GKScore(leaderboardIdentifier: "YOUR_LEADERBOARD_ID")
        scoreReporter.value = Int64(gameScore)
        let scoreArray: [GKScore] = [scoreReporter]

        GKScore.reportScores(scoreArray, withCompletionHandler: error -> Void in
            if error != nil 
                print("An error has occured: \(error)")
            
        )
    

7) 这是我在游戏结束时调用的函数。它决定分数是否大于之前的最高分数,如果是,它将发送到排行榜。 此代码完全取决于您,但请确保它调用了将数据发送到排行榜的 saveHighscore 函数。

func overrideHighestScore(gameScore: Int) 
    NSUserDefaults.standardUserDefaults().integerForKey("highscore")
    if gameScore > NSUserDefaults.standardUserDefaults().integerForKey("highscore") 

        NSUserDefaults.standardUserDefaults().setInteger(gameScore, forKey: "highscore")
        NSUserDefaults.standardUserDefaults().synchronize()

        saveHighscore(gameScore)
    

注意函数“saveHighscore”最后被调用。

8) 最后,在游戏结束函数(或任何你调用的函数)中,调用函数“overrideHighestScore”,以便它可以检查分数是否足以保存。

完成这两项操作后,等待几分钟,直到排行榜连接到您的应用(或加载)。它适用于我的游戏。 希望我没有忘记任何步骤。 用于制作本教程的 Screenshot of my game leaderboards。

【讨论】:

Bwahaha,我刚刚在 reddit 上用这个来回答我的答案 xDThankyou.. 我刚刚学会了如何做排行榜来帮助 OP @Jozemite Apps 知道为什么我会为let localPlayer = GKLocalPlayer.localPlayer() 得到&lt;GKLocalPlayer: 0x1702a4d40&gt;(playerID:(null) alias:(null) name:Me status:(null)) 吗?? 我从来没有得到过;你确定你做的每件事都正确吗?

以上是关于在特定场景中构建 SpriteKit/GameKit 排行榜的主要内容,如果未能解决你的问题,请参考以下文章

构建之法阅读笔记04

如何构建firestore数据库?

当特定 JIRA 项目下的所有问题的状态更改为“IN PROGRESS”状态时触发 Jenkins 构建

Swift / Spritekit:通过特定场景播放音乐

用场景中的特定点绘制椭圆

机器学习实验一 感知器及其应用