Search code examples
iosswiftsprite-kitskscene

Save state of GameScene through transitions


I want to build an inventory for my SpriteKit game. For this I want to go to another SKScene File which represents my inventory when I press the pause button. My problem is that when I make a transition from my InventoryScene back to my GameScene, the GameScene loads completely new. This is the transition code from my GameScene class:

func loadInventory(){
    if !transitionInProgress{
        transitionInProgress = true
        if let scene = InventoryScene(fileNamed: "Inventory"){
            Globals.InventoryGlobals.levelBeforeSwitch = currentLevel
            scene.scaleMode = .aspectFill
            let transition = SKTransition.push(with: .down, duration: 0.5)
            self.view?.presentScene(scene, transition: transition)
        }
    }
}

With this code I'll go to my InventoryScene.

Now in my InventoryScene I want to go back to my GameScene with this:

func loadLevel(level: String){
    if !transitionInProgress{
        transitionInProgress = true
        if let scene = GameScene(fileNamed: level){
            scene.currentLevel = level
            scene.scaleMode = .aspectFill
            let transition = SKTransition.doorsOpenHorizontal(withDuration: 1)
            self.view?.presentScene(scene, transition: transition)
        }
    }
}

The transitions are working but my problem is that the GameScene loads completely new, which is obviously because I instantiate a new GameScene. So when the player is in the middle of the level and then goes to the Inventory and back to the GameScene, the player is back at the beginning of the level. If I go from the Inventory back to the scene I want to be the GameScene as it was before (player position, enemy health, etc.)

Has anyone an idea how i can do this?


Solution

  • Retaining your scene is very simple, all you need to do is retain the scene with a strong reference

    In your ViewController, it is as simple as storing a variable

    class ViewController : UIViewController
    {
        var gameScene = GameScene(fileNamed:"GameScene")
    }
    

    Now for as long as your view controller is alive, your scene will be alive.

    To access it, you just need to find a way to tell the MenuScene where your view controller is, then present the scene.

    class ViewController : UIViewController
    {
        var gameScene = GameScene(fileNamed:"GameScene")
        lazy var skView : SKView = self.view as! SKView
        func gotoMenu()
        {
             let menu = MenuScene(fileNamed"MenuScene")
             menu.viewController = self
             skView.presentScene(menu)
        }
    }
    
    class MenuScene : SKScene
    {
       var viewController : ViewController!
       func returnToGame()
       {
           view.presentScene(viewcontroller.gameScene)
       }
    }
    

    But, what if you don't want to use custom SKScene classes all the time, use a view controller, or would rather rely on components, why isn't there a convenient way to go back to a scene.

    Well my friend, there is, and it is where userData comes into play

    class GameScene : SKScene
    {
        func gotoMenu()
        {
             let menu = MenuScene(fileNamed:"MenuScene")
             menu.userData = menu.userData ?? ["":Any]()
             menu.userData["backToScene"] = self
             view.presentScene(menu)
        }
    }
    
    class MenuScene : SKScene
    {
       func returnToGame()
       {
           guard let userData = userData, let scene = userData["backToScene"] as? SKScene
           view.presentScene(scene)
       }
    }
    

    Since we are retaining it in the user data, we can now present the old scene anywhere we have access to the menu scene.

    userData is also great in transferring inventory, of course I would create a class to manage the inventory, and just pass the reference via userData

    Now, to create a menu that overlays the current scene, that is as simple as applying a new node onto your scene.

    You can even use a separate SKS file to layout your menu, and overlay it:

    class GameScene : SKScene
    {
        let menu = MenuScene(fileNamed:"MenuScene")
    
        func overlayMenu()
        {
             scene.addChild(menu)  //You probably want to add an SKCameraNode, and add it to the camera instead
        }
        override func update(currentTime: CFTimeInterval) 
        {
            if menu.parent != nil
            {
                menu.update(currentTime:currentTime) //do this only when you need to have a constant update call,  be sure to include additional functionality like `didFinishUpdate` in the approprate functions when needed
            }
        }
    
    
    }
    

    Of course now would be a good time to develop what is called a worldNode, also may be referred to as gameNode

    Essentially what this node is, is the node that holds all your game elements. This allows you to add overlay nodes that can pause your game.

    Your scene hierarchy would like like this:

    SKScene
    --worldNode
    ----all nodes that belong in the game
    --menuNode
    ----all nodes that belong on the menu

    Now at any time, menu can set the worldNode's isPaused state to true, allowing the game to pause and still giving you the ability to interact with the menuNode