Simple Parse Tutorial with Sprite Kit game

In this tutorial we are going to add scoring logic to Sprite Kit game we build in previous tutorial and save the score on Parse. Once the score is saved on Parse we are going to fetch the highest score for the user and display it.

Adding Scoring Logic to game

We are going to add a variable ‘player_score’ to keep track of user’s current score in MyScene.m


NSInteger player_score;

Our scoring logic is fairly simple, we give user a point on every missile appearing on the screen. You can pick any logic you like to calculate score for your game.


-(id)initWithSize:(CGSize)size {
    if (self = [super initWithSize:size]) {
        self.backgroundColor = [SKColor whiteColor];
        [self initalizingScrollingBackground];
        [self addShip];

        //Making self delegate of physics World
        self.physicsWorld.gravity = CGVectorMake(0,0);
        self.physicsWorld.contactDelegate = self;
        player_score = 0; //initializing score as zero
    }

    return self;
}

-(void)addMissile
{
    //initalizing spaceship node
    SKSpriteNode *missile;
    missile = [SKSpriteNode spriteNodeWithImageNamed:@"red-missile.png"];
    [missile setScale:0.15];

    //Adding SpriteKit physicsBody for collision detection
    missile.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:missile.size];
    missile.physicsBody.categoryBitMask = obstacleCategory;
    missile.physicsBody.dynamic = YES;
    missile.physicsBody.contactTestBitMask = shipCategory;
    missile.physicsBody.collisionBitMask = 0;
    missile.physicsBody.usesPreciseCollisionDetection = YES;
    missile.name = @"missile";

    //selecting random y position for missile
    int r = arc4random() % 300;
    missile.position = CGPointMake(self.frame.size.width + 20,r);

    player_score = player_score + 1; //adding to score when ever new missile is created
    [self addChild:missile];

}

We have to update our initWithSize and addMissile methods to incorporate scoring logic.

Now we want to display score at the end of game screen. To do that we going to modify our GameOverScene.m initWithSize method and add declaration in GameOverScene.h

GameOverScene.h


#import <SpriteKit/SpriteKit.h>

@interface GameOverScene : SKScene

-(id)initWithSize:(CGSize)size score: (NSInteger)player_score;

@end

GameOverScene.m



#import "GameOverScene.h"
#import "MyScene.h"
#import <Parse/Parse.h>

@implementation GameOverScene
-(id)initWithSize:(CGSize)size score: (NSInteger)player_score{ //updated the existing method
    if (self = [super initWithSize:size]) {

        // 1
        self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];

        // 2
        NSString * message;
        message = @"Game Over";
        // 3
        SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        label.text = message;
        label.fontSize = 40;
        label.fontColor = [SKColor blackColor];
        label.position = CGPointMake(self.size.width/2, self.size.height/2);
        [self addChild:label];

        NSString * retrymessage;
        retrymessage = @"Replay Game";
        SKLabelNode *retryButton = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        retryButton.text = retrymessage;
        retryButton.fontColor = [SKColor blackColor];
        retryButton.position = CGPointMake(self.size.width/2, 50);
        retryButton.name = @"retry";
        [retryButton setScale:.5];

        [self addChild:retryButton];

        NSString * playerscoremsg = [NSString stringWithFormat:@"Player Score: %ld",(long)player_score];

        SKLabelNode *playerscore = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        playerscore.text = playerscoremsg;
        playerscore.fontColor = [SKColor blackColor];
        playerscore.position = CGPointMake(self.size.width/2, 250);
        playerscore.name = @"Player Score";
        [playerscore setScale:.5];

        [self addChild:playerscore];        
    }
    return self;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInNode:self];
    SKNode *node = [self nodeAtPoint:location];

    if ([node.name isEqualToString:@"retry"]) {
        SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];

        MyScene * scene = [MyScene sceneWithSize:self.view.bounds.size];
        scene.scaleMode = SKSceneScaleModeAspectFill;
        [self.view presentScene:scene transition: reveal];

    }

}
@end

In GameOverScene.m we have added another label which is displaying players score. Before we try out our scoring logic we need to make one more change in MyScene.m


- (void)didBeginContact:(SKPhysicsContact *)contact
{
    SKPhysicsBody *firstBody, *secondBody;
    if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
    {
        firstBody = contact.bodyA;
        secondBody = contact.bodyB;
    }
    else
    {
        firstBody = contact.bodyB;
        secondBody = contact.bodyA;
    }

    if ((firstBody.categoryBitMask & shipCategory) != 0 &&
        (secondBody.categoryBitMask & obstacleCategory) != 0)
    {
        [ship removeFromParent];
        SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];
        SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size score:player_score]; //updated this method so we can send players score to game over scene.
        [self.view presentScene:gameOverScene transition: reveal];

    }
}

Now we are ready to run our app and test out scoring logic.

iOS Simulator Screen shot Jan 13, 2014 11.39.01 PM

Saving Score on Parse

Download Parse SDK and follow the steps given in this guide to add Parse Framework to your app.

Once you have added Parse framework to your app, create a account on Parse.

Login to you account to get started.

create_new_app

Once you have created a new app, navigate to setting tab and copy Application ID and ClientKey.app-keys-big

Once you have the key copied Navigate to Data Browser to start creating your database.

create-new-table

After adding the class we going to add columns to PlayerScore class.

create-custom

We are going to add first column user_id which takes string value. Add another column ‘score’ which take number value.

new-table

Your class should look like this at the end.

Alright now are ready to add Parse related code in our app.

Step 1) Adding Parse Keys to app.

In AppDelegate.m update method didFinishLaunchingWithOptions to following


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [Parse setApplicationId:@"Application Key" //replace these values with keys you got from Parse account
                  clientKey:@"Client Key"];
    
    return YES;
}

Step 2) Push user score to Parse at end of the game.

Before we push user score to Parse we need a way to identify individual user, which we are going to save in “user_id” column in our Parse Class. We are going to use users “identifierForVendor” as unique identifier for now.

identifierForVendor:  The value of this property is the same for apps that come from the same vendor running on the same device. A different value is returned for apps on the same device that come from different vendors, and for apps on different devices regardless of vendor. You can read more about it here.

Note:  Every time you delete the app from your simulator and install it again you are going to get a different ID. So you will be considered as different user every time you delete the app and install it again.

We are going to update GameOverScene.m initialization method with the following code.



-(id)initWithSize:(CGSize)size score: (NSInteger)player_score{
    if (self = [super initWithSize:size]) {

        self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];

        NSString * message;
        message = @"Game Over";

        SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        label.text = message;
        label.fontSize = 40;
        label.fontColor = [SKColor blackColor];
        label.position = CGPointMake(self.size.width/2, self.size.height/2);
        [self addChild:label];

        NSString * retrymessage;
        retrymessage = @"Replay Game";
        SKLabelNode *retryButton = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        retryButton.text = retrymessage;
        retryButton.fontColor = [SKColor blackColor];
        retryButton.position = CGPointMake(self.size.width/2, 50);
        retryButton.name = @"retry";
        [retryButton setScale:.5];

        [self addChild:retryButton];

        NSString * playerscoremsg = [NSString stringWithFormat:@"Player Score: %ld",(long)player_score];

        SKLabelNode *playerscore = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        playerscore.text = playerscoremsg;
        playerscore.fontColor = [SKColor blackColor];
        playerscore.position = CGPointMake(self.size.width/2, 250);
        playerscore.name = @"Player Score";
        [playerscore setScale:.5];

        [self addChild:playerscore];

        UIDevice * currentDevice = [UIDevice currentDevice];
        NSString *deviceIDString = [currentDevice.identifierForVendor UUIDString]; //getting unique id for the user

        NSNumber *playerScoreNum = [NSNumber numberWithInt:player_score]; //converting score into NSNumber format in which Parse expect the score

       PFObject *scoreObject = [PFObject objectWithClassName:@"PlayerScore"];
        [scoreObject setObject:deviceIDString forKey:@"user_id"]; //user's unique id

        [scoreObject setObject:playerScoreNum forKey:@"score"]; //user's score 

        [scoreObject saveInBackground]; //saving in background, so our application is not halted while saving the score.

    }
    return self;
}

If you run the code now, you will at the end of the game your score is getting pushed to Parse.

Data Browser   Parse

Step 3)  Fetch score from Parse and display the highest score so far

We are going to fetch all previous scores of current user and sort them descending order to get the highest score on top. We are going to add the following lines to GameOverScene.m initialization method.


-(id)initWithSize:(CGSize)size score: (NSInteger)player_score{
    if (self = [super initWithSize:size]) {

        self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];

        NSString * message;
        message = @"Game Over";

        SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        label.text = message;
        label.fontSize = 40;
        label.fontColor = [SKColor blackColor];
        label.position = CGPointMake(self.size.width/2, self.size.height/2);
        [self addChild:label];

        NSString * retrymessage;
        retrymessage = @"Replay Game";
        SKLabelNode *retryButton = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        retryButton.text = retrymessage;
        retryButton.fontColor = [SKColor blackColor];
        retryButton.position = CGPointMake(self.size.width/2, 50);
        retryButton.name = @"retry";
        [retryButton setScale:.5];

        [self addChild:retryButton];

        NSString * playerscoremsg = [NSString stringWithFormat:@"Player Score: %ld",(long)player_score];

        SKLabelNode *playerscore = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        playerscore.text = playerscoremsg;
        playerscore.fontColor = [SKColor blackColor];
        playerscore.position = CGPointMake(self.size.width/2, 250);
        playerscore.name = @"Player Score";
        [playerscore setScale:.5];

        [self addChild:playerscore];

        UIDevice * currentDevice = [UIDevice currentDevice];
        NSString *deviceIDString = [currentDevice.identifierForVendor UUIDString]; 

        NSNumber *playerScoreNum = [NSNumber numberWithInt:player_score]; 

        PFQuery *query = [PFQuery queryWithClassName:@"PlayerScore"]; //creating query for Parse
        [query whereKey:@"user_id" equalTo:deviceIDString];
        [query orderByDescending:@"score"]; //Sorting the score so we have highest score on the top

        [query findObjectsInBackgroundWithBlock:^(NSArray *scoreArray, NSError *error) {

            NSNumber* hightestScore = [scoreArray.firstObject objectForKey:@"score"]; //highest score is first object
            NSLog(@"highest score %@ devise %@",hightestScore, deviceIDString);

            if (hightestScore < playerScoreNum){
                hightestScore = playerScoreNum; //if highest score so far is smaller than current score, display current score
            }
            NSString * highscoremsg = [NSString stringWithFormat:@"Highest Score: %@",hightestScore]; 

            SKLabelNode *highscore = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"]; //Displaying highest score label
            highscore.text = highscoremsg;
            highscore.fontColor = [SKColor greenColor];
            highscore.position = CGPointMake(self.size.width/2, 200);
            [highscore setScale:.5];
            [self addChild:highscore];

        }];

       PFObject *scoreObject = [PFObject objectWithClassName:@"PlayerScore"];
        [scoreObject setObject:deviceIDString forKey:@"user_id"];

        [scoreObject setObject:playerScoreNum forKey:@"score"];

        [scoreObject saveInBackground];

    }
    return self;
}

We are querying for the highest score in the background so that it does not halt the flow of the app, and user is displayed other information without a delay. We only create the label for highest score once the data is fetched from Parse.

Now run the app and see your highest score.

iOS Simulator Screen shot Jan 20, 2014 1.55.36 PM

 

I hope this was helpful.  You can download the code for this tutorial here.

About these ads

5 thoughts on “Simple Parse Tutorial with Sprite Kit game

  1. Hi! While I was just not looking for this particular example, you helped me with the Game Over screen as I was trying to create a “button” that would send the user back to the game. Thanks!

  2. hey there! nice tutorial! its written very easy to understand! im searching something like that but not to store with parse.. maybe with nsuserdefaults to store the info to the disk..can you even help me or write a new tutorial for this case? all the best for the future!

    • Hi! Thanks! Saving scores in NSUSerDefault is not the right place to do it, as it is for saving information like login session, username etc things which persist. You should try saving it with CoreData instead. I will be writing a CoreData tutorial soon.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s