Search code examples
sprite-kitsknode

isPaused not working properly in SKNode()


I'm making a card game with Swift 4 and SpriteKit. I made a custom subclass of SKScene, and inside this class is a SKNode which I'm using as a layer (gameLayer), to which I attach all Nodes, which shall be stopped.

I also have made a button, which calls a function (pauseGame) that toggles isPaused of the gameLayer, which works perfectly.

The problem is: I want that when the game moves to the scene, it starts already stopped, the user shall begin the game by pressing a button. But when I call the pauseGame function inside the didMove function, it doesn't work, the gameLayer remains active.

I made some observations putting ' print (gameLayer.isPaused) ' and frame count inside the update function. It showed that actually the scene starts with gameLayer.isPaused set to true inside the didMove as it should be, but after the 2nd frame it gets set to false. I have really no idea where it can happen, as I don't appeal to gameLayer.isPaused anywhere else in code.

Of course a solution would be to call pauseGame after the 2nd frame, but I think its not a clean way.

class BMScene: SKScene {
      let gameLayer = SKNode() 

    func pauseGame(){
      if gameLayer.isPaused {
        gameLayer.isPaused = false
      } else {
        gameLayer.isPaused = true
          }
    }

    override func didMove(to view: SKView) {
    self.pauseGame()
    }

Solution

  • isPaused is a bugged up mess in the world of SpriteKit. I have literally argued with developers at apple about the broken nature of it, and they claim that is how it is intended to work, and we must deal with it. The latest atrocity I know about, is that isPaused is defaulted to true for new scenes, then a message is sent out to unpause it. On top of that, when you unpause a parent node, it also unpauses all children nodes.

    So in your code here, override the isPaused of your scene to be able to capture your pausing event to block it from going to its children

    class BMScene: SKScene {
        let gameLayer = SKNode() 
        private var _paused : Boolean = false
        override var isPaused : Boolean
        {
            get
            {
                return _paused
            }
            set
            {
               _paused = newValue
            }
        }
        func pauseGame(_ state: Boolean? = null){
            gameLayer.isPaused = state ?? !gameLayer.isPaused
        }
    
        override func didMove(to view: SKView) {
            self.pauseGame(true)
        }
    }
    

    This will stop your node from changing its childrens pause state. After this moment, you should have complete control of your gameLayer pause state, and it should flow how you think it is flowing.

    I have also modified your function to allow you to force the pause state, as well as toggle when you want to swap the state.

    E.g.

    Pause:
    pauseGame(true)
    'gameLayer.isPaused is now true
    Unpause:
    pauseGame(true)
    'gameLayer.isPaused is now false
    Toggle:
    pauseGame()
    'gameLayer goes from false to true, or true to false
    

    ?? Means COALESCE, so if state is null, it will move along the chain to the next value, which is !gameLayer.isPaused