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 2 of 4 of this article. Click here to view the first page.

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

Contributors

Over 300 content creators. Join our team.