Построение таблицы лидеров SpriteKit/GameKit в определенной сцене

Я довольно новичок в Swift, и у меня возникли проблемы с реализацией лидеров в моей игре. Я только что смотрел учебник: "Game Center Leaderboards! (Swift 2 в Xcode)', в котором информация GameCenter все прошло через один вид приложения. В моей игре, я хочу, чтобы пользователь мог играть в игры и тогда только тогда, когда они находятся на определенном SKScene будет ли у них доступ к GameCenter.

например, на GameOverScene будут ли они аутентифицированы пользователем, а также будут иметь возможность загружать их высокий балл. Я думаю, что мне не хватает некоторых различий между GameViewController (где расположены все учебники логики) и одна из моих многих сцен, которые я сделал.

вот мой код, в котором я пытаюсь использовать GKGameCenterControllerDelegate на GameOverScene и создайте различные функции для достижения 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)
    }

любые советы о том, как я мог бы пойти об этом, было бы здорово, я думаю, что я могу получить весь контроллер сцены/просмотра перепутал и его приводит к проблемам. Спасибо!

2 ответов


этот ответ частично переносится туда, где мы остановились в комментариях, так как вы не опубликовали весь свой код, я не могу точно сказать, где было ваше зависание, но это то, что я собрал вместе с отдельным руководством (это немного другая версия кода, который вы публикуете):

автор большинства код:

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

GameViewController.Свифт:

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.

вы должны добавить библиотеку GameCenter, а также сделать iTunes connect, чтобы на самом деле увидеть ваши рекорды во всплывающем окне.

Я думаю, что ваши первоначальные проблемы заключались в том, что вы не понимали, как работает SpriteKit и даже iOS (что совершенно нормально, потому что Apple делает прыжки и делает вещи очень легкими). Но, как вы видите, следующие руководства / учебники могут быть трудными так как ваша реализация будет отличаться.

вот хорошая информация для начала:

диаграмма того, что происходит каждый кадр в SK:

enter image description here


Итак, вы видите, что SKScene-это класс со всеми забавными вещами, такими как узлы и действия, и где все (важно для вас) происходит. Вы можете сгенерировать эти сцены через редактор, но тогда вам, вероятно, нужно сделать новый .swift файл, чтобы пойти с ним (как каждая сцена может иметь свою логику).

редактор-это просто "ярлык" для инициализации кучи вещей, и, честно говоря, вы можете сделать полные игры с небольшим кодом (но вы очень быстро узнаете, что вы хочу больше)

Итак, в этом коде, где вы объявляете GameScene или PauseScreen (которые в основном являются объявлениями классов, которые наследуются от SKScene), вы быстро найдете эту строку, говорящую о чем-то, что не является сценой:

override func didMoveToView(view: SKView) .. это вызов SKView... что это и откуда взялось?

(читайте о SKView здесь, и посмотрите на его наследование):

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


мы находим это объявление SKView в GameViewController file, (который является просто классом), обратите внимание, что он такой же, как и обычные приложения 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:

anatomy

в основном, справа налево, у вас есть окно, которое (поправьте меня, если неправильно) AppDelegate, то ViewController, затем ваш вид, в котором есть все классные вещи (раскадровки сидят внутри вида, так же, как SKScenes сидят внутри вида.... Метки, узлы или кнопки, все сидят внутри своих соответствующих классов ((вид)))

это все большой бутерброд наследования.


Проверьте веб-сайты 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

в основном, все является классом, унаследованным от класса, унаследованного от класса и так далее, и так далее... Это может стать грязным. Вы также можете увидеть эти наследства в Xcode, нажав на них CMD+, что приведет вас к исходному файлу.

Goodluck с вашими исследованиями и приключениями в SpriteKit:)


подробный ответ, который все еще работает в моей игре по состоянию на Swift 2.2 и частично Xcode 7.1, я написал некоторое время назад. Подробный ответ, но сразу переходим к самому низу. Чтобы в основном ответить на ваш вопрос, showLeaderboard() будет вызываться всякий раз, когда вы хотите, чтобы он был вызван, просто поместите конкретную функцию в правильный класс SKScene.

iTunes Connect:

1) Войдите в ваш учетная запись iTunes Connect. Перейдите в "Мои приложения" и выберите приложение, которое вы хотите лидеров с.

2) перейти к функциям, а затем Game Center. Щелкните знак плюс, чтобы создать лидеров. Если вы хотите сделать набор лидеров (сгруппированных лидеров), то перейдите направо и нажмите на кнопку "Еще".

3) После нажатия на знак "плюс", следуйте инструкциям, какой именно баннер вы хотите. Сначала сделайте одну таблицу лидеров, если вы не уверены. "Leaderboard ID", который вы назначаете ему, будет использоваться в вашем коде в качестве строки при доступе к нему, поэтому обязательно напечатай что-нибудь приятное.

теперь в xCode:

1) включить GameKit.библиотека фреймворка, выбрав знак"+".

2) добавьте строку "GameKit" в свою информацию.файл 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) вызов функция" authenticateLocalPlayer " внутри функции viewDidLoad ().

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", вот где Ваш идентификатор лидеров, о котором я упоминал ранее, входит. как здесь на картинке.

//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", чтобы она могла проверить, достаточно ли хороша оценка для сохранения.

после выполняя оба, подождите несколько минут, пока таблицы лидеров не будут подключены к вашему приложению (или что они загружаются). Это работает на моей игре. надеюсь, я не забыл ни одного шага. скриншот моей игры лидеров которые были использованы для создания этого урока.