Swift 3 (SpriteKit): Reseting the GameScene after the game ends

元气小坏坏 提交于 2019-11-29 02:34:46

How to reset the scene?

You just have to present a new, same scene again whenever you want. So, you are doing it fine.

Possible leaking problems?

Also, if you don't have leaks in your game, means no strong reference cycles, you don't even need self.removeAllChildren() and self.removeAllActions()... Of course if you explicitly want to stop actions before transition animation starts, the using this method make sense. The point is, when scene deallocates, all objects that depends on it should / will deallocate as well.

Still, if you don't know from the beginning what you are doing and how to prevent from leaks, eg. you are using strong self in block which is a part of an action sequence, which repeats forever, then you certainly have a leak, and self.removeAllActions() might help (in many cases, but it is not an ultimate solution). I would recommend to read about capture lists and ARC in general because it can be useful to know how all that work just because of these situations.

Scene is a root node

Calling removeFromParent() on a scene itself has no effect. Scene is a root node, so it can't be removed in your current context. If you check scene's parent property you will notice that it is nil. Of course it is possible to add a scene to another scene, but in that case, the scene which is added as a child, will act as an ordinary node.

And finally, how to present the new scene ? Easy, like this:

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


    let newScene = GameScene(size: self.size)
    newScene.scaleMode = self.scaleMode
    let animation = SKTransition.fade(withDuration: 1.0)
    self.view?.presentScene(newScene, transition: animation)


}

If something doesn't work for you, it is likely that you have leaks (means your scene isn't deallocated). To check this, somewhere in your GameScene override deinit method, like this:

deinit{print("GameScene deinited")} 

To explain you this a bit further... What should happen is that you should present a new scene, a transition should occur, an old scene should be deallocated, and you should see a new scene with an initial state.

Also overriding deinit will just tell you if the scene is deallocated properly or not. But it will not tell you why. It is up to you to find what in your code retaining the scene.

There are 2 main ways that I can think of that do this. The main way that I go this is that if the game is over, (due to the character health falling to zero, or they collide with an object that causes the round to be over, or time is up or whatever), when that happens I like to transition to a new scene that is a summary screen of their score, how far they made it etc.

I do this by having a bool variable in the main GamePlay scene like this.

    var gameOver: Bool = false

Then in the code that fires off to cause the game to end set that variable = true.

In the update function check to see if gameOver == true and transition to the GameOverScene.

override func update(_ currentTime: TimeInterval) {
    // Called before each frame is rendered

    // Initialize _lastUpdateTime if it has not already been
    if (self.lastUpdateTime == 0) {
        self.lastUpdateTime = currentTime
    }

    // Calculate time since last update
    let dt = currentTime - self.lastUpdateTime

    // Update entities
    for entity in self.entities {
        entity.update(deltaTime: dt)
    }

    self.lastUpdateTime = currentTime

    if gameOver == true {
        print("Game Over!")

        let nextScene = GameOverScene(size: self.scene!.size)
        nextScene.scaleMode = self.scaleMode
        nextScene.backgroundColor = UIColor.black
        self.view?.presentScene(nextScene, transition: SKTransition.fade(with: UIColor.black, duration: 1.5))
    }
}

The update function will check at each frame render to see if the game is over and if it is found to be over it will perform any actions that you need it to and then present the next scene.

Then on the GameOverScene I put a button saying "Retry" and when they click that it fires off the GamePlayScene again, running the view DidLoad function and setting up the GamePlayScene from scratch the way that it should.

Here is an example of how I handle that. There are a few different ways to call a scene transtion. You can give this one a try if it isn't working quite right.

    if node.name == "retryButton" {

            if let scene = GameScene(fileNamed:"GameScene") {
                // Configure the view.
                let skView = self.view! as SKView

                /* 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
                scene.size = skView.bounds.size

                skView.presentScene(scene, transition: SKTransition.fade(withDuration: 2.0))
            }
        }

That is my preferred method of handling the game over transitions.

The other method would be to create a function that resets all of the variables that have changed during the course of playing. Then you could use the same code from the Update function above, but instead of transitioning you could create a label on the scene. If the user clicks the label it would fire off of that function to reset all of the variables that have changed, reset the players locations, stops all actions, resets players health etc. Depending on how many things you have changing during the course of gameplay it'll probably be more practical, (as I've found), to transition to a new scene to give a summary and then reload the GamePlayScene through a button. Then everything will load up just the same as it does the first time that the user entered that main GamePlayScene.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!