SpriteKit: Passing data between scenes

I am stuck trying to pass data between scenes "SKScene" in SpriteKit. For instance I would like to pass the score from level A to Level B.

Maybe the solution is archiving but I would like to implement something more simpler like the way we use with view controllers.

Any clue in this regard will be very much appreciated.

Answers


If you're going to be passing around the score to a lot of different scenes, you might want to store it in NSUserDefaults or some accessible storage mechanism. However, if you're looking to pass data between SpriteKit objects, every SKNode (including SKScene) has a dictionary property called userData that you can use for whatever you so desire. Here's an example of how you might pass the score between scenes:

 - (void)changeScene
 {
      SKView *spriteView = (SKView *) self.view;
      SKScene *currentScene = [spriteView scene];
      SKScene *newScene = [MySceneClass scene];
      [newScene.userData setObject:[currentScene.userData objectForKey:@"score"] forKey:@"score"];
      [spriteView presentScene:newScene];

 }

Store the score in your view controller instance, or subclass the SKView and store it there. This will retain any object for the lifetime of the view.

If you need the scores to be persisted between app restarts, use NSUserDefaults.

Kobold Kit actually makes stuff like this easy with no custom subclasses. The view has (and any node can have) a KKModel object which is a key/value storage for both integral types (float, int, etc via KKMutableNumber) and arbitrary objects.

So you can persist and access your highscores object from any node by tying it to the view:

[self.kkView.model setObject:highscores forKey:@"highscores"];

The kkView property is shorthand for writing (KKView*)self.view.

There's absolutely no need to use or benefit in using singletons for objects whose lifetime is tied either to the scene or the view. They belong to either the scene or the view, period.


This approach worked for me and I think it is correct. So far this is the easiest and fastest way to pass data between scenes which I found.

1. Basic informations about userData:

1.1. Hold the apple "Command key" and press the left mouse button (in Xcode) on the "userData" string (for example. "self.userData ;"). You will get the following info:

// An optional dictionary that can be used to hold user data pretaining to the node. Defaults to nil. 
@property (SK_NONATOMIC_IOSONLY, retain) NSMutableDictionary *userData;

1.2. Visit the apple docs:userData

1.3 userData is an NSMutableDictionary which is basically an special array. Beside the value it contains a key which is bind to the value. With this key you can find values. This might be helpful aswell:apple docs:NSMuttableDictionary

2. The solution:

2.1. firstScene

//somewhere in firstScene.m
//how the tranisiton from firstScene to secondScene is going to look and how long it is goint to take   
SKTransition *reveal = [SKTransition moveInWithDirection:SKTransitionDirectionDown duration:0.5];
 SKView * skView = (SKView *)self.view;
SKScene *secondScene = [WBMGameEndsScene sceneWithSize:skView.bounds.size];
//You need to initialize the NSMD since it is by default nil.
secondScene.userData = [NSMutableDictionary dictionary];

Here I am refering to the userData of the secondScene by adding a object which is my score . For test purpose it is just 6. I will add my score instance variable. You can add what ever you want since it accepts objects of type "id". The key is important. You will use it in the secondScene.m to access the object value since. Check that you dont type it wrong.

[secondScene .userData setObject:@"6" forKey:@"score"];
//Testing
NSLog(@"Is it finally working -- %@",[secondScene .userData objectForKey:@"score"]);
//The secondScene will scale to fit the whole SKView
secondScene.scaleMode = SKSceneScaleModeAspectFill;
 //present the secondScene
[self.scene.view presentScene: secondScene transition:reveal];

*WBMGameEndsScene is my secondScene. I created it in Xcode with "File - New File ..." as :SKScene . It will contain the userData shown later on.

2.2. secondScene

//secondScene.m ( ex. in my :"@implementation WBMGameEndsScene")

These next lines of code check if the value has been correctly added:

-(void)didMoveToView:(SKView *)view
{
    NSLog(@"-- -(void)willMoveFromView:(SKView *)view --");
    NSLog(@"Working score is : %@",[self.userData valueForKey:@"score"]);
    NSLog(@"Working score is : %@",[self.userData objectForKey:@"score"]);
}

Console output is:

2014-03-12 15:29:06.804 AppTest[4841:60b] -(id)initWithSize:(CGSize)size
2014-03-12 15:29:06.806 AppTest[4841:60b] scoreLabel
2014-03-12 15:29:06.812 AppTest[4841:60b] successMessage
2014-03-12 15:29:06.815 AppTest[4841:60b] Is it finally working -- 6
2014-03-12 15:29:06.815 AppTest[4841:60b] -- -(void)willMoveFromView:(SKView *)view --
2014-03-12 15:29:06.816 AppTest[4841:60b] Working score is : 6
2014-03-12 15:29:06.816 AppTest[4841:60b] Working score is : 6

If I got something wrong please correct me :). Specs: iOS 7.0 ,Xcode Version 5.1 (5B130a), OS X 10.9.2


Where/how are you transitioning between the scenes? If could you show just the code where you create your new scene that would help. I may be missing the point of the question but if you have a 'scene manager'/navigator of some sort to do this then simply save the score in a local var temporarily and pass that into your new scene.


Apple documentation states that userData is the way to go for storing node specific data here in Sprite Kit Best Practices

Use the userData property on nodes to store game-specific data, especially if you are not implementing your own subclasses.

Therefore I would accept @doctorBroctor's answer as the correct one for when using Sprite Kit natively.

However, you should remember to initialize userData for the target node before setting a value as @LearnCocos2d points out in this answer. You can do this inside initWithSize: method of the SKScene subclass you are about to transition into.


    // First Scene
    NSUserDefaults.standardUserDefaults().setInteger(12345, forKey:"SCORE")

    // Second Scene
    if let myScore: AnyObject = NSUserDefaults.standardUserDefaults().objectForKey("SCORE") {
        println(myScore)
        finalScore = myScore as IntegerLiteralType
    }

Need Your Help

Java: JDOQL startsWith query, case sensitive

java jdo datanucleus jdoql

I'm using the .startsWith() filter in a JDOQL query but it's case sensitive.