在上一篇如何使用Cocos2D制作一款简单的iphone游戏里有很多令人惊奇的内容,很多读者都表现出了浓厚的兴趣,并要求在这个系列中再讲述些更酷的功能!
其中一些人特别提到想要一个教如何旋转一个炮台,并让它朝着射击的方向。这是游戏里经常用到的功能,包括我最喜欢的游戏类型,塔防!
所以本篇教学我们将学习如何在上篇的简单示例游戏中加入旋转炮台。特别感谢Jason和Robert,是他们建议我写的这篇教程!
(不要忘记本系列教学的第3部分哦 – 更难的怪物和更多的关卡!)
如果你跟着并完成了上一篇教学,那么使用你目前的工程即可。如果没有,那么可以从这个链接下载上篇教学的代码,好了,让我们开始吧 。
下一步,下载新的player sprite和projectile sprite图片,并把它们加入到你的工程,从工程中删除旧的Player.jpg和Projectile.jpg。把创建每个sprite的地方修改如下:
// In the init method CCSprite *player = [CCSprite spriteWithFile:@"Player2.jpg"]; // In the ccTouchesEnded method CCSprite *projectile = [CCSprite spriteWithFile:@"Projectile2.jpg"]; |
注意,这次我们没有特别指定sprite的高度和宽度,并让Cocos2D替我们处理之。
编译并运行工程,如果一切顺利你会看到一个不断射击的小炮台。不过,它看起来不是很好,因为他完全没有向着该冲着的方向射击-让我们来修复它!
在我们让炮台转起来之前,先存储一个主人公(现在是炮台了)sprite的引用,以便之后可以旋转它。打开HelloWorldScene.h并修改类使其包含以下成员变量:
CCSprite *_player; |
然后修改init方法里边的代码,加入player对象到层中:
_player = [[CCSprite spriteWithFile:@"Player2.jpg"] retain]; _player.position = ccp(_player.contentSize.width/2, winSize.height/2); [self addChild:_player]; |
最后一定要在我们忘记释放内存这件事之前释放内存,在dealloc中加入:
[_player release]; _player = nil; |
好的,现在我们获得了player对象的引用,旋转它的时刻到啦!为了旋转它,我们必须先计算出它需要转到的角度。
回想一下中学时学过的三角几何学吧。还记得方便记忆的SOH CAH TOA(译者注:其实就是对应的sin cos和tan)吗?那么,tangent就是对边/临边,如图:
如上所示,我们想要的角度即等于,Y offset除以X offset的结果的反正切arctangent。
然而有两件事儿我们要牢记在心,第一,当我们计算得到反正切arctangent(offY / offX),结果是弧度,但是Cocos2D使用的是角度。幸运的是,Cocos2D提供了一个方便使用的弧度转角度的宏。
第二,我们一般会认为上图中的角度是正的(大概20度左右),但在Cocos2D里边正向是顺时针的(并不是平常认为的逆时针)。如下图所示:
所以为了得到正确的方向,我们需要把结果乘上-1。比如上图中的20度,乘上-1,就得到能表示逆时针的20度角了。
好了不多说了,让我们付诸代码!在ccTouchesEnded里你调用projectile的runAction之前加入以下代码:
// 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; |
编译并运行,小炮台就可以自由地旋转着发射了!
到目前为止都很好,除了有一点有点儿奇怪的,炮台会突然转到一个方向并射击,而不是缓慢的转动过去。我们可以修复这个,但是需要稍微重构下我们的代码。
首先打开HelloWorldScene.h并在类中加入如下成员变量:
CCSprite *_nextProjectile; |
然后修改ccTouchesEnded 并加入一个叫finishShoot的方法,如下所示:
- (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; } |
代码有点儿多,不过我们实际上并没有做很大个改动,很大部分都是一些小小的重构而已。罗列一下我们所做的改动:
让我们试一试!编译并运行,现在炮台旋转的流畅的多了。
首先,项目到目前为止所有的源码在此simple Cocos2D iPhone game。
在接下来的教程中你将学到如何添加更难的怪物和更多的关卡!
你也可以时常来看看我的 Cocos2D 和Box2D 的教程!