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
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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!

Contributors

Over 300 content creators. Join our team.