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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Rotating Turrets: How To Make A Simple iPhone Game with Cocos2D 2.X Part 2
10 mins
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! :]
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!
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:
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:
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!
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.