How To Make A Simple iPhone Game with Cocos2D 2.X Tutorial

Note from Ray: You guys voted for me to update this classic beginning Cocos2D tutorial series from Cocos2D 1.X to Cocos2D 2.X in the weekly tutorial vote, so your wish is my command! :] This tutorial series is now fully up-to-date for Cocos2D 2.X, Xcode 4.5, and has a ton of improvements such as Retina […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share
Ninjas Going Pew-Pew!

Ninjas Going Pew-Pew!

Note from Ray: You guys voted for me to update this classic beginning Cocos2D tutorial series from Cocos2D 1.X to Cocos2D 2.X in the weekly tutorial vote, so your wish is my command! :]

This tutorial series is now fully up-to-date for Cocos2D 2.X, Xcode 4.5, and has a ton of improvements such as Retina display and iPhone 4″ screen support. Here’s the pre Cocos2D 1.X version if you need it!

Cocos2D is a powerful library for the iPhone that can save you a lot of time while building your iPhone game. It has sprite support, cool graphical effects, animations, physics libraries, sound engines, and a lot more.

Back when I first started learning Cocos2D, there were several useful tutorials on getting started with Cocos2D out there, but I couldn’t find anything quite like what I was looking for – making a very simple but functional game with animation, collisions, and audio without using too many advanced features.

I ended up making a simple game of my own, and thought I’d write a tutorial series based on my experience in case it might be useful to other newcomers.

This tutorial series will walk you through the process of creating a simple game for your iPhone with Cocos2D, from start to finish. You can follow along with the series, or just jump straight to the sample project at the end of the article. And yes. There will be ninjas.

(Jump to Part 2 or Part 3 of the series.)

Downloading and Installing Cocos2D

You can download Cocos2D from the official Cocos2D-iPhone home page.

You’ll notice there are several different choices you have about which version to download: Cocos2D 1.X vs Cocos2D 2.x, and stable vs stable choices. Let’s go over them.

Cocos2D 1.X vs Cocos2D 2.X

The main difference between these two versions is that under the hood, Cocos2D 1.X is made with OpenGL ES 1.X, and Cocos2D 2.X is made with OpenGL ES 2.X. Unless you have prior experience with OpenGL, this probably doesn’t mean much to you :]

All you need to know for now is the following:

  • Cocos2D 1.X has been around longer. So there’s a lot of code out there that only works with Cocos2D 1.X. This is changing though, as more and more people move to Cocos2D 2.X!
  • Cocos2D 2.X can use shaders. Shaders are a fancy thing in OpenGL ES 2.X that allows you to create some cool effects that you just can’t do with OpenGL ES 1.X.

Although both versions of Cocos2D work fine and there are tons of great games made with both versions, in this tutorial you’ll use the latest and greatest, Cocos2D 2.X. :]

Cocos2D Stable vs Unstable

You’ll also notice there’s “stable” and “unstable” versions.

I’ve noticed that generally it takes a long time for useful new features to move from “unstable” to “stable.” So I tend to go for the “unstable” versions so I have all the good new stuff. Don’t worry, even though it’s called “unstable” it’s actually pretty good usually :]

So in this tutorial, you will use the “unstable” version.

Conclusion

For this tutorial, download the latest unstable version of Cocos2D 2.X.

After you pull down the code, you’ll want to install the useful project templates. Open up a Terminal window to the directory you downloaded Cocos2D to, and enter the following command:

./install-templates.sh -f -u

You should see “Installing cocos2d templates” and a bunch of messages. Congrats – you’re now ready to work with Cocos2D!

Note: If you already have Cocos2D 1.X installed and are worried about installing Cocos2D 2.X because it might override your current Cocos2D 1.X templates, don’t panic! :] Cocos2D 2.X installs its templates in a separate folder, so you can have the templates for both versions of Cocos2D installed side by side.

Hello, Cocos2D!

Let’s start by getting a simple Hello World project up and running by using the Cocos2D template you just installed. Start up XCode and create a new Cocos2D project by selecting the iOS\cocos2d v2.X\cocos2d iOS template, and name the project Cocos2DSimpleGame.

Selecting the Cocos2D 2.X Template

Go ahead and build and run the template as-is. If all works OK, you should see the following:

The Basic Cocos2D 2.X Template

Cocos2D is organized into the concept of scenes, which are kind of like “levels” or “screens” for a game. For example you might have a scene for the initial menu for the game, another for the main action of the game, and a game over scene to end.

Inside scenes, you can have a number of layers (kind of like in Photoshop), and layers can contain nodes such as sprites, labels, menus, or more. And nodes can contain other nodes as well (i.e. a sprite could have a child sprite inside it).

If you take a look at the sample project, you’ll see there are two layers – an IntroLayer and a HelloWorldLayer, each of which has a scene that contains it.

In this tutorial, you will be working with the HelloWorldLayer. Open up HelloWorldLayer.m and look at the init method. You’ll see it’s adding a “Hello World” label and a menu right now. Next you’ll take that out, and put a sprite in instead.

Adding A Sprite

Before you can add a sprite, you’ll need some images to work with. You can use the ones my lovely wife has created for the project by downloading the resources for this tutorial.

Once you’ve downloaded the resources, unzip the file, drag everything over to the Resources folder in XCode, and make sure “Copy items into destination group’s folder (if needed)” is checked.

Now that you have your images, you have to figure out where you want to place the player. Note that in Cocos2D the bottom left corner of the screen has coordinates of (0,0) and the x and y values increase as you move to the upper right. Since this project is in landscape mode, this means that the upper right corner is (480, 320) if you’re running on a 3.5″ screen, or (568, 320) if you’re running on a 4″ screen.

Note: Those of you who have been programming on an iOS for a while might think to yourself, “Wait a minute – I thought the 4″ screen was 1136×640 pixels, not 568×320 pixels!”

You are right about that, but Cocos2D works with points, not pixels. On Retina display devices, 1 point = 2 pixels, so 1136×640 pixels = 568×320 points. This is quite handy, because if you use points in your game, your coordinates can be the same for both retina and non-retina displays!

Also note that by default when you set the position of an object, the position is relative to the center of the sprite you are adding. So if you wanted your player sprite to be aligned with the left edge of the screen horizontally, and vertically centered:

  • For the x coordinate of the position, you’d set it to [player sprite’s width]/2.
  • For the y coordinate of the position, you’d set it to [window height]/2.

Here’s a picture that helps illustrate this a bit better:

Screen and Sprite Coordinates

So let’s give it a shot! Open HelloWorldLayer.m, and replace the init method with the following:

- (id) init
{
    if ((self = [super init])) {
        CGSize winSize = [CCDirector sharedDirector].winSize;
        CCSprite *player = [CCSprite spriteWithFile:@"player.png"];
        player.position = ccp(player.contentSize.width/2, winSize.height/2);
        [self addChild:player];
    }
    return self;
}

You can build and run it, and your sprite should appear just fine, but note that the background defaults to black. For this artwork, white would look a lot better.

One easy way to set the background of a layer in Cocos2D to a custom color is to use the CCLayerColor class. So let’s give this a shot. Open HelloWorldLayer.h and change the HelloWorld interface declaration to read as follows:

@interface HelloWorldLayer : CCLayerColor

Then click on HelloWorldLayer.m and make a slight modification to the init method so you can set the background color to white:

if ((self = [super initWithColor:ccc4(255,255,255,255)])) {

Go ahead and compile and run, and you should see your sprite on top of a white background. w00t your ninja looks ready for action!

Sprite Added Screenshot

Note: You may have noticed that there are actually two versions of the player image included in the resource pack: player.png (27×40 pixels), and player-hd.png (double the size – 54×80 pixels).

This shows a really cool feature of Cocos2D – it’s smart enough to substitute the high resolution graphics when you’re running on a Retina display! Just put in some artwork that is double the size and add an -hd extension. This is similar to the @2x behavior that UIKit supports.

Moving Monsters

Next you want to add some monsters into your scene for your ninja to combat. To make things more interesting, you want the monsters to be moving – otherwise there wouldn’t be much of a challenge! So let’s create the monsters slightly off screen to the right, and set up an action for them telling them to move to the left.

Add the following method right before the init method:

- (void) addMonster {
    
    CCSprite * monster = [CCSprite spriteWithFile:@"monster.png"];
    
    // Determine where to spawn the monster along the Y axis
    CGSize winSize = [CCDirector sharedDirector].winSize;
    int minY = monster.contentSize.height / 2;
    int maxY = winSize.height - monster.contentSize.height/2;
    int rangeY = maxY - minY;
    int actualY = (arc4random() % rangeY) + minY;
    
    // Create the monster slightly off-screen along the right edge,
    // and along a random position along the Y axis as calculated above
    monster.position = ccp(winSize.width + monster.contentSize.width/2, actualY);
    [self addChild:monster];
    
    // Determine speed of the monster
    int minDuration = 2.0;
    int maxDuration = 4.0;
    int rangeDuration = maxDuration - minDuration;
    int actualDuration = (arc4random() % rangeDuration) + minDuration;
    
    // Create the actions
    CCMoveTo * actionMove = [CCMoveTo actionWithDuration:actualDuration 
      position:ccp(-monster.contentSize.width/2, actualY)];
    CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) {
        [node removeFromParentAndCleanup:YES];
    }];
    [monster runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];
    
}

I’ve spelled out things in a verbose manner here to make things as easy to understand as possible. The first part should make sense based on what we’ve discussed so far: you do some simple calculations to determine where you want to create the object, set the position of the object, and add it to the scene the same way you did for the player sprite.

The new element here is adding actions. Cocos2D provides a lot of extremely handy built-in actions you can use to animate your sprites, such as move actions, jump actions, fade actions, animation actions, and more. Here you use three actions on the monster:

  • CCMoveTo: You use the CCMoveTo action to direct the object to move off-screen to the left. Note that you can specify the duration for how long the movement should take, and here you vary the speed randomly from 2-4 seconds.
  • CCCallBlockN: The CCCallBlockN function allows us to specify a callback block to run when the action is performed. In this game, you’re going to set up this action to run after the monster goes offscreen to the left – and you’ll remove the monster from the layer when this occurs. This is important so that you don’t leak memory over time by having tons of unused sprites sitting off-screen. Note that there are other (and better) ways to address this problem such as having reusable arrays of sprites, but for this beginner tutorial you are taking the simple path.
  • CCSequence: The CCSequence action allows us to chain together a sequence of actions that are performed in order, one at a time. This way, you can have the CCMoveTo action perform first, and once it is complete perform the CCCallBlockN action.

One last thing before you go. You need to actually call the method to create monsters! And to make things fun, let’s have monsters continuously spawning over time. You can accomplish this in Cocos2D by scheduling a callback function to be periodically called. Once per second should do for this. So add the following call to your init method before you return:

[self schedule:@selector(gameLogic:) interval:1.0];

And then implement the callback function simply as follows:

-(void)gameLogic:(ccTime)dt {
    [self addMonster];
}

That’s it! Build and run the project, now you should see monsters happily moving across the screen:

Monsters Moving Across the Screen

Shooting Projectiles

At this point, the ninja is just begging for some action – so let’s add shooting! There are many ways you could implement shooting, but for this game you are going to make it so when the user taps the screen, it shoots a projectile from the player in the direction of the tap.

I want to use a CCMoveTo action to implement this to keep things at a beginner level, but in order to use this you have to do a little math. This is because the CCMoveTo requires us to give a destination for the projectile, but you can’t just use the touch point because the touch point represents just the direction to shoot relative to the player. You actually want to keep the bullet moving through the touch point until the bullet goes off-screen.

Here’s a picture that illustrates the matter:

Projectile Triangle

So as you can see, you have a small triangle created by the x and y offset from the origin point to the touch point. You just need to make a big triangle with the same ratio – and you know you want one of the endpoints to be off the screen.

Ok, so onto the code. First you have to enable touches on your layer. Add the following line to your init method:

[self setIsTouchEnabled:YES];

Since we’ve enabled touches on your layer, you will now receive callbacks on touch events. So let’s implement the ccTouchesEnded method, which is called whenever the user completes a touch, as follows:

- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    
    // Choose one of the touches to work with
    UITouch *touch = [touches anyObject];
    CGPoint location = [self convertTouchToNodeSpace:touch];
    
    // Set up initial location of projectile
    CGSize winSize = [[CCDirector sharedDirector] winSize];
    CCSprite *projectile = [CCSprite spriteWithFile:@"projectile.png"];
    projectile.position = ccp(20, winSize.height/2);
    
    // Determine offset of location to projectile
    CGPoint offset = ccpSub(location, projectile.position);
    
    // Bail out if you are shooting down or backwards
    if (offset.x <= 0) return;
    
    // Ok to add now - we've double checked position
    [self addChild:projectile];
    
    int realX = winSize.width + (projectile.contentSize.width/2);
    float ratio = (float) offset.y / (float) offset.x;
    int realY = (realX * ratio) + projectile.position.y;
    CGPoint realDest = ccp(realX, realY);
    
    // Determine the length of how far you're shooting
    int offRealX = realX - projectile.position.x;
    int offRealY = realY - projectile.position.y;
    float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));
    float velocity = 480/1; // 480pixels/1sec
    float realMoveDuration = length/velocity;
    
    // Move projectile to actual endpoint
    [projectile runAction:
     [CCSequence actions:
      [CCMoveTo actionWithDuration:realMoveDuration position:realDest],
      [CCCallBlockN actionWithBlock:^(CCNode *node) {
         [node removeFromParentAndCleanup:YES];
    }],
      nil]];
    
}

In the first portion, you choose one of the touches to work with, and use convertTouchToNodeSpace to convert the coordinates of the touch from view coordinates into coordinates within the current layer.

Next you load up the projectile sprite and set the initial position as usual. You then determine where you wish to move the projectile to, using the vector between the player and the touch as a guide, according to the algorithm described previously.

Note that the algorithm isn't ideal. You're forcing the bullet to keep moving until it reaches the offscreen X position - even if you would have gone offscreen in the Y position first! There are various ways to address this including checking for the shortest length to go offscreen, having your game logic callback check for offscreen projectiles and removing rather than using the callback method, etc. but for this beginner tutorial you'll keep it as-is.

The last thing you have to do is determine the duration for the movement. You want the bullet to be shot at a constant rate despite the direction of the shot, so again you have to do a little math. You can figure out how far you're moving by using the Pythagorean Theorem. Remember from geometry, that is the rule that says the length of the hypotenuse of a triangle is equal to the square root of the sum of the squares of the two sides.

Once you have the distance, you just divide that by the velocity in order to get the duration. This is because velocity = distance over time, or in other words time = distance over velocity.

The rest is setting the actions just like you did for the targets. Build and run, and now your ninja should be able to fire away at the oncoming hordes!

Shooting projectiles in Cocos2D

Collision Detection

So now you have shurikens flying everywhere - but what your ninja really wants to do is to lay some smack down. So let's add in some code to detect when your projectiles intersect your targets.

There are various ways to solve this with Cocos2D, including using one of the included physics libraries: Box2D or Chipmunk. However to keep things simple, you are going to implement simple collision detection yourself.

To do this, you first need to keep better track of the targets and projectiles currently in the scene. Add the following to your HelloWorldLayer class declaration:

NSMutableArray * _monsters;
NSMutableArray * _projectiles;

And initialize the arrays in your init method:

_monsters = [[NSMutableArray alloc] init];
_projectiles = [[NSMutableArray alloc] init];

And while you're thinking of it, clean up the memory in your dealloc method (at the time of writing this tutorial, ARC isn't enabled by default in the Cocos2D 2.X template):

[_monsters release];
_monsters = nil;
[_projectiles release];
_projectiles = nil;

Note: Even though ARC isn't enabled by default in the Cocos2D template, it is very easy to do so. To learn how, check out this tutorial.

Now, modify your addMonster method to add the new monster to the monsters array and set a tag for future use:

monster.tag = 1;
[_monsters addObject:monster];

And modify your ccTouchesEnded method to add the new projectile to the projectiles array and set a tag for future use:

projectile.tag = 2;
[_projectiles addObject:projectile];

Finally, modify your both of your CCCallBlockN blocks to remove the sprite from the appropriate array:

// CCCallBlockN in addMonster
[_monsters removeObject:node];

// CCCallBlockN in ccTouchesEnded
[_projectiles removeObject:node];

Build and run the project to make sure everything is still working OK. There should be no noticeable difference at this point, but now you have the bookkeeping you need to implement some collision detection.

Now add the following new method:

- (void)update:(ccTime)dt {
    
    NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
    for (CCSprite *projectile in _projectiles) {
        
        NSMutableArray *monstersToDelete = [[NSMutableArray alloc] init];
        for (CCSprite *monster in _monsters) {
            
            if (CGRectIntersectsRect(projectile.boundingBox, monster.boundingBox)) {
                [monstersToDelete addObject:monster];
            }
        }
        
        for (CCSprite *monster in monstersToDelete) {
            [_monsters removeObject:monster];
            [self removeChild:monster cleanup:YES];
        }
        
        if (monstersToDelete.count > 0) {
            [projectilesToDelete addObject:projectile];
        }
        [monstersToDelete release];
    }
    
    for (CCSprite *projectile in projectilesToDelete) {
        [_projectiles removeObject:projectile];
        [self removeChild:projectile cleanup:YES];
    }
    [projectilesToDelete release];
}

The above should be pretty clear. You just iterate through your projectiles and monsters, creating rectangles corresponding to their bounding boxes, and use CGRectIntersectsRect to check for intersections. If any are found, you remove them from the scene and from the arrays.

Note that you have to add the objects to a "toDelete" array because you can't remove an object from an array while you are iterating through it. Again, there are more optimal ways to implement this kind of thing, but you are going for the simple approach.

You just need one more thing before you're ready to roll - schedule this method to run as often as possible by adding the following line to your init method:

[self schedule:@selector(update:)];

Give it a build and run, and now when your projectiles intersect targets they should disappear!

Finishing Touches

You're pretty close to having a workable (but extremely simple) game now. You just need to add some sound effects and music (since what kind of game doesn't have sound!) and some simple game logic.

If you've read our blog series on audio programming for the iPhone, you'll be extremely pleased to hear how simple the Cocos2D developers have made it to play basic sound effects in your game.

You already have some cool background music I made and an awesoem pew-pew sound effect in your project, from the resources for this tutorial you downloaded earlier. You just need to play them!

To do this, add the following import to the top of HelloWorldLayer.m:

#import "SimpleAudioEngine.h"

In your init method, start up the background music as follows:

[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"background-music-aac.caf"];

And in your ccTouchesEnded method play the sound effect as follows:

[[SimpleAudioEngine sharedEngine] playEffect:@"pew-pew-lei.caf"];

Now, let's create a new scene and layer that will serve as your "You Win" or "You Lose" indicator. Create a new file with the iOS\cocos2d v2.x\CCNode class template, make it a subclass of CCLayerColor, and click Next. Name it GameOverLayer, and click Create.

Then replace GameOverLayer.h with the following code:

#import "cocos2d.h"

@interface GameOverLayer : CCLayerColor

+(CCScene *) sceneWithWon:(BOOL)won;
- (id)initWithWon:(BOOL)won;

@end

Then replace GameOverLayer.m with the following code:

#import "GameOverLayer.h"
#import "HelloWorldLayer.h"

@implementation GameOverLayer

+(CCScene *) sceneWithWon:(BOOL)won {
    CCScene *scene = [CCScene node];
    GameOverLayer *layer = [[[GameOverLayer alloc] initWithWon:won] autorelease];
    [scene addChild: layer];
    return scene;
}

- (id)initWithWon:(BOOL)won {
    if ((self = [super initWithColor:ccc4(255, 255, 255, 255)])) {
        
        NSString * message;
        if (won) {
            message = @"You Won!";
        } else {
            message = @"You Lose :[";
        }

        CGSize winSize = [[CCDirector sharedDirector] winSize];
        CCLabelTTF * label = [CCLabelTTF labelWithString:message fontName:@"Arial" fontSize:32];
        label.color = ccc3(0,0,0);
        label.position = ccp(winSize.width/2, winSize.height/2);
        [self addChild:label];
        
        [self runAction:
         [CCSequence actions:
          [CCDelayTime actionWithDuration:3],
          [CCCallBlockN actionWithBlock:^(CCNode *node) {
             [[CCDirector sharedDirector] replaceScene:[HelloWorldLayer scene]];
        }],
          nil]];
    }
    return self;
}

@end

Note that there are two different objects here: a scene and a layer. The scene can contain any number of layers, however in this example it just has one. The layer just puts a label in the middle of the screen, and schedules a transition to occur 3 seconds in the future back to the Hello World scene.

Finally, let's add some extremely basic game logic. First, let's keep track of the projectiles the player has destroyed. Add a member variable to your HelloWorldLayer class in HelloWorldLayer.h as follows:

int _monstersDestroyed;

Inside HelloWorldLayer.m, add an import for the GameOverScene class:

#import "GameOverLayer.h"

Increment the count and check for the win condition in your update method inside the monstersToDelete loop right after removeChild:monster:

_monstersDestroyed++;
if (_monstersDestroyed > 30) {
    CCScene *gameOverScene = [GameOverLayer sceneWithWon:YES];
    [[CCDirector sharedDirector] replaceScene:gameOverScene];
}

And finally let's make it so that if even one target gets by, you lose. In addMonster:'s CCCallBlockN callback, add the following right after removeFromParentAndCleanup:

CCScene *gameOverScene = [GameOverLayer sceneWithWon:NO];
[[CCDirector sharedDirector] replaceScene:gameOverScene];

Go ahead and give it a build and run, and you should now have win and lose conditions and see a game over scene when appropriate!

Where To Go From Here?

And that's a wrap! Here's the full code for the simple Cocos2D iPhone game that you developed thus far.

This project could be a nice basis for playing around some more with Cocos2D by adding some new features into the project. Maybe try adding in a bar chart to show how many more targets you have to destroy before you win (check out the drawPrimitivesTest sample project for examples of how to do that). Maybe add cooler death animations for when the monsters are destroyed (see ActionsTest, EffectsTest, and EffectsAdvancedTest projects for that). Maybe add more sounds, artwork, or gameplay logic just for fun. The sky's the limit!

If you want to keep going with this tutorial series, check out part two, How To Add A Rotating Turret, or part three, Harder Monsters and More Levels!

Also, if you'd like to keep learning more about Cocos2D, we have ton of Cocos2D tutorials on this site - this is the right place to be! :]

If you have any questions or comments about this tutorial, please join the discussion below!

Contributors

Over 300 content creators. Join our team.