Swift, spritekit: How to restart GameScene after game over? Stop lagging? - swift

Alright, so I have a sprite kit game in Swift here and I'm having trouble restarting my GameScene after it's game over.
Right now, when the user loses all their lives, a variable gameIsOver is set to true, which pauses specific nodes within the scene as well as sets off a timer. After this timer ends, I move to my Game Over scene. From the Game Over scene, the user can either go back to home or restart the game.
Here is how I transition to my game over scene:
countdown(circle, steps: 120, duration: 5) {
//Performed when timer ends
self.gameSoundTrack.stop()
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewControllerWithIdentifier("GOViewController")
self.viewController!.presentViewController(vc, animated: true, completion: nil)
//Resetting GameScene
self.removeAllChildren()
self.removeAllActions()
self.scene?.removeFromParent()
self.paused = true
gameIsOver = false
}
I pause my scene here is because, if I don't, when GameScene is reloaded gameIsOver is still set to true and my app crashes. I don't know why this occurs considering I set gameIsOver to false here.
After moving from GameScene to my game over scene and back to GameScene, or from GameScene to my home view controller and back to GameScene a couple times, my fps count has decreased so much that the whole game is lagging to the point where gameplay is impossible.
This leads me to believe that I am not removing/disposing of GameScene properly each time I present my game over scene.
I believe I'm having the same problem as here: In Swift on "game over" move from scene to another UIView and dispose the scene? , but I'm new to this and I can't figure out how they solved their problem.
How I can I completely reset/delete GameScene each time I present my Game Over scene in order to stop the lagging?

Your gameIsOver Bool will not keep a value while switching between scenes unless your make it a struct. So instead of
var gameIsOver = false
it should be
struct game {
static var IsOver : Bool = false
}
so when you change the value as things happen you call
game.IsOver = true
//if your calling it in a different View controller or scene than where you created it just put the name before it like so
GameViewController.game.IsOver = true
As for the transition back to your GameScene create a function
func goToGameScene(){
let gameScene:GameScene = GameScene(size: self.view!.bounds.size) // create your new scene
let transition = SKTransition.fadeWithDuration(1.0) // create type of transition (you can check in documentation for more transtions)
gameScene.scaleMode = SKSceneScaleMode.Fill
self.view!.presentScene(gameScene, transition: transition)
}
then whenever you want to reset to GameScene just call
goToGameScene()

Related

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 :)
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.

Keeping the game paused after app become active?

It is my first post on this forum and I apologize in advance if I am doing something not in the right way ! :)
I am making an iphone game with Swift & SpriteKit and I am currently facing a problem. When my app is going to background it calls a function pause (cf. below) but it automatically unpause when the game resumes.
I have seen this very interesting post : Spritekit - Keep the game paused when didBecomeActive (and How to keep SpriteKit scene paused when app becomes active?) but I am stuck.
I don't know how to implement the new SKView class as my View is configured as shown in the below code...
This is how my application works :
class GameViewController: UIViewController {
var scene: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
// Configure the View
let SkView = view as! SKView
SkView.multipleTouchEnabled = true
// Create and configure the scene
scene = GameScene(size: SkView.bounds.size)
scene.scaleMode = .AspectFill
// Present the scene
SkView.presentScene(scene)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("PauseWhenBackGround:"), name:"PauseWhenBackGround", object: nil)
}
func PauseWhenBackGround(notification : NSNotification) {
if scene.Pausing == false{
scene.Pause()
}
}
I've tried the following :
I added a new class which is :
class GameSceneView : SKView {
func CBApplicationDidBecomeActive() {
}
}
Then, I tried to set my view as let SkView = view as! GameSceneView but I got an error saying that I cannot cast the view to MyProjectName.GameSceneView()...
I also tried the following : let SkView! = GameSceneView() as GameSceneView! but I end up with a gray background scene...
Does anyone knows how I can implement the new SKView class to prevent the CBApplicationDidBecomeActive() bug from happening so that the game does not unpause when becoming active ?
Thank you very much in advance ! :)
I think a better way is instead of pausing the whole scene you could create a worldNode in your GameScene and add all the sprites that need to be paused to that worldNode. Its better because if you pause the scene you cannot add pause menu nodes or use touches began etc. It basically gives you more flexibility pausing a node rather than the whole scene.
First create the world node (make global property if needed)
let worldNode = SKNode()
addChild(worldNode)
Than add all the sprites you need paused to the worldNode
worldNode.addChild(sprite1)
worldNode.addChild(sprite2)
Create an enum for your different game states
enum GameState {
case Playing
case Paused
case GameOver
static var current = GameState.Playing
}
Than make a pause func in your game scene
func pause() {
GameState.current = .Paused
//self.physicsWorld.speed = 0 // in update
//worldNode.paused = true // in update
// show pause menu etc
}
And call it like you did above using NSNotification or even better using delegation.
I prefer this method way more than pausing the scene from the gameViewController and also pausing the whole scene.
Create a resume method
func resume() {
GameState.current = .Playing
self.physicsWorld.speed = 1
worldNode.paused = false
// remove pause menu etc
}
and finally add this to your update method
override func update(currentTime: CFTimeInterval) {
if GameState.current == .Paused {
self.physicsWorld.speed = 0
worldNode.paused = true
}
Spritekit sometimes tends to resume the game when the app becomes active again or when an alert such as for in app purchases is dismissed. To avoid this I always put the code to actually pause the game in the update method.
Hope this helps.

How to choose which scene shows up first spritekit/swift?

Where is it declared which scene will show up first on load up of the app? The first file I made which is the game obviously shows up first, but if I'm wanting to switch it so that my main menu pops up first, how and where do I do that?
Thanks
In the view controller, the first scene is set. By default, it's automatically set to GameScene but you can change this by altering the code in the GameScene. You should change this line in unarchive from file:
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as! GameScene to
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as! sceneName
and this line in didMoveToView
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
to
if let scene = GameScene.unarchiveFromFile("GameScene") as? sceneName {
This will cause the game to load sceneName, or whatever you scene is called as the initial scene.

SpriteKit scene transition good practices

I am writing a game using SpriteKit with Swift and have run into a memory concern.
The layout of my game is such that the GameViewController (UIViewController) presents the first SKScene (levelChooserScene) in the viewDidLoad Screen. This scene does nothing more than display a bunch of buttons. When the user selects a button the scene then transitions to the correct scene using skView.presentScene, and when the level is complete, that scene then transitions back to the levelChooserScene and the game is ready for the user to select the next level.
The problem is that when the transition back to the levelChooserScene occurs the memory allocated for the game play scene is not deallocated, so after selecting only a few levels I start receiving memory errors.
Is my design correct in transitioning from SKScene to SKScene, or should I instead return to the GameViewController each time and then transition to the next SKScene from there?
I have found a few posts on here that say I should call skView.presentScene(nil) between scenes, but I am confused on how or where to implement that.
I simply want to transition from one SKScene to another and have the memory used from the outgoing scene to be returned to the system.
This is an example of how I have implemented the SKScene:
class Level3: SKScene
{
var explodingRockTimer = NSTimer()
var blowingUpTheRocks = SKAction()
override func didMoveToView(view: SKView)
{
NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "dismissTheScene:", userInfo: nil, repeats: false)
var wait = SKAction.waitForDuration(0.5)
var run = SKAction.runBlock{
// your code here ...
self.explodeSomeRocks()
}
let runIt = SKAction.sequence([wait,run])
self.runAction(SKAction.repeatActionForever(runIt), withKey: "blowingUpRocks")
var dismissalWait = SKAction.waitForDuration(5.0)
var dismissalRun = SKAction.runBlock{
self.removeActionForKey("blowingUpRocks")
self.dismissTheScene()
}
self.runAction(SKAction.sequence([dismissalWait,dismissalRun]))
}
func explodeSomeRocks()
{
println("Timer fired")
}
//MARK: - Dismiss back to the level selector
func dismissTheScene()
{
let skView = self.view as SKView?
var nextScene = SKScene()
nextScene = LevelChooserScene()
nextScene.size = skView!.bounds.size
nextScene.scaleMode = .AspectFill
var sceneTransition = SKTransition.fadeWithColor(UIColor.blackColor(), duration: 1.5) //WithDuration(2.0)
//var sceneTransition = SKTransition.pushWithDirection(SKTransitionDirection.Down, duration: 0.75) //WithDuration(2.0)
//var sceneTransition = SKTransition.crossFadeWithDuration(1.0)
//var sceneTransition = SKTransition.doorwayWithDuration(1.0)
sceneTransition.pausesOutgoingScene = true
skView!.presentScene(nextScene, transition: sceneTransition)
}
}
Well the thing that was causing my trouble was inserting particle emitters every half second for 5 seconds using SKAction.repeatActionForever() to call the emitter insert function.
This repeatAction apparently was not killed by transitioning to another scene, and was causing the memory for the whole scene to be retained. I switched to SKAction.repeatAction() instead and specify how many time it should fire. The scene now returns all of its memory when I transition to the new scene.
I am not sure I understand this behavior though.
SpriteKit it's not strongly documented when it comes to create complex games. I personally had a problem like this for days until I managed to figure it out.
Some objects retain the reference, so it doesn't deinit. (SKActions, Timers, etc)
Before presenting a new scene I call a prepare_deinit() function where I manually remove the strong references which are usually not deallocated by swift.
func prepare_deinit()
{
game_timer.invalidate() // for Timer()
removeAction(forKey: "blowingUpRocks") // for SKAction in your case
// I usually add the specific actions to an object and then remove
object.removeAllActions()
// If you create your own object/class that doesn't deinit, remove all object
//actions and the object itself
custom_object.removeAllActions()
custom_object.removeFromParent()
}
deinit
{
print("GameScene deinited")
}
The last problem I encountered was that the new scene was presented much faster than my prepare_deinit() so I had to present the new scene a little later, giving the prepare_deinit() enough time to deallocate all objects.
let new_scene =
{
let transition = SKTransition.flipVertical(withDuration: 1.0)
let next_scene = FinishScene(fileNamed: "FinishScene")
next_scene?.scaleMode = self.scaleMode
next_scene?.name = "finish"
self.view?.presentScene(next_scene!, transition: transition)
}
run(SKAction.sequence([SKAction.run(prepare_deinit), SKAction.wait(forDuration: 0.25), SKAction.run(exit_to_finish)]))

Spritekit- Scene transition Delay

I have a main screen which has a button, when this button is pressed it should transition immediately to another scene, but it doesn't. It actually takes a few seconds. Is there a way that I could load all the nodes in that scene beforehand? (Example: in the games load screen)
This is my code:
let pressButton = SKAction.setTexture(SKTexture(imageNamed: "playButtonP.png"))
let buttonPressed = SKAction.waitForDuration(0.15)
let buttonNormal = SKAction.setTexture(SKTexture(imageNamed: "playButton.png"))
let gameTrans = SKAction.runBlock(){
let doors = SKTransition.doorsOpenHorizontalWithDuration(0)
let levelerScene = LevelerScene(fileNamed: "LevelerScene")
self.view?.presentScene(levelerScene, transition: doors)
}
playButton.runAction(SKAction.sequence([pressButton,buttonPressed,buttonNormal,gameTrans]))
You could preload the SKTextures you're using in LevelerScene before presenting the scene. Then, once the loading has finished you would then present the scene. Here's an example from the Apple's Documentation, translated to Swift:
SKTexture.preloadTextures(arrayOfYourTextures) {
if let scene = GameScene(fileNamed: "GameScene") {
let skView = self.view as! SKView
skView.presentScene(scene)
}
}
In your case you have a couple of options:
1.
Keep an array of the textures, which you need to use in LevelerScene, that you preload in GameScene:
class LevelerScene : SKScene {
// You need to keep a strong reference to your textures to keep
// them in memory after they've been loaded.
let textures = [SKTexture(imageNamed: "Tex1"), SKTexture(imageNamed: "Tex1")]
// You could now reference the texture you want using the array.
//...
}
Now in GameScene, when the user presses the button:
if let view = self.view {
let leveler = LevelerScene(fileNamed: "LevelerScene")
SKTexture.preloadTextures(leveler.textures) {
// Done loading!
view.presentScene(leveler)
}
}
There's no way you can get around having to wait a little bit, but taking this approach the main thread won't get blocked and you'll be able to interact with GameScene whilst LevelerScene is loading.
You could also use this approach to make a loading SKScene for LevelerScene. GameScene would take you to the loading scene, which would load the textures and then move you to LevelerScene once it was complete.
It's important to note that because the reference to the textures is in LevelerScene, once LevelerScene is deinit-ed the textures will be removed from memory. Therefore, if you want to go back to LevelerScene you'll need to load the textures again.
2. You could use SKTexture.preloadTextures in GameViewController before any SKScenes have been presented. You'd need to keep a strong reference to these textures (perhaps in a singleton) which you could then reference in LevelerScene (or anywhere else you needed them in the app).
With this approach, because the SKTextures are stored outside of a scene, they won't be removed from memory when you transition to the next scene. This means you won't have to load the textures again if you leave and then go back to a scene. However, if you've got a lot of textures taking up a lot of memory you could run into some memory issues.
For more information see Preloading Textures Into Memory from Working with Sprites.
Hope that helps!