featured

By Megha GulatiNovember 23, 2013

Simple iOS Sprite Kit Game Tutorial

In this tutorial we are going to make a basic 2D game with Sprite Kit.

You can get the full source code of this tutorial on github. We learned how to do this as part of our Code Fellows iOS bootcamp, and I thought I’d write it up to share with you.

image

Getting Started

Start Xcode and select File\New\Project from the main menu. Select the iOS\Application\SpriteKit Game template and click Next.

image

Enter your game name for Product Name, choose iPhone for devices and leave the Class Prefix blank and click Next.

image

On Device Orientation deselect Portrait.

image

In XCode’s toolbar, select the iPhone (4-inch) Simulator and click Run

image

You will see a single label that says “Hello World!” and when you click anywhere on the screen, a rotating space ship will appear.

image

SpriteKit Physics and Collision Detection

SpriteKit comes with a physics engine built-in, which helps simulate realistic movements and collision detection. We are going to use SpriteKit’s physics engine to move our spaceship and detect collisions between spaceship and missile. We will build our game in the following steps:

  1. Adding the main character of the game (spaceship) on the screen (Part 1)
  2. Adding scrolling background (Part 1)
  3. Adding a missile with which spaceship can collide (Part 2)
  4. Show end of game screen when the spaceship collides with a missile and give replay option (Part 2)

Before starting these steps download the art folder from here, and add it to your Xcode project.

Adding Main Character

Open MyScene.m and you will see the code for displaying rotating space ship. For now delete everything in MyScene.m and replace it with:

#import "MyScene.h"
@implementation MyScene{
    SKSpriteNode *ship;
    SKAction *actionMoveUp;
    SKAction *actionMoveDown;
}

-(id)initWithSize:(CGSize)size {
    if (self = [super initWithSize:size]) {
        self.backgroundColor = [SKColor whiteColor];        
        //Making self delegate of physics World
        self.physicsWorld.gravity = CGVectorMake(0,0);
        self.physicsWorld.contactDelegate = self;

    }
    return self;
}
@end

Let’s go over what this does line by line. Variables ship, actionMoveUp and actionMoveDown we will be using later on in the code. initWithSize: is the method that gets called when the scene is first created.

  self.backgroundColor = [SKColor whiteColor];        

We set the background color to whiteColor.

    self.physicsWorld.gravity = CGVectorMake(0,0);
    self.physicsWorld.contactDelegate = self;

These lines set up the physics world to have no gravity, and sets the scene as the delegate to be notified when two physics bodies collide.

Add these lines right after importing MyScene.h

#import "MyScene.h"

static const uint32_t shipCategory =  0x1 << 0;
static const uint32_t obstacleCategory =  0x1 << 1;

These will setup two categories we will need in a bit.

Now we are going to add the spaceship to the screen. Add this method after initWithSize method

-(void)addShip
{
    //initalizing spaceship node
    SKSpriteNode * ship = [SKSpriteNode new];
    ship = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];
    [ship setScale:0.5];
    ship.zRotation = - M_PI / 2;

    //Adding SpriteKit physicsBody for collision detection
    ship.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ship.size];
    ship.physicsBody.categoryBitMask = shipCategory;
    ship.physicsBody.dynamic = YES;
    ship.physicsBody.contactTestBitMask = obstacleCategory;
    ship.physicsBody.collisionBitMask = 0;
    ship.name = @"ship";
    ship.position = CGPointMake(120,160);

    [self addChild:ship];

    actionMoveUp = [SKAction moveByX:0 y:30 duration:.2];
    actionMoveDown = [SKAction moveByX:0 y:-30 duration:.2];
}

Edit initWithSize method and add [self addShip] after setting background color to whiteColor

self.backgroundColor = [SKColor whiteColor];  
[self addShip];

Lets go over addShip method step-by-step

SKSpriteNode * ship = [SKSpriteNode new];
ship = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];
[ship setScale:0.5];
ship.zRotation = - M_PI / 2;

This creates a new SKSpriteNode with Spaceship image and reduces the size of the image half. After that we rotate this ship so it points in the right direction.

ship.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ship.size]; //1
ship.physicsBody.categoryBitMask = shipCategory; //2
ship.physicsBody.dynamic = YES; //3
ship.physicsBody.contactTestBitMask = obstacleCategory; //4
ship.physicsBody.collisionBitMask = 0; //5
ship.name = @"ship"; //6
ship.position = CGPointMake(120,160); //7


[self addChild:ship]; //8

actionMoveUp = [SKAction moveByX:0 y:30 duration:.2]; //9
actionMoveDown = [SKAction moveByX:0 y:-30 duration:.2]; //10       
  1. We are creating a rectangle physics body for the ship which has same size as ship node
  2. Sets the category bit mask to be the shipCategory you defined earlier
  3. Setting dynamic YES means that physics engine will not control the movement of the ship
  4. contactTestBitMask means what categories of objects this object should notify the contact listener when they intersect
  5. On collision with missile we don’t want the ship to bounce off, so we set collisionBitMask = 0
  6. Naming ship node
  7. Setting position of ship on screen
  8. Adding ship as child node of the scene
  9. Defining SKAction for moving ship up
  10. Defining SKAction for moving ship down

Before we move further we need to replace MyScene.h with the following:

#import 

@interface MyScene : SKScene 

@end

If you run the project now, it wont show the spaceship where we want it be on the screen. To fix the issue, remove all the methods from ViewController and replace it with the following:

- (void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    SKView * skView = (SKView *)self.view;

    if (!skView.scene) {
        skView.showsFPS = YES;
        skView.showsNodeCount = YES;

        // Create and configure the scene.
        SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
        scene.scaleMode = SKSceneScaleModeAspectFill;

        // Present the scene.
        [skView presentScene:scene];
    }
}

-(BOOL)prefersStatusBarHidden{
    return YES;
}

We are now creating the scene from viewWillLayoutSubviews method instead of viewDidLoad. When viewDidLoad is called it is not aware of the layout of changes, hence it will not set the bounds of the scene correctly

image

Now we are going to add capability to move the ship on its y axis based on where you have touched on the screen. In MyScene.m add following method

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInNode:self.scene]; //1
    if(touchLocation.y >ship.position.y){ //2
        if(ship.position.y < 270){ //3             
            [ship runAction:actionMoveUp]; //4         
        }     
    }else{        
        if(ship.position.y > 50){ 
            [ship runAction:actionMoveDown]; //5
        }
    }
}
  1. Will track the location of the touch on the screen
  2. If the location is higher than the ship’s, we want to move the ship up
  3. Setting offset from the edge so the ship completely stays in bounds of the scene
  4. Calling actionMoveUp to move the ship up by 30 points
  5. Calling actionMoveDown when touch location is below ship current location

Scrolling Background

Add these static vector math methods and constants right after obstacleCategory

static const float BG_VELOCITY = 100.0; 
static inline CGPoint CGPointAdd(const CGPoint a, const CGPoint b)
{
    return CGPointMake(a.x + b.x, a.y + b.y);
}

static inline CGPoint CGPointMultiplyScalar(const CGPoint a, const CGFloat b)
{
    return CGPointMake(a.x * b, a.y * b);
}

To make an endlessly scrolling background, make two background images instead of one and lay them side-by-side. Then, as you scroll both images from right to left, once one of the images goes off-screen, you simply put it back to the right.

Add this method in MyScene.m

-(void)initalizingScrollingBackground
{
    for (int i = 0; i < 2; i++) {
        SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:@"bg"];
        bg.position = CGPointMake(i * bg.size.width, 0);
        bg.anchorPoint = CGPointZero;
        bg.name = @"bg";
        [self addChild:bg];
    }

}

Call this method right after setting background color to whiteColor.

self.backgroundColor = [SKColor whiteColor];
[self initalizingScrollingBackground];

Next we will add method in MyScene.m to move the background

- (void)moveBg
{
    [self enumerateChildNodesWithName:@"bg" usingBlock: ^(SKNode *node, BOOL *stop)
     {
         SKSpriteNode * bg = (SKSpriteNode *) node;
         CGPoint bgVelocity = CGPointMake(-BG_VELOCITY, 0);
         CGPoint amtToMove = CGPointMultiplyScalar(bgVelocity,_dt);
         bg.position = CGPointAdd(bg.position, amtToMove);

  //Checks if bg node is completely scrolled of the screen, if yes then put it at the end of the other node
         if (bg.position.x <= -bg.size.width)
         {
             bg.position = CGPointMake(bg.position.x + bg.size.width*2,
                                       bg.position.y);
         }
     }];
}

This finds any child with the bg name and moves it to the left according to the velocity. Finally, to scroll the background with every frame we are going add update method in MyScene.m

 -(void)update:(CFTimeInterval)currentTime {

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

    [self moveBg];
}

Run the project and you should have a scrolling background.

image


Get job-ready in Objective-C »


Check out more on Megha’s Blog.