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.

Simple Sprite Kit game tutorial- Part2

In the first part of the series, we created the basics scrolling background and a flying spaceship.

iOS Simulator Screen shot Oct 28, 2013 10.51.25 PM

In this part we are going to:

  1. Adding Missile with which spaceship can collide with
  2. Show end of game screen when spaceship collides with missile and give replay option
We are gonna add method to display missile on the screen, add this method at the end on MyScene.m

-(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);
    [self addChild:missile];
}
For explanation of each line, take a look at addShip method. Once you have added this method add [self addMissile] at the end of initWithSize method and run to code to see our missile is appearing on the screen.
iOS Simulator Screen shot Nov 13, 2013 2.28.34 PM
Now we want to move the missile on the screen, so our spaceship can dodge it.
Add another constant for saving missile’s velocity right after background velocity.We want the missile to move little bit faster than background.
static const float BG_VELOCITY = 100.0;
static const float OBJECT_VELOCITY = 160.0;

Now we are going to add method to move the missile, at the end on MyScene.m

- (void)moveObstacle
{
    NSArray *nodes = self.children;//1

    for(SKNode * node in nodes){
        if (![node.name  isEqual: @"bg"] && ![node.name  isEqual: @"ship"]) {
            SKSpriteNode *ob = (SKSpriteNode *) node;
            CGPoint obVelocity = CGPointMake(-OBJECT_VELOCITY, 0); //2
            CGPoint amtToMove = CGPointMultiplyScalar(obVelocity,_dt); //3

            ob.position = CGPointAdd(ob.position, amtToMove); //4
            if(ob.position.x < -100)
            {
                [ob removeFromParent]; //5
            }
        }
    }
}
Let’s go through the code line by line.
  1. We collect all child nodes of the scene.
  2. Set the velocity by which the node is going to move
  3. Set the amount by which node has to move
  4. Set new position of the node
  5. We remove any node which has scrolled off the screen.
We going to call this method in update method like we called moveBg method. So now our update method looks like

-(void)update:(CFTimeInterval)currentTime {

    if (_lastUpdateTime)
    {
        _dt = currentTime - _lastUpdateTime;
    }
    else
    {
        _dt = 0;
    }
    _lastUpdateTime = currentTime;

    [self moveBg];
    [self moveObstacle];
}
Now we want to add missiles randomly at different y axis, every one second.
We are going to add another property to manage when was last time a missile was added on the screen.
    NSTimeInterval _lastMissileAdded;
We are going to use our update method to keep track of lastMissileAdded time interval.
-(void)update:(CFTimeInterval)currentTime {

    if (_lastUpdateTime)
    {
        _dt = currentTime - _lastUpdateTime;
    }
    else
    {
        _dt = 0;
    }
    _lastUpdateTime = currentTime;

    if( currentTime - _lastMissileAdded > 1)
    {
        _lastMissileAdded = currentTime + 1;
            [self addMissile];
    }

    [self moveBg];
    [self moveObstacle];

}
Run the project and you should be seeing multiple missiles on the screen now.
iOS Simulator Screen shot Nov 13, 2013 4.35.14 PM
Now we need to end the game if spaceship collides with the missile. To do that we need to add these two methods at the end of  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];
    }
}

Since we set the scene as the contactDelegate of the physics world earlier, this method will be called whenever two physics bodies collide

There are two parts to this method:

  1. This method passes you the two bodies that collide, but does not guarantee that they are passed in any particular order. So this bit of code just arranges them so they are sorted by their category bit masks so you can make some assumptions later.
  2. Finally, it checks to see if the two bodies that collide are the spaceship and missile, and if so calls the method you wrote earlier.
If you run the current code, you will see if the spaceship collides with missile it will disappear.
Now, let’s create a new scene and layer that will serve as your “End of game” indicator. Create a new file with the iOS\Cocoa Touch\Objective-C class template, name the class GameOverScene, make it a subclass of SKScene, and click Next and then Create.
Then replace GameOverLayer.m with the following code:

#import "GameOverScene.h"
#import "MyScene.h"

@implementation GameOverScene
-(id)initWithSize:(CGSize)size {
    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];

        //4
        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";
        [self addChild:retryButton];

    }
    return self;
}

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

    if ([node.name isEqualToString:@"retry"]) { //2

        //3
        SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];
        MyScene * scene = [MyScene sceneWithSize:self.view.bounds.size];
        scene.scaleMode = SKSceneScaleModeAspectFill;
        [self.view presentScene:scene transition: reveal];

    }
}
@end

There are four parts to initWithSize method:

  1. Sets the background color to white, same as you did for the main scene.
  2. Sets the message to “Game Over”.
  3. Displays the label with Sprite Kit, choosing font and size.
  4. Creating another Sprite Kit label to represent our “Retry” button.
To track when user hit the replay button, we need touchesBegan method. This method is called when user touches anywhere on the screen. Let’s break down this method
  1. We track the node present at the location where user touched the screen.
  2. If the node touched name is equal to “retry”, then it means user has hit the replay button
  3. We transition back to MyScene to restart the game. You can pick from a variety of different animated transitions for how you want the scenes to display – we choose a flip transition here that takes 0.5 seconds.Then you create the scene you want to display, and use thepresentScene:transition: method on the self.view property.
Just one more thing to do before our game is ready to play.
In MyScene.m after removing the spaceship after collision we need to transition to GameOverScene. So replace didBeginContact with following code.

- (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];
        [self.view presentScene:gameOverScene transition: reveal];

    }
}

Run the game and you should see Game over screen if you collide with a missile.

iOS Simulator Screen shot Nov 13, 2013 9.22.06 PM

And that’s a wrap! Here is the full source code for this Sprite Kit tutorial.

I hope you enjoyed this tutorial and are inspired to make your own game.