Swift 3 (SpriteKit): GameScene reload after the end of the game

I want to "reset" and "restart" GameScene, so that as if GameScene was first called. I looked at various methods for this, but every time I get a warning that I am trying to add a node to a parent object that already has a parent. However, in my code, I delete all my existing nodes, so I'm really confused about how to reset GameScene. Here's how I do it now (this code is called when I want to restart GameScene from scratch and it is called in 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 understood why I received this warning: “I am trying to add a node to a parent that already has a parent”, because I had all the variables for the scene outside the class and global variables. However, now that the game is resuming, the game is in the lower left corner. Why is this and how can I fix it? - FIXED

2.Edited: Now everything works fine, but now I'm worried that deinit{} not called, although each node is deleted, and fps does not crash over time. Here is what I have in my GameViewController for setting the scene and in my GameScene (each instance related to the scenes, so basically everything that matters):

 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 very helpful :)

+2
source share
2 answers

How to reset a scene?

You just need to re-introduce the same new scene whenever you want. So, you are all well.

Possible leakage problems?

In addition, if you do not have leaks in your game, then there are no strong reference loops, you do not even need self.removeAllChildren() and self.removeAllActions() ... Of course, if you explicitly want to stop actions before transitional animation begins, use This method makes sense. The fact is that when a scene is freed, all objects that depend on it should / will also be freed.

However, if you do not know from the very beginning what you are doing and how to prevent the leak, for example. you use a strong self in a block, which is part of a sequence of actions that repeats forever, then you probably have a leak, and self.removeAllActions() can help (in many cases, but this is not the final solution). I would recommend reading about capture lists and ARC in general, because it might be useful to know how all this works just because of these situations.

The scene is the root of the node

The call to removeFromParent() in the scene itself has no effect. A scene is the root of a node, so it cannot be deleted in the current context. If you check the parent property of the scene, you will notice that it is zero. Of course, you can add a scene to another scene, but in this case, the scene added as a child will act like a normal node.

And finally, how to introduce a 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 does not work for you, it is likely that you have leaks (which means your scene is not released). To test this, somewhere in your GameScene override method, for example:

 deinit{print("GameScene deinited")} 

To explain it to you a little further ... What should happen is that you have to introduce a new scene, a transition must take place, the old scene must be released, and you should see a new scene with the initial state.

Also, overriding deinit will just tell you if the scene is freed properly or not. But that will not tell you why. It is up to you to find the scene in your code.

+8
source

There are two main ways that I can think of to do this. The main way I do is that if the game is over (due to the character’s health falling to zero or they collide with an object that causes the round to end, or time is up or something else) when this happens I like the transition to a new scene, which is a summary screen of their assessment, how much they did 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 to end the game, set this variable = true.

In the update function, check if gameOver == true and go to 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 in each renderer of the frame to see if the game has ended, and if it is above it, it will perform any actions you need and then present the next scene.

Then on the GameOverScene I put on the “Repeat” button, and when they click on the button, it will disconnect from GamePlayScene again by launching the ViewDrawLoad function and setting up GamePlayScene from scratch as it should be.

Here is an example of how I handle this. There are several different ways to invoke scene transposition. You can give this a try if it doesn't work perfectly 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)) } } 

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

Another method would be to create a function that resets all variables that were changed during the game. Then you can use the same code from the Update function above, but instead of moving, you can create a shortcut on the stage. If the user clicks on the label, he will disable this function until reset all the changed variables, reset the location of the players, stop all actions, reset the health of players, etc. Depending on how many things you change during the course, the gameplay will probably be more practical (as I already found) to go to a new scene to give a resume, and then reload GamePlayScene with a button. Then everything will load in the same way as the first time the user entered this main GamePlayScene.

0
source

Source: https://habr.com/ru/post/1234438/


All Articles