Search code examples
iosswiftsprite-kitsknode

Why does SpriteKit crash when transitioning back and forth between scenes


I checked the answer here and on other sites but didn't get a definitive solution.

I have two scenes

Scene 1:

class GameMenuScene: SKScene {

override func didMoveToView(view: SKView) {

    // Add background
    var background: SKSpriteNode = SKSpriteNode(imageNamed: "Starfield")
    background.position = CGPointMake(-20, 0)
    background.size = CGSizeMake(self.size.width + 40, self.size.height)
    background.anchorPoint = CGPointZero
    background.blendMode = SKBlendMode.Replace
    self.addChild(background)

    // Add game title
    gameTitle = SKSpriteNode(imageNamed: "Title")
    gameTitle.name = "Game Title"
    gameTitle.xScale = scale
    gameTitle.yScale = scale
    gameTitle.position = CGPoint(x: CGRectGetMidX(self.frame), y: self.frame.height * 0.75)
    self.addChild(gameTitle)

    // Add scoreboard
    scoreboard = SKSpriteNode(imageNamed: "ScoreBoard")
    scoreboard.name = "Scoreboard"
    scoreboard.xScale = scale
    scoreboard.yScale = scale
    scoreboard.position = CGPoint(x: CGRectGetMidX(self.frame), y: self.frame.height * 0.50)
    self.addChild(scoreboard)

    // Add play button
    playButton = SKSpriteNode(imageNamed: "PlayButton")
    playButton.name = "PlayButton"
    playButton.xScale = scale
    playButton.yScale = scale
    playButton.position = CGPoint(x: CGRectGetMidX(self.frame), y: self.frame.height * 0.25)
    self.addChild(playButton)

    // Add menu score label
    var menuScoreLabel = SKLabelNode()
    menuScoreLabel.fontName = fontName
    menuScoreLabel.fontSize = scoreFontsize
    menuScoreLabel.text = String(score)
    menuScoreLabel.position = CGPointMake(scoreboard.position.x - (scoreboard.size.width / 4), scoreboard.position.y - (scoreboard.size.height / 4))
    menuScoreLabel.zPosition = 10
    self.addChild(menuScoreLabel)

    // Add menu top score label
    var menuTopScoreLabel = SKLabelNode()
    menuTopScoreLabel.fontName = fontName
    menuTopScoreLabel.fontSize = scoreFontsize
    menuTopScoreLabel.text = String(userDefaults.integerForKey("TopScore"))
    menuTopScoreLabel.position = CGPointMake(scoreboard.position.x + (scoreboard.size.width / 4), scoreboard.position.y - (scoreboard.size.height / 4))
    menuTopScoreLabel.zPosition = 10
    self.addChild(menuTopScoreLabel)

}

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
    /* Called when a touch begins */

    for touch in (touches as! Set<UITouch>) {

        // Get touch location
        var touchLocation = touch.locationInNode(self)

        // Check if Play button is presssed

        if playButton.containsPoint(touchLocation) == true {

            //println("right node touched")

            // Transition to GameScene.swift
            var transition: SKTransition = SKTransition.fadeWithDuration(1)

            // Configure the view.
            let scene = GameScene()
            let skView = self.view! as SKView
            skView.showsFPS = true
            skView.showsNodeCount = true

            /* Sprite Kit applies additional optimizations to improve rendering performance */
            skView.ignoresSiblingOrder = true

            /* Set the scale mode to scale to fit the window */
            scene.size = skView.bounds.size
            scene.scaleMode = .AspectFill

            skView.presentScene(scene, transition: transition)

        }
    }
}

}

Scene 2:

class GameScene: SKScene, SKPhysicsContactDelegate {

override func didMoveToView(view: SKView) {
    /* Setup your scene here */

    // Add background
    var background: SKSpriteNode = SKSpriteNode(imageNamed: "Starfield")
    background.position = CGPointMake(-20, 0)
    background.size = CGSizeMake(self.size.width + 40, self.size.height)
    background.anchorPoint = CGPointZero
    background.blendMode = SKBlendMode.Replace
    self.addChild(background)

    // Add mainlayer & labelHolderLayer
    self.addChild(mainLayer)
    self.addChild(labelHolderLayer)

    // Add cannon
    cannon = SKSpriteNode(imageNamed: "Cannon")
    cannon.name = "Cannon"
    cannon.position = CGPoint(x: CGRectGetMidX(self.frame), y: 0)
    self.addChild(cannon)

    // Add score label
    scoreLabel.name = "Score Label"
    scoreLabel.fontName = fontName
    scoreLabel.fontSize = scoreFontsize
    scoreLabel.text = String(score)
    scoreLabel.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height - scoreFontsize)
    scoreLabel.zPosition = 10
    self.addChild(scoreLabel)

    // Add LivesDisplay
    livesDisplay = SKSpriteNode(imageNamed: "Ammo5")
    livesDisplay.name = "livesDisplay"
    livesDisplay.position = CGPoint(x: self.size.width - livesDisplay.size.width, y: self.size.height - livesDisplay.size.height)
    self.addChild(livesDisplay)

    // Settings for new game
    newGame()

}

func gameIsOver() {

    // Set game over flag and stop movement
    gameOver = 1
    mainLayer.speed = 0
    mainLayer.paused = true

    // Add game over label
    gameOverLabel.fontName = fontName
    gameOverLabel.fontSize = gameOverFontsize
    gameOverLabel.text = "Game Over!"
    gameOverLabel.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
    gameOverLabel.zPosition = 10
    labelHolderLayer.addChild(gameOverLabel)

    // Set main menu top score
    if score > userDefaults.integerForKey("TopScore") {

        userDefaults.setInteger(score, forKey: "TopScore")
        userDefaults.synchronize()

    }

    // Run acton sequence (wait a few seconds before transitioning)
    runAction(SKAction.sequence([SKAction.waitForDuration(1), SKAction.runBlock({ () -> Void in

        // Transition back to GameMenuScene
        var transition: SKTransition = SKTransition.fadeWithDuration(1)

        // Configure the view.
        let scene = GameMenuScene()
        let skView = self.view! as SKView
        skView.showsFPS = true
        skView.showsNodeCount = true

        /* Sprite Kit applies additional optimizations to improve rendering performance */
        skView.ignoresSiblingOrder = true

        /* Set the scale mode to scale to fit the window */
        scene.size = skView.bounds.size
        scene.scaleMode = .AspectFill

        skView.presentScene(scene, transition: transition)

    })]))

}

}

When I initially launch the app, I can transition from scene 1 to scene 2. when the game is over, the app transitions back to scene 1. BUT when I try to go to scene 2 again, the app crashes. This only happens on a device, on the simulator, i can go back and forth without any problems.

the error I get is that a SKNode is being added that already exists. I know what it means and the error starts when I try to

    // Add mainlayer & labelHolderLayer
    self.addChild(mainLayer)
    self.addChild(labelHolderLayer)

But I don't see why It can add the background with no problem but crash from there on. Also why does it work on the simulator but not on a device? do I really need to check in my didMoveToView if the nodes are already created?


Solution

  • I found out the mistake I was making.

    I was instantiating the nodes outside my scene 2 class, making them global. Therefore when I went from scene 1 -> scene 2 -> scene 1 there was not problem but going then to scene 2 again caused a crash because the nodes were globally created.

    Solution:

    Moving the following code within the class solved the problem.

    var mainLayer = SKSpriteNode()
    var labelHolderLayer = SKSpriteNode()
    var livesDisplay = SKSpriteNode()
    var scoreLabel = SKLabelNode()