Simple Sprite Kit game tutorial- Part1

In this tutorial we are gonna make  a basics 2D games with Sprite Kit. You can get the full source code of this tutorial on github.

iOS Simulator Screen shot Oct 28, 2013 10.51.25 PM

Getting Started

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

spritekit_project copy

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

create-project copy

On Device Orientation deselect Portrait.
only-landscape copy
In XCode’s toolbar, select the iPhone (4-inch) Simulator and click Run
run
You will see a single label that says “Hello World!” and when you click anywhere on the screen, a rotating space ship will appear.
hello copy spaceship copy

SpriteKit Physics and Collision Detection

SpriteKit comes with Physics engine built-in. Which helps in simulating realistic movements and collision detection.

We are going to use SpriteKit physics engine to move our spaceship and detect the collision between spaceship and missile.

We are build our game in following steps

  1. Adding Main character of the game(spaceship) on the screen (Part1)
  2. Adding scrolling background (Part1)
  3. Adding Missile with which spaceship can collide with (Part2)
  4.  Show end of game screen when spaceship collides with missile and give replay option (Part2)
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 by.


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

-(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,actionMoveDown,_lastUpdateTime,_dt and _lastMissileAdded we will using later on in code.

initWithSize: is the method that gets called when scene is first created.

        self.backgroundColor = [SKColor whiteColor];        

We set the background color to white.

        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 spaceship on the screen. Add this method after initWithSize method
 
-(void)addShip
{
        //initalizing spaceship node
        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 white
        self.backgroundColor = [SKColor whiteColor];  
        [self addShip];
Lets go over addShip method step-by-step
        ship = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];
        [ship setScale:0.5];
        ship.zRotation = - M_PI / 2;
This creates a new SpriteKit node 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 as 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 by following

#import <SpriteKit/SpriteKit.h>
@interface MyScene : SKScene <SKPhysicsContactDelegate>

@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 by 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
iOS Simulator Screen shot Oct 28, 2013 8.02.43 PM
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. When track the location of the touch on the screen
  2. If the location is higher than the ship’s location then we want to move the ship up.
  3. Setting offset from the edge so 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; //Velocity with which our background is going to move

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 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 white.
        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.
iOS Simulator Screen shot Oct 28, 2013 10.51.25 PM
Advertisements

33 thoughts on “Simple Sprite Kit game tutorial- Part1

  1. hey Megha Gulati this tutorial is very good
    but i wanna to store my game score in database(on device ) and i am using sprite kit,so, pls give me suggestion, tutiorial or any demo about this

  2. Everything worked UNTIL _dt and _lastUpdateTime were introduced in the code. The compiler gives me an Syntax error saying:
    Use of undeclared identifier ‘_dt’
    Use of undeclared identifier ‘_lastUpdateTime’

    Can you give me a hint how to correct this? Thank you in advance

    • Make sure you have following variable defined at top of MyScene.m file with other variables.

      NSTimeInterval _lastUpdateTime;
      NSTimeInterval _dt;
      NSTimeInterval _lastMissileAdded;

      Let me know if this helped.

  3. It helped! Thank you! BUT there is another change I had to make to the code in order to get the full functionality. That is:
    Inside of the addship function remove SKSpriteNode *.

    original version:
    -(void)addShip
    {
    //initalizing spaceship node
    SKSpriteNode * ship = [SKSpriteNode new];
    …..

    modified version:

    -(void)addShip
    {
    //initalizing spaceship node
    ship = [SKSpriteNode new];
    ……

  4. I think you are missing a step above to modify MyScene.h to become a physics delegate. I was getting a the following warning in Xcode 5:

    ../Sprite Kit Tutorial/MyScene.m:26:39: Assigning to ‘id’ from incompatible type ‘MyScene *__strong’

    You should add that the following line in MyScene.h should be changed from this:

    @interface MyScene : SKScene

    to this:

    @interface MyScene : SKScene

    Great tutorial!

  5. Pingback: Simple Parse Tutorial with Sprite Kit game | Megha Gulati
  6. Hey great tutorial!
    I have a question, what if i want to make the game in portrait mode? what changes do i have to make to the code?
    Thanks a lot!

    • I managed to make the plane fly on the X-coordinates, but i cant see anything that will make the missiles fly from the y-coordinates (the missiles fly towards the plane). Please help 🙂 once again great tutorial! 😀

      • That was the first thing i did 🙂
        This (code bellow) is the only code i found which could change the direction they are coming from, but I don’t know what to change. Sorry if im being annoying but i need your help 🙂

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

      • Sorry, I am unable to understand your issue clearly. After following Part-2 of tutorial are you unable to make the missiles fly towards the plane? or You are trying to make this game in Portrait mode and are unable to make missiles to work in that mode?

        Please let me know what is exact issue, and I will try to help you out 🙂

        Thanks!

      • This is the situation: I want to make this game in portrait mode completely, that means that i want to rotate everything so the missiles come from the top of the screen and towards the home button. (hope i made it clear enough :D)

      • To do that there are few things which you need to change.
        1) Enable gravity so missiles fall down from where ever they are generated.
        2) Disable gravity for the ship, so the ship doesn’t fall of the screen
        3) Remove the code which moves the missiles ‘[self moveObstacle]’
        4) Probably disable the scrolling background

      • I managed to fix almost everything 😀 thank you, there is only one issue left, the rockets are “spawning” in the middle of the screen instead of coming from the top :/ any clue? If not i would like to thank you anyway for your awesome help 🙂

      • It is because of this code below.

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

        In this we are generating missile at random y axis. I am guessing you want to generate missile at random x axis starting at top of screen. So try replacing the above code with

        int r = arc4random() % 140;
        missile.position = CGPointMake(r,self.frame.size.height+ 20);
        [self addChild:missile];

        Let me know if this worked or helped in some way! 🙂

    • Hi Megha.

      I did a Copy and Paste from your file and found that my game didn’t move at all but yours did. Any advice?

  7. Pingback: Simple Sprite Kit game tutorial- Part1 | Megha Gulati - appgong
  8. Hi,Megha:

    Where is your part 2? are you going to use NSNotification in SKScene in your part2. Do you have any advice about adding NSNotification observer in SKScene ,I have the following code in my game, but the notification doesn’t work, can you help me. Thank you!

    -(id)initWithSize:(CGSize)size {
    if (self = [super initWithSize:size]) {
    /* Setup your scene here */

    self.backgroundColor = [SKColor colorWithRed:1 green:1 blue:1 alpha:1.0];
    background= [SKSpriteNode spriteNodeWithImageNamed:@”background2.jpg”];

    background.position = CGPointMake(self.size.width/2, self.size.height/2);

    [self addChild:background];

    sprite = [SKSpriteNode spriteNodeWithImageNamed:@”icon2.png”];

    sprite.position = CGPointMake(200, 500);

    [self addChild:sprite];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ok:) name:IAPHelperProductPurchasedNotification object:nil];

    }

    return self;

    }

  9. When the background scrolls, I am getting a white blank space before it comes in again. Is there anyway I can fix this?

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