(译)如何使用cocos2d开发一个简单的iphone游戏:旋转炮塔。(第二部分)

 免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!

原文链接地址:http://www.raywenderlich.com/692/rotating-turrets

 《怎样使用cocos2d来开发一个简单的iphone游戏》这个帖子太火了,你们当中的许多人都想要一些后续的教程!特别是,有些人问到我如何旋转炮塔来改变射击的方向。许多游戏都有这个功能,包括我最喜欢的一款游戏----塔防!

 因此,在这个教程中,我将会详细地讲解如何实现这个功能,即如何把旋转炮塔的功能添加到一个游戏当中去。在这里,特别要感谢Jason和Robert,是他们建议我来写这篇教程。

准备工作

  如果你看完并实践了上一个教程,你可以继续使用那个工程。如果没有的话,那么下载这个链接的代码吧。

  接下来,下载新的 player sprite 和 projectile sprite图片,然后把它们加到工程里面,在这之前,先从工程里删除旧的Player.png和Projectile.png图片。然后,修改代码,把每个sprite添加进去。如下所示:

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

  注意,这一次我们并没有指定精灵的宽度和高度,而是让cocos2d替我们来处理这些事情。

  编译并运行你的工程,如果一切顺利的话,你将会看到一个炮塔正在发射子弹。然后,这并不是很好,因为炮塔在射击的时候并没有面朝那个方向。因此,接下来让我们来解决这个问题。

旋转并射击

  在我们旋转炮塔之前,首先,我们需要保存Player精灵的引用,以便后面旋转它的时候使用。打开HelloWorldScene.h,然后修改类文件并包含以下成员变量:

CCSprite *_player;

 

然后修改init方法中的代码,把Player对象加入到层(layer)中。代码如下:

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

  最后,我们在dealloc函数里面添加一些清除代码。(这是个好习惯,初使化后就做相应的清理操作,防止忘记。)

[_player release];
_player
= nil;

  好了,现在让我们取出player对象的引用并且旋转它吧!为了旋转它,我们首先需要计算出旋转的角度。为了解决这个问题,想想我们在高中时候学过的三角代数吧。还记得sin cos tan吗?为了便于理解,下面使用一张图来解释一下:tan = 对面/邻边。

  如上所示,我们想要旋转的角度是arctangent(angle),即对offY/offX求arctangent运算。

  然而,这里还有两件事情,我们需要放在心上。首先,当我们计算actangent(offY/offX)的时候,这个结果是弧度,但是cocos2d使用的却是角度。还好,cocosd2d提供了一个非常方便的宏,可以使得角度和弧度之间方便转化。

  第二点,我们假定上面的图中angle的偏转是正20度,但是,cocos2d里面顺时针方向为正(而不是上图所示的逆时针为正)。让我们看到下面这个图:

  因此,为了得到正确的方向,我们把运算结果乘以一个-1就可以了。比如,如果我们把上面那幅图片里的角度乘以-1的话,我们就得够得到-20度,这个角度其实就是逆时针方向的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.png"] 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;

}

 

  这看上去好像有许多代码,但是,实际上我们改动的并不多--大部分只是做一些小小的重构。下面是我们所修改的内容的一个列表:

  1.在函数开头检查nextProjectile的值是否为nil。这意味着我们当前的touch事件正发生在射击过程之中。也就是说,炮塔已经发射出一个子弹了。

  2.之前,我们使用一个projectile的局部变量,并把它加入到了当前的场景中。在这个版本中,我们增加了一个nextProjectile的成员变量,但是并没有马上加到当前场景中。因为后要还要使用。

  3.定义炮塔旋转的角度,半秒钟旋转半个圆。记住,一个圆有2 PI个弧度。

  4.计算旋转特定的角度需要多长时间,这里是拿弧度乘以速度。

  5.接下来,我们使用一个sequence action来旋转我们的炮塔。最后,调用一个函数,把projectile加入到当前场景当中去。

  好,大功告成!编译并运行工程,现在炮塔可以旋转,并且很流畅地射击了!

接下来做什么?

  首先,这里有《怎样使用cocos2d来开发简单的iphone游戏》目前为止的完整代码。

  接下来,在这个系列的教程中,我们教大家如何添加更猛的怪物和更多的关卡

  或者期待我接下来翻译的cocos2d和box2d方面的教程吧!

 

著作权声明:本文由http://www.cnblogs.com/andyque翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

posted on 2011-03-28 16:53 子龙山人 阅读(4044) 评论(10) 编辑 收藏

评论

#1楼 2011-07-29 13:49 木由心生      

这个旋转炮塔,好像存在一个这样的问题,不知道是我自己哪里弄错了还是游戏本身存在?呵呵, 在旋转的时候触屏的位置如果触发if (offX <= 0) return;
之后的触屏好像就没有反应了˜˜˜˜
 回复 引用 查看   

#2楼 2011-07-29 13:51 木由心生      

主要是旋转之后再射击这块代码出现的, 这之前的没有问题```  回复 引用 查看   

#3楼[楼主] 2011-07-29 14:17 子龙山人      

@木由心生
作者写作教程的时候,就是这样设置的,当offx<0时没有反应。因为我们的忍者在最左边,所以才这样判断的。如果忍者在中间,那么就应该实现360度射击了。
 回复 引用 查看   

#4楼 2011-08-12 00:19 忘我和尚      

这段代码有两个小bug

第一个是:
// Bail out if we are shooting down or backwards
if (offX <= 0) return;
如果不小心点到x <= 20的地方,_player的action 不会被添加,所以
_nextProjectile 不会被清空,程序就陷入死锁了,再点屏幕也没有反应,只能等待失败。

修正方法:
location = [[CCDirector sharedDirector] convertToGL:location];
后面加多
if(location.x <= 20) return;

第二个是:
float rotateSpeed = 0.5 / M_PI; // Would take 0.5 seconds to rotate 0.5 radians, or half a circle

前面算得时角度,计算速度的时候用了弧度。所以炮塔反应好慢,这个速度相当于是0.5秒旋转3.14159度,正确的应该是
float rotateSpeed = 0.5 / 180;

不过这个不能算是bug啦

新手刚开始学习,看了楼主的代码,可能说的不对,欢迎拍砖拍回来
 回复 引用 查看   

#5楼[楼主] 2011-08-12 10:24 子龙山人      

@忘我和尚
你提的第一个bug是正确的,应该那样改。
第二个 应该不算是bug,rotateSpeed是相对于弧度来的 所以是 0.5 / M_PI,因为duration计算的时候是用atan × rotateSpeed,而反三角函数是弧度值。
 回复 引用 查看   

#6楼 2011-09-01 17:59 暗号      

博主好人呐  回复 引用 查看   

#7楼 2011-11-21 21:07 L2on      

我是用cocos2d-x写的, 但是结果出来以后炮塔不会转, 子弹也不出了, 对了很多遍都没什么问题阿..  回复 引用 查看   

#8楼[楼主] 2011-11-22 08:39 子龙山人      

@L2on
可能某些地方写得不对吧,我现在在赶一个项目,等项目弄完,我准备把所有的教程都移植到cocos2d-x上面。
 回复 引用 查看   

#9楼 2011-11-22 16:15 L2on      

@子龙山人
啊, 谢谢阿, 我再多看几遍以后发现_nextProjectile还没初始化..
Objc那边能默认初始化的阿?
 回复 引用 查看   

#10楼 2011-12-09 10:47 bjyitu      

难得的中文资料,原作者对于ccTouchesEnded的代码作了更新,比现在的写法更合理了,搬运如下.本篇教程的代码下载


- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    
    if (_nextProjectile != nil) return;
    
    //
    // Easier method using Cocos2D helper functions, suggested by 
    // Caleb Wren - see comments for post
    //
    
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:[touch view]];
    location = [[CCDirector sharedDirector] convertToGL:location];
    
    // Play a sound!
    [[SimpleAudioEngine sharedEngine] playEffect:@"pew-pew-lei.caf"];
    
    // Set up initial location of projectile
    CGSize winSize = [[CCDirector sharedDirector] winSize];
    _nextProjectile = [[CCSprite spriteWithFile:@"Projectile2.png"] retain];
    _nextProjectile.position = _player.position;
    
    // Rotate player to face shooting direction
    CGPoint shootVector = ccpSub(location, _nextProjectile.position);
    CGFloat shootAngle = ccpToAngle(shootVector);
    CGFloat cocosAngle = CC_RADIANS_TO_DEGREES(-1 * shootAngle);
    
    CGFloat curAngle = _player.rotation;
    CGFloat rotateDiff = cocosAngle - curAngle;    
    if (rotateDiff > 180)
		rotateDiff -= 360;
	if (rotateDiff < -180)
		rotateDiff += 360;    
    // Old way
    //CGFloat rotateSpeed = 0.5 / 180; // Would take 0.5 seconds to rotate half a circle
    //CGFloat rotateDuration = fabs(rotateDiff * rotateSpeed);
    // More clear way
    CGFloat rotateSpeed = 360; // degrees per second
    CGFloat rotateDuration = fabs(rotateDiff / rotateSpeed);
    
    [_player runAction:[CCSequence actions:
                        [CCRotateTo actionWithDuration:rotateDuration angle:cocosAngle],
                        [CCCallFunc actionWithTarget:self selector:@selector(finishShoot)],
                        nil]];
    
    // Move projectile offscreen
    ccTime delta = 1.0;
    CGPoint normalizedShootVector = ccpNormalize(shootVector);
    CGPoint overshotVector = ccpMult(normalizedShootVector, 420);
    CGPoint offscreenPoint = ccpAdd(_nextProjectile.position, overshotVector);
    
    [_nextProjectile runAction:[CCSequence actions:
                                [CCMoveTo actionWithDuration:delta position:offscreenPoint],
                                [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)],
                                nil]];
    
    // Add to projectiles array
    _nextProjectile.tag = 2;
}
 回复 引用 查看   

<2012年2月>
2930311234
567891011
12131415161718
19202122232425
26272829123
45678910

公告

总访问量: blogger graphics

交流论坛

传送门:泰然论坛

交流QQ群

传送门:交流QQ群  

本博客翻译文章贡献人列表:

小狼、iven、北方、skingTree,Benna糖炒小虾、u0u0、无敌葫芦娃、蓝羽

昵称:子龙山人
园龄:11个月
粉丝:195
关注:48

统计

  • 随笔 - 65
  • 文章 - 1
  • 评论 - 640

搜索

 
 

常用链接

我的标签

随笔分类(70)

随笔档案(65)

文章分类

Indie Blogs

iphone游戏开发

积分与排名

最新评论

阅读排行榜

评论排行榜

推荐排行榜