Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am wanting to 'reset' and 'restart' the GameScene so it is as if the GameScene was first called. I have looked at different methods for doing this, but each time I get a warning that I'm trying to add a node to a parent which already has a parent. However, in my code I delete all my existing nodes so I'm really confused as to how to reset the GameScene. This is how I do it now (this code is called when I want to restart the GameScene from scratch and it is called within the GameScene class):

let scene = GameScene(size: self.size)
scene.scaleMode = .aspectFill
let animation = SKTransition.fade(withDuration: 1.0) 
self.view?.presentScene(scene, transition: animation)
self.removeAllChildren()
self.removeAllActions()
self.scene?.removeFromParent()

1.Edited: I realised that why I was getting this warning: "I'm trying to add a node to a parent which already has a parent" was because I had all the variables for the scene outside of the class and as global variables. However, now when the game restarts, the game is in the bottom left corner. Why is this the case and how do I fix this? - FIXED

2.Edited: Everything works fine now, but now my concern is that deinit{} isn't called even though every node is deleted and the fps doesn't drop over time. Here is what I have in my GameViewController for setting the scene and in my GameScene (every instance relating to the scenes so basically all that is relevant):

import UIKit
import SpriteKit
import GameplayKit

var screenSize = CGSize()

class GameViewController: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    if let view = self.view as! SKView? {
        // Load the SKScene from 'GameScene.sks'
        if let scene = SKScene(fileNamed: "GameScene") {
            // Set the scale mode to scale to fit the window
            scene.scaleMode = .aspectFill
            screenSize = scene.size     
            // Present the scene
            view.presentScene(scene)
        }

        view.ignoresSiblingOrder = true

        view.showsFPS = true
        view.showsNodeCount = true
    }
}

Then my GameScene is basically:

import SpriteKit
import GameplayKit

class GameScene: SKScene, SKPhysicsContactDelegate {
//Declare and initialise variables and enumerations here

deinit{print("GameScene deinited")}

override func didMove(to view: SKView) {
     //Setup scene and nodes
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    //Do other things depending on when and where you touch

    //When I want to reset the GameScene 
    let newScene = GameScene(size: self.size)
    newScene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    newScene.scaleMode = self.scaleMode
    let animation = SKTransition.fade(withDuration: 1.0)
    self.view?.presentScene(newScene, transition: animation)
}

Any answers would be greatly appreciated :)

like image 686
J.Treutlein Avatar asked Jan 14 '17 11:01

J.Treutlein


1 Answers

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.

like image 129
Whirlwind Avatar answered Nov 15 '22 11:11

Whirlwind