7
votes

Normally in a sprite kit game, when a new scene presented, all the nodes in the old scene and their content removed automatically. Now what is, if a node like "HUD" should be not removed? Is there any way in sprite kit to create a node only once and use it in all scenes without removing and creating it again and again every time in every new scene? There must be a technique that makes it possible. that's a serious sprite kit design problem, if it is not possible. But I don't think so. The singleton technique is working great with an audio player, that created only once and used in all scenes. There is surley a way to create a node only once and use it in all scenes. Thanks for any idea.

1
What exactly are you striving for? Is it the code that actually creates the node? Stored values? Both?sangony
@sangony I said "HUD" only for example. What I'm striving for, is, I want to create a node (e.g SKEmitterNode) only once and use it in all game scenes. When I create a node and add it to the scene, the node is automatically removed, when a new scene is presented. I have to create the node again in the new scene. I have to create the same node in every new scene again and again. Therfore I want to create the node only once and use it in all scenes without removing and creating it in every scene. I hope, it is clear now, what I'm striving for. Otherways let me know. Thanks for any ideas.suyama
Ok... ... ... that would be called class instantiation. Create a custom SKEmitterNode class for your node and create an instance of it when you load a new scene.sangony
Thanks. I'm not sure, if I understand you correctly. You mean, I create a custom class MyEmitterNode : SKEmitterNode. I then create a new instance of MyEmitterNode class and add it to my new scene like this: MyEmitterNode *myEmitter = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:@"MyParticle" ofType:@"sks"]]; [sceneOne addChild:myEmitter]; should this node (myEmitter) not be removed, when I present a new scene (sceneTwo)? (sorry for my english)suyama
If you are unfamiliar with the concept of subclassing, you should read up on this subject as it is a cornerstone of any program.sangony

1 Answers

13
votes

You can't create a node that persists between scenes. Once you present a new scene, you would need to add the nodes to this new scene.

For this reason, I do not use SKScenes the way Apple describes in the documentation because of this issue. Not only is it cumbersome to have to add the nodes to the new scene each time but also extremely inefficient for nodes like background nodes that should always be present.

So what I did is create 2 scenes, one for the game scene and one for the menu (GUI).

For the menu scene I subclass SKNodes for my interface and then use SKActions on these nodes to present and dismiss them on the screen so it feels like the user is transitioning between scenes. This gives you total customization because you can present multiple nodes, you can keep nodes on the screen permanently etc.

By subclassing the SKNodes you can organize your code just as you did for the scenes. Each node will represent a "scene" in your App. Then you just need to write a method to present and dismiss these nodes.


I've added some sample code below to show one implementation of using SKNodes as "Scenes." The sample code has a base class called SceneNode which we subclass (just as you would subclass an SKScene). In this implementation, I use the GameScene to handle all transitions between scene nodes*. I also keep track of the current scene node so that I can update its layout in case the scene changes size (such as rotation or window resize on OS X**). Your game might not need this, but it's a great way to dynamically layout your nodes. Anything that you want to add to the background or keep around, simply add it to the GameScene. Anything that you want to add to a scene, simply subclass a SceneNode, transition to it and your good to go.

*You could easily present scene nodes directly from other scene nodes instead of going through the GameScene. However I have found that using the GameScene to handle transitions between nodes works very well, especially when you have many scenes with complex transitions.
**There is a bug on OS X, resizing the window does not call the scene's didChangeSize. You need to manually call it.

class GameViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let scene = GameScene(size:self.view.bounds.size)
        scene.scaleMode = .ResizeFill
        (self.view as! SKView).presentScene(scene)
    }
}

class GameScene: SKScene {
    var currentSceneNode: SceneNode!

    override func didMoveToView(view: SKView) {
        self.backgroundColor = SKColor.whiteColor()
        transitionToScene(.Menu)
    }
    override func didChangeSize(oldSize: CGSize) {
        currentSceneNode?.layout()
    }
    func transitionToScene(sceneType: SceneTransition) {
        switch sceneType {
        case .Menu:
            currentSceneNode?.dismissWithAnimation(.Right)
            currentSceneNode = MenuSceneNode(gameScene: self)
            currentSceneNode.presentWithAnimation(.Right)

        case .Scores:
            currentSceneNode?.dismissWithAnimation(.Left)
            currentSceneNode = ScoresSceneNode(gameScene: self)
            currentSceneNode.presentWithAnimation(.Left)

        default: fatalError("Unknown scene transition.")
        }
    }
}

class SceneNode: SKNode {
    weak var gameScene: GameScene!

    init(gameScene: GameScene) {
        self.gameScene = gameScene
        super.init()
    }
    func layout() {}
    func presentWithAnimation(animation:Animation) {
        layout()
        let invert: CGFloat = animation == .Left ? 1 : -1
        self.position = CGPoint(x: invert*gameScene.size.width, y: 0)
        gameScene.addChild(self)
        let action = SKAction.moveTo(CGPoint(x: 0, y: 0), duration: 0.3)
        action.timingMode = SKActionTimingMode.EaseInEaseOut
        self.runAction(action)
    }
    func dismissWithAnimation(animation:Animation) {
        let invert: CGFloat = animation == .Left ? 1 : -1
        self.position = CGPoint(x: 0, y: 0)
        let action = SKAction.moveTo(CGPoint(x: invert*(-gameScene.size.width), y: 0), duration: 0.3)
        action.timingMode = SKActionTimingMode.EaseInEaseOut
        self.runAction(action, completion: {self.removeFromParent()})
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


class MenuSceneNode: SceneNode {
    var label: SKLabelNode
    var container: SKSpriteNode

    override func layout() {
        container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
    }
    override init(gameScene: GameScene) {
        label = SKLabelNode(text: "Menu Scene")
        label.horizontalAlignmentMode = .Center
        label.verticalAlignmentMode = .Center
        container = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 200, height: 200))
        container.addChild(label)
        super.init(gameScene: gameScene)
        self.addChild(container)
        self.userInteractionEnabled = true
    }
    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        self.gameScene.transitionToScene(.Scores)
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class ScoresSceneNode: SceneNode {
    var label: SKLabelNode
    var container: SKSpriteNode

    override func layout() {
        container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
    }
    override init(gameScene: GameScene) {
        label = SKLabelNode(text: "Scores Scene")
        label.horizontalAlignmentMode = .Center
        label.verticalAlignmentMode = .Center
        container = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 200, height: 200))
        container.addChild(label)
        super.init(gameScene: gameScene)
        self.addChild(container)
        self.userInteractionEnabled = true
    }
    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        self.gameScene.transitionToScene(.Menu)
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

enum SceneTransition{
    case Menu, Scores
}
enum Animation {
    case Left, Right, None
}

enter image description here