Создание таблицы лидеров SpriteKit/GameKit в конкретной сцене

Я новичок в Swift, и у меня возникли проблемы с внедрением таблицы лидеров в мою игру. Я только что посмотрел туториал: «Списки лидеров Game Center! (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)
    }

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


person muZero    schedule 25.08.2016    source источник
comment
Я в замешательстве.. где именно проблема? У вас проблемы с доступом к сцене GameOver или с загрузкой рекордов, или и то, и другое? @ Сэм, если это код Джареда, то я предполагаю, что он компилируется, я не могу его протестировать, потому что у тебя есть что-то вырезанное, кажется   -  person Fluidity    schedule 25.08.2016
comment
@fluidity это код Джареда. Я добавлю оставшийся код, если это поможет, и моя проблема в том, что я не думаю, что полностью понимаю разницу между загрузкой и хранением информации GameCenter в GameViewController (как в учебнике Джареда с одним представлением) и размещением логики в одном из многих SKScene. Разве это не должно проходить через GameViewController, и это управляет всеми сценами? В конечном счете, я пытаюсь сохранить и получить данные GC, когда пользователь нажимает на метку в данной сцене (GameOverScene).   -  person muZero    schedule 25.08.2016
comment
хорошо, я почти с вами (я еще не использовал GC). Если у вас есть код GC в вашем GameViewController, вам также необходимо опубликовать его. Я думаю, что это может быть просто проблема с областью действия, но она должна работать, если вы правильно поместите функции в свои метки.   -  person Fluidity    schedule 25.08.2016
comment
Я сейчас посмотрю видео, если вы можете опубликовать весь свой код #^^   -  person Fluidity    schedule 25.08.2016
comment
хорошо, я думаю, что знаю, что здесь происходит. Он показывает обычное приложение, но вы используете SK - так что да, как вы и предположили, структура немного отличается в том, как все обрабатывается / область действия и т. д.   -  person Fluidity    schedule 25.08.2016
comment
@fluidity Я хочу сказать, что он помещает весь свой код / ​​логику в GameViewController, но я хотел знать, как я могу справиться с этим через SKScene, например GameOverScene. Вместо его UIButtons я использую метку для вызова функций. Любые предложения были бы замечательными!   -  person muZero    schedule 25.08.2016
comment
Я скачал его проект и сейчас работаю над чем-то для вас. Убедитесь, что выполнили все шаги в iTunes connect.   -  person Fluidity    schedule 25.08.2016
comment
@Fluidity Думаю, я правильно выполнил все шаги iTunes - моя настоящая путаница заключается в том, что я поместил всю логику таблицы лидеров в конкретную сцену. Когда пользователь проигрывает, он переводится в GameOverScene. Я уже передал счет пользователя этой сцене, чтобы отобразить его в метке, но счет также должен быть сохранен в таблице лидеров (GameCenter) при входе в эту сцену, и у пользователя должна быть возможность просмотреть его в этой сцене. Спасибо еще раз!   -  person muZero    schedule 25.08.2016
comment
Я сделал приложение, которое записывает. Я не могу полностью протестировать его, потому что я еще не настроил GC, но, похоже, он работает довольно хорошо. Готовлюсь написать свой ответ   -  person Fluidity    schedule 25.08.2016


Ответы (2)


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

Автор большей части кода:

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.

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

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

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

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

введите описание изображения здесь


Итак, вы видите, 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 (который является просто классом), обратите внимание, что оно в основном такое же, как и в обычных приложениях 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 находятся внутри представления.... Ярлыки, узлы или кнопки, все они находятся внутри соответствующих классов ((представление)))

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


Посетите веб-сайты 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+, что перенесет вас к исходному файлу.

Удачи в учебе и приключениях в SpriteKit :)

person Fluidity    schedule 25.08.2016
comment
Это работает блестяще. Большое спасибо - теперь я пытаюсь отобразить информацию о рекордах в игре, а не в игровом центре, но это действительно помогло. - person muZero; 26.08.2016
comment

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

Подключиться к Айтюнс:

1) Войдите в свою аккаунт iTunes Connect. Перейдите в «Мои приложения» и выберите приложение, в котором вы хотите видеть лидеров.

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

3) Нажав на знак плюса, следуйте инструкциям, какую таблицу лидеров вы хотите. Сначала сделайте единую таблицу лидеров, если вы не уверены. «Идентификатор таблицы лидеров», который вы ему назначаете, будет использоваться в вашем коде в виде строки при доступе к нему, поэтому убедитесь, что вы вводите что-то правильное.

Теперь в xCode:

1) Включите библиотеку GameKit.framework, выбрав знак "+".

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

5а) Теперь переходим к файлу 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», чтобы она могла проверить, достаточно ли хорош счет для сохранения.

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

person Jose Ramirez    schedule 25.08.2016
comment
Бвахаха, я только что использовал это на Reddit для своего ответа xD Спасибо .. Я только что научился составлять списки лидеров, пытаясь помочь OP @Jozemite Apps - person Fluidity; 25.08.2016
comment
Есть идеи, почему я получаю <GKLocalPlayer: 0x1702a4d40>(playerID:(null) alias:(null) name:Me status:(null)) за let localPlayer = GKLocalPlayer.localPlayer()?? - person muZero; 17.09.2016
comment
Я никогда этого не понимал; вы убедились, что все сделали правильно? - person Jose Ramirez; 17.09.2016