Измените SKScene с помощью presentScene()

В моей игре SpriteKit я использую:

    self.scene!.removeFromParent()
    let skView = self.view! as SKView
    skView.ignoresSiblingOrder = true
    var scene: PlayScene!
    scene = PlayScene(size: skView.bounds.size)
    scene.scaleMode = .AspectFill
    skView.presentScene(scene, transition: SKTransition.fadeWithColor(SKColor(red: 25.0/255.0, green: 55.0/255.0, blue: 12.0/255.0, alpha: 1), duration: 1.0))

для перехода от одной сцены к другой. Но как я могу вернуться к исходной сцене? Использование одного и того же принципа кода всегда приводило к серьезному сбою.


person user3138007    schedule 28.12.2015    source источник


Ответы (2)


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

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

Что вам нужно знать об этом примере (вы измените эти правила в соответствии с вашей игрой, но логика та же — установите предыдущую сцену перед фактическим переходом):

  • есть три сцены: WelcomeScene (по умолчанию), MenuScene и GameScene.
  • нажав на черную кнопку, вы попадете в GameScene. Существует исключение из этого правила, когда текущая сцена является GameScene. В этом случае переход приведет вас к предыдущей сцене.
  • нажатие в любом месте вокруг черной кнопки приведет вас к предыдущей сцене. Существует исключение из этого правила, когда WelcomeScene загружается в первый раз (предыдущая сцена не установлена), и в этом случае переход приведет вас к MenuScene.

- в вашем GameViewController вы должны настроить WelcomeScene по умолчанию. В противном случае вам следует немного изменить код, чтобы обрабатывать ситуации, которые происходят, когда предыдущая сцена не установлена ​​(как я сделал в touchesBegan из WelcomeScene).

Так что это правила, которые я создал, просто для того, чтобы сделать все эти переходы немного более значимыми...

Вот код (BaseScene.swift):

import SpriteKit

enum SceneType: Int {

    case WelcomeScene   = 0
    case MenuScene      //1
    case GameScene      //2  
}

struct GlobalData
{
    static var previousScene:SceneType?
    //Other global data...
}

class BaseScene:SKScene {

    let button = SKSpriteNode(color: SKColor.blackColor(), size: CGSize(width: 50, height: 50))

    override func didMoveToView(view: SKView) {
        setupButton()
    }

    private func setupButton(){

        if (button.parent  == nil){

            //Just setup button properties like position, zPosition and name

            button.name = "goToGameScene"
            button.zPosition = 1
            button.position = CGPoint(x: CGRectGetMidX(frame), y: 100)
            addChild(button)
        }
    }

    func goToScene(newScene: SceneType){

        var sceneToLoad:SKScene?

        switch newScene {

        case SceneType.GameScene:

            sceneToLoad = GameScene(fileNamed: "GameScene")

        case SceneType.MenuScene:

            sceneToLoad = MenuScene(fileNamed: "MenuScene")

        case SceneType.WelcomeScene:

            sceneToLoad = WelcomeScene(fileNamed:"WelcomeScene")

        }

        if let scene = sceneToLoad {

            scene.size = size
            scene.scaleMode = scaleMode
            let transition = SKTransition.fadeWithDuration(3)
            self.view?.presentScene(scene, transition: transition)
        }
    }
}

Каждая сцена (WelcomeScene, MenuScene, GameScene) наследуется от класса BaseScene (который является подклассом SKScene). Думаю, нет необходимости объяснять это, но не стесняйтесь спрашивать, если вас что-то смущает. Важным методом здесь (который используется каждым подклассом) является goToScene(scene:SceneType) и его параметр (типа SceneType), который сообщает нам, какой тип сцены должен загрузить метод.

SceneType — это просто перечисление, которое содержит целые числа... Так что на самом деле мы здесь не работаем с объектами, поэтому не нужно бояться циклы надежных ссылок.

Далее идут другие сцены (WelcomeScene.swift):

import SpriteKit


class WelcomeScene:BaseScene {


    override func didMoveToView(view: SKView) {

        super.didMoveToView(view)

        self.backgroundColor = SKColor.darkGrayColor() 
    }

    deinit {print ("WelcomeScene deinited")}

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

        let touch = touches.first

        if let location = touch?.locationInNode(self){

            //Give a priority to a button - if button is tapped go to GameScene
            let node = nodeAtPoint(location)
            if node.name == "goToGameScene"{
                GlobalData.previousScene = SceneType.MenuScene
                goToScene(SceneType.GameScene)
            }else{
                //Otherwise, do a transition to the previous scene

                //Get the previous scene
                if let previousScene = GlobalData.previousScene {

                    GlobalData.previousScene = SceneType.WelcomeScene
                    goToScene(previousScene)

                }else{

                    // There is no previousScene set yet? Go to MenuScene then...
                    GlobalData.previousScene = SceneType.WelcomeScene
                    goToScene(SceneType.MenuScene)

                }
            }       
        }      
    }
}

Чтобы быть кратким, все прокомментировано. Следующий код (MenuScene.swift):

import SpriteKit

class MenuScene: BaseScene  {

    override func didMoveToView(view: SKView) {

        super.didMoveToView(view)
        backgroundColor = SKColor.purpleColor()
    }

    deinit {
        print ("MenuScene deinited") //If this method isn't called, you might have problems with strong reference cycles.
    }

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

        let touch = touches.first

        if let location = touch?.locationInNode(self){

            //Give a priority to a button - if button is tapped go to GameScene
            let node = nodeAtPoint(location)
            if node.name == "goToGameScene"{
                GlobalData.previousScene = SceneType.MenuScene
                goToScene(SceneType.GameScene)
            }else{
                //Otherwise, do a transition to the previous scene

                //Get the previous scene
                if let previousScene = GlobalData.previousScene {

                    GlobalData.previousScene = SceneType.MenuScene
                    goToScene(previousScene)

                } 
            }
        } 
    }
}

И напоследок (GameScene.swift):

import SpriteKit

class GameScene: BaseScene{

    override func didMoveToView(view: SKView) {

        super.didMoveToView(view)

     self.backgroundColor = SKColor.orangeColor() 
    }

    deinit {print ("GameScene deinited")}

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

        //Here, we ignore black button because we don't want to transition to the same scene

         if let previousScene = GlobalData.previousScene {

            GlobalData.previousScene = SceneType.GameScene
            goToScene(previousScene)  
        }
    }
}

Предварительный просмотр:

переходы

Просто прочитайте еще раз правила с самого начала, и все будет в порядке (например, в GameScene черная кнопка не работает, или при первом запуске не установлена ​​предыдущая сцена, поэтому по умолчанию вы будете переведены в MenuScene).

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

СОВЕТ. Здесь важно то, что каждая сцена BaseScene, WelcomeScene... имеет собственный файл .sks. Вы создаете их из File->New->File->Resource и называете их соответствующим образом (например, BaseClass.sks, WelcomeScene.sks...). Кроме того, ваша работа — поддерживать состояние переменной GlobalData.previousScene (например, установить ее перед переход сделан).

person Whirlwind    schedule 29.12.2015
comment
Оно работает! большое спасибо! Именно то, что мне было нужно :)) - person user3138007; 29.12.2015

Вам нужно будет создать свойство в вашей новой сцене, которое хранит предыдущую, что-то вроде previousScene. Затем вы можете установить его так: scene.previousScene = self.scene. В вашей новой сцене теперь вы можете вернуться к предыдущей сцене с помощью skView.presentScene(previousScene)

И я бы посоветовал не называть новую сцену, которую вы собираетесь представить, scene, потому что ваша текущая сцена также называется сценой, поэтому, если вы случайно забудете self в self.scene, это может вызвать много путаницы. Я бы назвал это что-то вроде newScene или sceneToPresent.

Кроме того, ваша первая строка, self.scene!.removeFromParent(), не нужна. Вам не нужно удалять текущую сцену, прежде чем представить новую.

person Addison    schedule 28.12.2015
comment
Без проблем! Не забудьте выбрать это как правильный ответ, если это сработало :) - person Addison; 28.12.2015
comment
@Addison Хотя это может работать как решение для отслеживания предыдущей сцены, оно создаст сильный эталонный цикл, и предыдущая сцена никогда не будет выпущена после перехода (deinit() не будет вызываться). Нет необходимости держать в памяти весь объект сцены только для того, чтобы сделать переход на новую (предыдущую) сцену. - person Whirlwind; 28.12.2015
comment
@Whirlwind Так решит ли эта проблема слабую ссылку? - person Addison; 28.12.2015
comment
@Whirlwind было бы неплохо, если бы вы могли опубликовать ответ, включая правильный фрагмент кода. - person user3138007; 28.12.2015
comment
@Addison Короче говоря, нет. Слабая ссылка не мешает ARC избавиться от экземпляра, на который ссылаются. Вот что произойдет: вы установите scene.previousScene = self.scene, но после перехода текущая сцена будет определена, и автоматически для scene.previousScene будет установлено значение nil... - person Whirlwind; 28.12.2015
comment
@user3138007 user3138007 Когда мне нужно отследить предыдущую сцену, я обычно делаю то, что указала Эддисон. Я создаю свойство myScene.sceneToLoad, но вместо сохранения реальной сцены я создаю перечисление с именем SceneType и сохраняю простое целое число. Если вам все еще нужен пример кода, дайте мне знать. - person Whirlwind; 28.12.2015
comment
@Whirlwind, да, пример кода был бы полезен! Хотя я понял вашу точку зрения, я не совсем уверен, как настроить фактический код ... для лучшего способа легкого переключения между несколькими сценами. Спасибо! - person user3138007; 29.12.2015
comment
@ user3138007 Хорошо, скоро сделаю пример ... Но из вашего последнего комментария у меня сложилось впечатление, что вы не пытаетесь отследить предыдущую сцену (после каждого перехода), а просто переходите к определенной сцене (ам) ... В любом случае, я постараюсь сделать оба примера (вероятно, это будет огромный ответ :)) - person Whirlwind; 29.12.2015