How to Save your Game’s Data: Part 1/2
This tutorial will walk you through how to save your game data – locally on the device and also up in the cloud. By Marin Todorov.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How to Save your Game’s Data: Part 1/2
40 mins
Immortalizing Your Photo
Now you need to make sure the photo is useful to the player. In this section, you'll add the delegate methods to the image picker view controller to make sure your handsome face saves to the disc, along your high score and total distance flown.
First open RWGameData.h and add a new property:
@property (strong, nonatomic) UIImage* pilotPhoto;
Switch to RWGameData.m and along with the other constants near the top of the file, add this:
static NSString* const SSGameDataPilotPhotoKey = @"pilotPhoto";
This will be the key you use to send the photo to the encoder/decoder.[TODO:I replaced 'encode' with 'send' to reduce redundancy. Please confirm the word choice is suitable] Speaking of which, scroll to encodeWithCoder:
and add the following to the end to encode the photo property:
if (self.pilotPhoto) {
NSData* imageData = UIImagePNGRepresentation(self.pilotPhoto);
[encoder encodeObject:imageData forKey: SSGameDataPilotPhotoKey];
}
This code is a tiny bit different from how you've encoded the rest of the properties.
Note how you check whether self.pilotPhoto
is set, and encode that property only if it is. After all, there's no need to write anything if the user is camera-shy!
Encoding the photo is a bit different when you compare it to the rest of the properties. UIImage does not implement NSCoding
, so you have to convert it to a buffer of bytes first.
To do this, you use UIImagePNGRepresentation
to convert the image to NSData
(which does implement NSCoding
), and then you can use encodeObject:forKey:
as usual.
Now scroll to initWithCoder:
. Look inside the if
statement, which is after the code that initializes the other properties; add this code to initialize the photo:
NSData* imageData = [decoder decodeObjectForKey: SSGameDataPilotPhotoKey];
if (imageData) {
_pilotPhoto = [[UIImage alloc] initWithData:imageData];
}
You're reversing what you did earlier. First, you try to decode the object for the photo key. If it exists, you just create a new UIImage
instance out of the data.
Implementing the image picker delegate methods in your scene is the last step to immortalizing the player's photo. Open MyScene.m and at the top, alongside the rest of the imports, add:
#import "UIImage+Mask.h"
First, create a method stub for adding the pilot. You will be adding to it later in the tutorial. Please this code at the bottom of MyScene.m, just before the @end
directive.
-(void) setupPilot
{
// Code to go here.
}
Next, create a new method underneath the previous one to handle a successful photo capture.
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
//1
lockToPortraitOrientation = NO;
//2
[picker dismissViewControllerAnimated:YES
completion:^{
//3
UIImage* photoTaken = info[UIImagePickerControllerOriginalImage];
UIImage* pilotImage = [photoTaken imageWithSize: CGSizeMake(25, 25) andMask:[UIImage imageNamed:@"25_mask.png"]];
//4
[RWGameData sharedGameData].pilotPhoto = pilotImage;
[[RWGameData sharedGameData] save];
//5
[self setupPilot];
//6
self.paused = NO;
}];
}
This delegate method received the captured photo, so you're good to process and use it. You do this in several steps:
- First you reset the screen orientation to landscape only.
- Then you dismiss the image picker view controller.
- When the view controller is dismissed, you grab the captured photo (located in
info[UIImagePickerControllerOriginalImage]
), then store a resized and masked version of it inpilotImage
. - In the next couple lines you store the photo in
RWGameData
and then invokesave
to make sure the photo saves to the disc immediately. - Then you call
setupPilot
, which displays the photo on screen. - Finally, you un-pause the scene with
setupPilot
, which you'll add in few moments
Now you'll wrap up the image picker part of the code. Add the delegate method, the one called when the user cancels the image capture:
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
lockToPortraitOrientation = NO;
[picker dismissViewControllerAnimated:YES
completion:^{
self.paused = NO;
}];
}
This method just resets the orientation back to landscape, dismisses and image picker view controller, and un-pauses the scene.
You're at the last step before wrapping up! Now you just need to display the photo on the screen. Make sure setupPilot:
looks like the following:
-(void)setupPilot
{
//1
if ([RWGameData sharedGameData].pilotPhoto) {
//2
UIImage* pilotImage = [[RWGameData sharedGameData].pilotPhoto imageWithSize: CGSizeMake(25, 25) andMask:[UIImage imageNamed:@"25_mask.png"]];
//3
[[_ship childNodeWithName:@"Pilot"] removeFromParent];
//4
SKTexture* pilotTexture = [SKTexture textureWithImage:pilotImage];
SKSpriteNode* pilotSprite = [SKSpriteNode spriteNodeWithTexture: pilotTexture];
pilotSprite.name = @"Pilot";
pilotSprite.position = CGPointMake(28, 5);
[_ship addChild: pilotSprite];
}
}
- First, you check whether there's a pilot's photo. You're going to call this method when the game starts, so you need to check.
- Then you make sure the photo is properly sized and masked, because once you persist it via the encoder the masking will actually disappear.
- Check for an existing pilot photo node with name of "Pilot" and if found, remove it from the scene.
- Create a new texture from the pilot photo and create a sprite node out of the resulting texture. Finally, position the sprite node and add it as a child to the space ship.
Your app calls this method when you capture a new photo, and also when you build the scene and load the game data from a file. If the player sets his or her photo in a previous app launch, you'll need to load the photo when the game starts.
Do that in initWithSize:
. Just find the line where you add the ship to the scene: [self addChild:_ship];
and add this above:
[self setupPilot];
Perfect! Now when you start the game the app will check for persisted photo, and if found, it'll display on-screen. If the player takes a new photo - it overwrites and saves the new one to the game data file.
Build and run. You should be able to take your photo and see yourself piloting that space ship! Yeah!
Where To Go From Here?
Here is the example project up to this point.
Here's a recap of what you learned in the part I of this tutorial series:
- How to encode/decode data to the disc
- How to encode/decode more complex data (i.e. beyond primitives)
- How to have optional game data (i.e. can be set or undefined)
- Bonus: using dynamic photos in your game scene and advanced screen orientation handling
In part two, you'll learn how to prevent malicious users from tinkering with your game's data file and how to share the game data between devices via iCloud.
If you have any questions or comments, please share them with us in the comments below!