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

Ray Wenderlich

This post is also available in: Japanese

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.

What’s Next?

First off, here’s the full code for the simple Cocos2D iPhone game that we’ve made so far.

Next up in the series is a tutorial on how to add harder monsters and more levels!

Or you could always check out our many other Cocos2D tutorials !


This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer.

Ray Wenderlich

Ray is an indie software developer currently focusing on iPhone and iPad development, and the administrator of this site. He’s the founder of a small iPhone development studio called Razeware, and is passionate both about making apps and teaching others the techniques to make them.

When Ray’s not programming, he’s probably playing video games, role playing games, or board games.

User Comments

7 Comments

  • Hi

    I am confused at how the CCSequence for _nextProjectile will not execute until the CCSequence for _player has finished.
    Could you clarify?

    Thanks
    tic
  • Thanks for this tutorial.

    There is a couple of bugs that I found.

    1. If you shoot a projectile with the offset.x <= 0 the next touches do not produce any projectiles since _nextProjectile is not nil. So the if statement should be:

    Code: Select all

        // Bail out if you are shooting down or backwards
        if (offset.x <= 0)
        {
            [_nextProjectile release];
            _nextProjectile = nil;
           
            return;
        }


    2. The pew pew sound is produced when the projectile is defined not when it is added to the layer. If you shoot a shot in the vertically up then another vertically down you hear the sound before the projectile is fired from the canon. A better placement would be when adding the projectile to the layer:
    Code: Select all

             [self addChild:_nextProjectile];
             [_projectiles addObject:_nextProjectile];
             [[SimpleAudioEngine sharedEngine] playEffect:@"pew-pew-lei.caf"];


    Best regards,

    Hussam Kazah
    urth
  • Hai,

    Actually this tutorial going for landscape mode for rotating firing Turrets , Once i have designed for portrait mode for iphone then how can i run the touch handles please clarify me once. Send sample code if u have ...

    Thanks
    akram
  • I am trying to modify the code so that I can position a rotating turret at the bottom center of the screen. The positioning went fine for the sprite and the starting position of the bullets, but I am having trouble firing to the left side of the screen. I commented out the section of code for bailing out if shooting down or backwards, unfortunately the turret fires down and to the right when I attempt to shoot on the left side of the screen. I understand this involves the positional coordinates, but I don't know how to fix the problem. If I find a solution before I can get any assistance, then I will post it here.

    Code: Select all

    - (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(winSize.width/2, 20);
       
        // 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"];
    }
    Erick Beard
  • @Erick, the reason is because of how the arctangent function is defined. To be mathy about it, the range of arctangent is (-pi/2, pi/2).

    To be less mathy about it, this means that atanf will give you something to the top-right of the turret (Quadrant I), something to the bottom-right of the turret (Quadrant IV), or something directly to the right of the turret, which is not in any quadrant. These are the only three possibilities. But what you want is something to the top-left of the turret (Quadrant II). The way to achieve this is by putting the following code right after the definition of angleRadians:

    Code: Select all

    if (angleRadians < 0)
        angleRadians = M_PI + angleRadians;


    This adds the value of pi (3.14159...) to angleRadians, thus rotating the turret in the proper direction.

    But we still need to take care of the bullets. Replace the 4 lines of code under the comment "Determine where you wish to shoot the projectile to" with the following lines:

    Code: Select all

    int realX, realY;
    if (offset.x == 0) {  // tapped directly above the turret
        realX = location.x;
        realY = winSize.height + _nextProjectile.contentSize.height/2;
    }
    else if (offset.x >= 0) {  // tapped right of the turret
        float ratio = ((float) offset.y) / ((float) offset.x);
        realX = winSize.width + _nextProjectile.contentSize.width/2;
        realY = realX * ratio + _nextProjectile.position.y;
    }
    else {  // tapped left of the turret
        float ratio = - ((float) location.y) / ((float) location.x);
        realX = -_nextProjectile.contentSize.width/2;
        realY = location.y + ratio * (realX - location.x);
    }
    CGPoint realDest = ccp(realX, realY);


    Note that the last line remains unchanged. I know that could be a little more slick, but I set it up like that to clearly and completely separate the cases.
    xenogear
  • I am just wondering how does the _nextprojectile sprite run its action after it has been released from memory in the whole [_player runAction:... ]method where we release the _nextProjectile with the line [_nextProjectile release] and set it to nil?

    Doesn't this mean the projectile no longer exists after we shoot it? So how can it run the [_nextproject runaction...] method? Not sure if I am making sense. If anyone could answer this , much would be appreciated.
    jookjah
  • Hi so does this turret going to do a 360 degree rotation then? i havent finished this tutorial yet.
    rayjan

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in May: Procedural Level Generation in Games with Kim Pedersen.

Sign Up - May

Coming up in June: WWDC Keynote - Podcasters React! with the podcasting team.

Sign Up - June

Vote For Our Next Book!

Help us choose the topic for our next book we write! (Choose up to three topics.)

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

  • Jean-Pierre Distler
  • Felipe Laso Marsetti

... 55 total!

Editorial Team

... 21 total!

Code Team

  • Orta Therox

... 1 total!

Translation Team

  • Sonic Zhao
  • Team Tyran

... 38 total!

Subject Matter Experts

  • Richard Casey

... 4 total!