Rotating Turrets: How To Make A Simple iPhone Game with Cocos2D 2.X Part 2

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

Contents

Hide contents

Rotating Turrets: How To Make A Simple iPhone Game with Cocos2D 2.X Part 2

10 mins

Let's Rotate This Turret!

Let's Rotate This Turret!

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!

There’s been a surprising amount of interest in the post on How To Make a Simple iPhone Game with Cocos2D 2.X – and several of you guys have asked for some more in this series!

Specifically, some of you asked for a tutorial on how to rotate a turret to face the shooting direction. This is a common requirement for a lot of games – including one of my favorite genres, tower defense!

Note: Want a tutorial specifically on how to create a Tower Defense game? We’ve got a tutorial for that! :]

So in this tutorial you’ll learn how to add a rotating turret into the simple game. Special thanks goes to Jason and Robert for suggesting this tutorial!

(And don’t forget Part 3 in this series – Harder Monsters and More Levels!)

Getting Set Up

If you have followed along with the last tutorial, you can continue with the project exactly how you left off. If not, just download the code from the last tutorial and start from there.

Next, download the new resources for this tutorial and add them to your project. Delete the old player.png, player-hd.png, projectile.png, and projectile-hd.png images from your project to be safe.

Then modify the lines of code in HelloWorldLayer.m that create each sprite to read as follows:

// In the init method
CCSprite *player = [CCSprite spriteWithFile:@"player2.png"];

// In the ccTouchesEnded method
CCSprite *projectile = [CCSprite spriteWithFile:@"projectile2.png"];

Build and run your project, and if all looks well you should see a turret shooting bullets. However, it doesn’t look right because the turret doesn’t rotate to face where it’s shooting – so let’s fix that!

A shooting turret!

Rotating To Shoot

Before you can rotate the turret, you first need to store a reference to your Player sprite so you can rotate it later on. Open up HelloWorldLayer.h and modify the class to include the following member variable:

CCSprite *_player;

Then back in HelloWorldLayer.m modify the code in the init method that adds the player object to the layer as follows:

_player = [CCSprite spriteWithFile:@"player2.png"];
_player.position = ccp(_player.contentSize.width/2, winSize.height/2);
[self addChild:_player];

Ok, now that you’ve got a reference to your player object, let’s rotate it! To rotate it, you first need to figure out the angle that you need to rotate it to.

To figure this out, think back to high school trigonometry. Remember the mnemonic SOH CAH TOA? That helps you remember that the Tangent of an angle is equal to the Opposite over the Adjacent. This picture helps explain:

Shooting Angle Math

As shown above, the angle you want to rotate is equal to the arctangent of the Y offset divided by the X offset.

However, there are two things you need to keep in mind. First, when you compute arctangent(offY / offX), the result will be in radians, while Cocos2D deals with degrees. Luckily, Cocos2D provides an easy to use conversion macro you can use.

Secondly, while you’d normally consider the angle in the picture above positive angle (of around 20°), in Cocos2D rotations are positive going clockwise (not counterclockwise), as shown in the following picture:

Cocos2D angles

So to point in the right direction, you’ll need to multiply your result by negative 1. So for exaple, if you multiplied the angle in the picture above by negative 1, you’d get -20°, which would represent a counterclockwise rotation of 20°.

Ok enough talk, let’s put it into code! Add the following code inside ccTouchesEnded, right before you call runAction on the projectile:

// Determine angle to face
float angleRadians = atanf((float)offRealY / (float)offRealX);
float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians);
float cocosAngle = -1 * angleDegrees;
_player.rotation = cocosAngle;

Compile and run the project and the turret should now turn to face the direction it’s shooting!

A rotating turret in Cocos2D!

Rotate Then Shoot

It’s pretty good so far but is a bit odd because the turret just jumps to shoot in a particular direction rather than smoothly flowing. You can fix this, but it will require a little refactoring.

First open up HelloWorldLayer.h and add the following member variables to your class:

CCSprite *_nextProjectile;

Then back in HelloWorldLayer.m modify your ccTouchesEnded so it looks like the following:

- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    
    if (_nextProjectile != nil) return;
    
    // 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];
    _nextProjectile = [[CCSprite spriteWithFile:@"projectile2.png"] retain];
    _nextProjectile.position = ccp(20, winSize.height/2);
    
    // Determine offset of location to projectile
    CGPoint offset = ccpSub(location, _nextProjectile.position);
    
    // Bail out if you are shooting down or backwards
    if (offset.x <= 0) return;
    
    // Determine where you wish to shoot the projectile to
    int realX = winSize.width + (_nextProjectile.contentSize.width/2);
    float ratio = (float) offset.y / (float) offset.x;
    int realY = (realX * ratio) + _nextProjectile.position.y;
    CGPoint realDest = ccp(realX, realY);
    
    // Determine the length of how far you're shooting
    int offRealX = realX - _nextProjectile.position.x;
    int offRealY = realY - _nextProjectile.position.y;
    float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));
    float velocity = 480/1; // 480pixels/1sec
    float realMoveDuration = length/velocity;
    
    // Determine angle to face
    float angleRadians = atanf((float)offRealY / (float)offRealX);
    float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians);
    float cocosAngle = -1 * angleDegrees;
    float rotateDegreesPerSecond = 180 / 0.5; // Would take 0.5 seconds to rotate 180 degrees, or half a circle
    float degreesDiff = _player.rotation - cocosAngle;
    float rotateDuration = fabs(degreesDiff / rotateDegreesPerSecond);
    [_player runAction:
     [CCSequence actions:
      [CCRotateTo actionWithDuration:rotateDuration angle:cocosAngle],
      [CCCallBlock actionWithBlock:^{
         // OK to add now - rotation is finished!
         [self addChild:_nextProjectile];
         [_projectiles addObject:_nextProjectile];
         
         // Release
         [_nextProjectile release];
         _nextProjectile = nil;
     }],
      nil]];
    
    // Move projectile to actual endpoint
    [_nextProjectile runAction:
     [CCSequence actions:
      [CCMoveTo actionWithDuration:realMoveDuration position:realDest],
      [CCCallBlockN actionWithBlock:^(CCNode *node) {
         [_projectiles removeObject:node];
         [node removeFromParentAndCleanup:YES];
    }],
      nil]];
    
    _nextProjectile.tag = 2;
    
    [[SimpleAudioEngine sharedEngine] playEffect:@"pew-pew-lei.caf"];
}

That looks like a lot of code, but you actually didn't change that much - most of it was just some minor refactoring. Here are the changes you made:

  • You bail at the beginning of the function if there is a value in nextProjectile, which means you're in the process of shooting.
  • Before you used a local object named projectile that you added to the scene right away. In this version you create an object in the member variable nextProjectile, but don't add it until later.
  • You define the speed at which you want your turret to rotate as half a second for half a circle's worth of rotation.
  • So to calculate how long this particular rotation should take, you divide the degrees of rotation by degrees per second.
  • Then you start up a sequence of actions where you rotate the turret to the correct angle, then call a block to add the projectile to the scene.

That's it - give it a shot! Compile and run the project, and the turret should now rotate much more smoothly.

Contributors

Over 300 content creators. Join our team.