raywenderlich-Rotating Turrets: How To Make A Simple iPhone Game with Cocos2D Part 2

 

 
 
 
 
Let's Rotate This Turret!

Let's Rotate This Turret!

There’s been a surprising amount of interest in the post on How To Make a Simple iPhone Game with Cocos2D – 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!

So in this tutorial we’ll cover how do exactly that, and add a rotating turret into the simple game. Special thanks goes to Jason and Robert for suggesting this tutorial!

Getting Set Up

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

Next, download the new player sprite and projectile sprite images, add them into your project, and delete the old Player.jpg and Projectile.jpg from your project. Then modify the lines of code that create each sprite to read as follows:

// In the init method
CCSprite *player = [CCSprite spriteWithFile:@"Player2.jpg"];
// In the ccTouchesEnded method
CCSprite *projectile = [CCSprite spriteWithFile:@"Projectile2.jpg"];

Note that this time, we don’t bother specifying the width and height of our sprites and let Cocos2D handle it for us instead.

Compile 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!

Screenshot of new Turret Sprite

Rotating To Shoot

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

CCSprite *_player;

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

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

And finally let’s add the cleanup code in dealloc before we forget:

[_player release];
_player = nil;

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

To figure this out, think back to high school trigonometry. Remember the mnemonic SOH CAH TOA? That helps us 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 we want to rotate is equal to the arctangent of the Y offset divided by the X offset.

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

Secondly, while we’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, we’ll need to multiply our result by negative 1. So for exaple, if we multiplied the angle in the picture above by negative 1, we’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!

Rotating Turret Screenshot

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. We can fix this, but it will require a little refactoring.

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

CCSprite *_nextProjectile;

Then modify your ccTouchesEnded and add a new method named finishShoot 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 = [touch locationInView:[touch view]];
    location = [[CCDirector sharedDirector] convertToGL:location];
 
    // Set up initial location of projectile
    CGSize winSize = [[CCDirector sharedDirector] winSize];
    _nextProjectile = [[CCSprite spriteWithFile:@"Projectile2.jpg"] retain];
    _nextProjectile.position = ccp(20, winSize.height/2);
 
    // Determine offset of location to projectile
    int offX = location.x - _nextProjectile.position.x;
    int offY = location.y - _nextProjectile.position.y;
 
    // Bail out if we are shooting down or backwards
    if (offX <= 0) return;
 
    // Play a sound!
    [[SimpleAudioEngine sharedEngine] playEffect:@"pew-pew-lei.caf"];
 
    // Determine where we wish to shoot the projectile to
    int realX = winSize.width + (_nextProjectile.contentSize.width/2);
    float ratio = (float) offY / (float) offX;
    int realY = (realX * ratio) + _nextProjectile.position.y;
    CGPoint realDest = ccp(realX, realY);
 
    // Determine the length of how far we'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 rotateSpeed = 0.5 / M_PI; // Would take 0.5 seconds to rotate 0.5 radians, or half a circle
    float rotateDuration = fabs(angleRadians * rotateSpeed);    
    [_player runAction:[CCSequence actions:
      [CCRotateTo actionWithDuration:rotateDuration angle:cocosAngle],
      [CCCallFunc actionWithTarget:self selector:@selector(finishShoot)],
      nil]];
 
    // Move projectile to actual endpoint
    [_nextProjectile runAction:[CCSequence actions:
      [CCMoveTo actionWithDuration:realMoveDuration position:realDest],
      [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)],
      nil]];
 
    // Add to projectiles array
    _nextProjectile.tag = 2;
 
}
 
- (void)finishShoot {
 
    // Ok to add now - we've finished rotation!
    [self addChild:_nextProjectile];
    [_projectiles addObject:_nextProjectile];
 
    // Release
    [_nextProjectile release];
    _nextProjectile = nil;
 
}

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

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

So let’s 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 my other Cocos2D and Box2D tutorials!

 

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值