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.
-
What exactly are you striving for? Is it the code that actually creates the node? Stored values? Both? – sangony Mar 31 '15 at 12:22
-
@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 Mar 31 '15 at 12:56
-
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 Mar 31 '15 at 13:01
-
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 Mar 31 '15 at 13:37
-
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 Mar 31 '15 at 13:46
-
Of course I know what and how to subclass. Therefore I asked you, if I understand you correctly and if I subclass the SKEmitterNode and create my custom child class of it, then create an instance from it and finally add the instance to my scene.. If I do all that and I have an emitter node in my scene, how can this node stay, when I present a new scene. In the new scene I have to create it again, because it will be removed in the old scene, when a new scene is presented. What is the difference if I create an instance from my custom class or from SKEmitterNode class? – suyama Mar 31 '15 at 14:05
-
Presenting a new scene starts with no nodes hence you cannot make a node "stay". You will always have to add it to a new scene. Creating a custom class allows you to preset properties, modify, run methods, etc... It makes for cleaner and more logical code. – sangony Mar 31 '15 at 14:35
-
Thanks. But either you don't understand what I want, or I don't understand your point. If I create a new emitter node in every new scene, the emitter and his particles start new in the new scene. The old emitter node and his particles in the old scene are stopped and removed. I want, that particles continue playing in the new scenes without stopping and removing in the old scene and without creating and new starting in the new scene. that's exactly my problem. I want to create the emitter node only once and use it in every new scene without creating it again. Sorry, I cann't explain it better. – suyama Mar 31 '15 at 14:56
1 Answers
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
}

- 33,840
- 12
- 45
- 93
-
Thanks a lot . That’s what I mean and what I'm searching for. IMO it’s a serious deficit in the concept / design of the sprite kit. I thought, it must be possible to use the same node (e.g. background emitter node) over all scenes, without creating it each time in each new scene. I accept your great answer and I’ll try to do it, as you described. Thank you! – suyama Mar 31 '15 at 19:11