Swift 3 (Spritekit): Reseting the Gamescene After the Game Ends

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

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.

Swift: Deallocate GameScene after transition to new scene?

In my project, I used the following mechanism and it worked well. All my scenes SKScene objects were optional variables. When i need the new scene to show, i create it and present at SKView. When i need to display the new scene, I set the previous object scene object to nil, this immediately reducing the reference count by 1, and becouse of at this moment no one object is not use my scene, the reference count becomes zero and scene was deleted.

The SKScene object is a ordinary class object and ARC works with them like with all reference type objects. You only need to monitor the number of references to the scene. All are finished with the various resources I was start in deinit of SKScene object

The simple example:

At UIViewController we have optional objects of GameScene:

class GameViewController: UIViewController {
var scene1: GameScene?
var scene2: GameScene?
var skView: SKView?

// Action to present next Scene
@IBAction func nextSceneAction(sender: AnyObject) {
print("Next scene")
// Create new GameScene object
scene2 = GameScene(fileNamed:"GameScene")
// Present scene2 object that replace the scene1 object
skView!.presentScene(scene2)
scene = nil
}

override func viewDidLoad() {
super.viewDidLoad()

// Create GameScene object
scene = GameScene(fileNamed:"GameScene")
skView = (self.view as! SKView)
skView!.showsFPS = true
skView!.showsNodeCount = true

skView!.ignoresSiblingOrder = true

scene!.scaleMode = .AspectFill

// Present current scene
skView!.presentScene(scene)
}
}

At GameScene in deinit print some text to show that it object will be deleted:

class GameScene: SKScene {
...

deinit {
print("Deinit scene")
}
}

Debug output after push the nextSceneAction button:

Next scene

Deinit scene

SpriteKit transition between scenes without resetting game

You have lots of options to keep persistent state across your game scenes. I've listed two approaches I've used.

Option A: Maintain a Reference to the Scene

When a scene is swapped out for a new one, the scene is often fully removed from memory. If you hold onto a reference for the scene object somewhere else, and present that reference, no data will be lost.

To maintain a reference over time (and present the scene again when needed), I recommend a scene presenter class with a static instance such as the following:

class SceneCoordinator {
static var shared = SceneCoordinator()

var gameScene : GameScene?
var shopScene : ShopScene?
}

when you initialize a GameScene, also register it with your with SceneCoordinator.shared.gameScene = self. Then, when transitioning away from another scene, you can present the instance you stored in the coordinator class.

didMoveToView() will still be called on the scene when it is presented again. You could move all your initializing code to a separate function, create a new instance var such as var isInitialized = false, and only initialize your content if it is false (and set it to true after your initialize).

The issue with this approach is that scene objects are expensive, which means you could build up a large overhead by not allowing scenes to be released.

Option B: Model Struct

The better way (which would also allow for easier reinit of a scene after your app closes) would be to create a data model of your game state, and provide a function to create a GameScene from your model object.

This method is more consistent with the Model-View-Controller design pattern, and allows your scenes and data to be a lot more lightweight.

Such as:

struct GameModel {
var coins : Int
}

class GameScene : SKScene {
var state : GameModel

convenience init(size: CGSize, state: GameModel) {
self.state = state
// set up scene content from state
}

// lots of fun game scene logic

func playerCollectedCoin() {
state.coins += 1
}

func moveToShopScene() {
// init a new shop scene with the state of this scene
let shop = ShopScene(size: self.view!.bounds.size, state: self.state)
(self.view as! SKView).presentScene(scene)
}
}

class ShopScene : SKScene {
var state : GameModel

convenience init(size: CGSize, state: GameModel) {
self.state = state
// set up scene content from state
}

// lots of fun shop scene logic

func playerSpentCoins(amount: Int) {
state.coins -= amount
}

func moveToGameScene() {
// init a new game scene with the updated state of this scene
let game = GameScene(size: self.view!.size, state: self.state)
(self.view as! SKView).presentScene(game)
}
}

Cannot initialise GameScene more than once using SpriteKit in Swift

You have probably defined this method inside your GameScene class, right?

Let's see what the API doc says about the view property of SKScene.

The view that is currently presenting the scene. (read-only) To
present a scene, you call the presentScene: method or
presentScene:transition: method on the SKView class. If the scene is
not currently presented, this property holds nil.

So I guess you are calling this method after the current scene has been removed from the view.

Is there a better way to reload the entire GameScene?

This approach is right, but you should call restart before the current scene has been removed from the view, otherwise it has no reference to your view.

Do I need to do anything with the old discarded GameScene instance?

Nope.

Once you replace the current scene with the new one, if you don't have strong references to the old scene, ARC does remove it from memory.

To be sure the current scene is being removed you have several easy tools:

1. Debug Navigator

While the game is running take a look + 6 at the Debug Navigator in Xcode, then invoke restart several times.
If the used memory indicator grows up, then probably you are not releasing the GameScene from memory (or some other object).

Sample Image

Not releasing the GameScene is one of the worst thing that can happen in a videogame because a scene holds references (directly or indirectly) to every other node in you current level/screen. It happened during the development of my game so I know what I'm talking about :D

2. deinit

Add the deinit method to you GameScene

class GameScene : SKScene {
deinit {
print("The GameScene has been removed from memory")
}
}

deinit is automatically called when the object is removed from memory. Now each time you restart your scene, you should see the message

The GameScene has been removed from memory

on the console coming from the dead scene.

My GameScene is not positioned correctly after switching scene

Take a look at scene.anchorPoint, maybe you accidentally changed it. Default is CGPoint(x: 0.5, y: 0.5).



Related Topics



Leave a reply



Submit