(译)如何使用cocos2d来制作简单的iphone游戏:更猛的怪物和更多的关卡。(第三部分。完!)

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

  原文链接地址:http://www.raywenderlich.com/782/harder-monsters-and-more-levels

目前为止,我正在翻译的游戏《如何如何使用cocos2d来制作一个简单的iphone》游戏非常cool。我们有一个可以旋转的炮塔,有怪物可以射杀,还有很棒的音效。

  但是,我们的炮塔觉得这太简单了。这些怪物只要开一枪就挂了,而且现在只有一个关卡!它还没有热身呢!

  在这个教程里,我将会扩展我们的工程,并增加一些不同种类和难度的怪物,然后实现多个关卡。

更猛的怪物

  为了好玩,让我们创建两种不同类型的怪物:一种不怎么经打,但是移动速度很快,还有一种很能抗(坦克级别),但是移动速度很慢!为了使玩家可以区分这两种不同类型的怪物,下载修改的怪物图片并把它们添加到工程里。同时,下载我制作的爆炸音效,也把它们添加到工程中去。

  好了,让我们来创建Monster类。这里有许多方法来为Monster类建模,但是,我们选择最简单的方式,即把Monster类当作CCSprite的一个子类。同时,我们会创建两个Monster类的子类:一个为我们的虚弱快速怪创建,另一个为我们的强悍缓慢怪创建。

  选择File\New File,再选择Cocoa Touch Class\Objective-C class,确保Subclass of NSObject被选中。单击下一步,取名为Monster.m,然后确保“Also create Monster.h”复选上。

  接下来,把Monster.h中的代码替换成下面的:

#import "cocos2d.h"

@interface Monster : CCSprite {
int _curHp;
int _minMoveDuration;
int _maxMoveDuration;
}

@property (nonatomic, assign)
int hp;
@property (nonatomic, assign)
int minMoveDuration;
@property (nonatomic, assign)
int maxMoveDuration;

@end

@interface WeakAndFastMonster : Monster {
}
+(id)monster;
@end

@interface StrongAndSlowMonster : Monster {
}
+(id)monster;
@end

 

  这里非常直白:我们从CCSprite派生一个Monster类,然后增加了一些成员变量来记录monster的状态。然后,我们又从Monster类派生出两个不同的monster子类。

  现在,打开Monster.m并添加下面的代码:

#import "Monster.h"

@implementation Monster

@synthesize hp
= _curHp;
@synthesize minMoveDuration
= _minMoveDuration;
@synthesize maxMoveDuration
= _maxMoveDuration;

@end

@implementation WeakAndFastMonster

+ (id)monster {

WeakAndFastMonster
*monster = nil;
if ((monster = [[[super alloc] initWithFile:@"Target.png"] autorelease])) {
monster.hp
=1;
monster.minMoveDuration
=3;
monster.maxMoveDuration
=5;
}
return monster;

}

@end

@implementation StrongAndSlowMonster

+ (id)monster {

StrongAndSlowMonster
*monster = nil;
if ((monster = [[[super alloc] initWithFile:@"Target2.png"] autorelease])) {
monster.hp
=3;
monster.minMoveDuration
=6;
monster.maxMoveDuration
=12;
}
return monster;

}

@end

 

  这里代码很简单的,只有我们为每个类添加的一个静态方法,用来返回这个类的实例。然后初使化了默认的HP和移动所需要的时间。

  现在,让我们把新创建的Monster类集成到之前的代码中去!首先在HelloWorldScene.m中导入文件:

#import "Monster.h"

  然后,修改addTarget方法来构造我们新创建的类的实例,而不是直接创建精灵(sprite)。替换spriteWithFile那一行,如下所示:

//CCSprite *target = [CCSprite spriteWithFile:@"Target.png" rect:CGRectMake(0, 0, 27, 40)];
Monster *target = nil;
if ((arc4random() %2) ==0) {
target
= [WeakAndFastMonster monster];
}
else {
target
= [StrongAndSlowMonster monster];
}

 

  这里将会有50%的机率来出现不同类型的monster。当然,我们把怪物的speed定义移到了类当中,因此,我们需要修改min/max移动间隔,把它改成下面的样子:

int minDuration = target.minMoveDuration; //2.0;
int maxDuration = target.maxMoveDuration; //4.0;

  最后,在updateMethod里面做一些修改。首先,在targetsToDelete的声明之前,添加一个boolean值。

BOOL monsterHit = FALSE;

  然后,在CGRectIntesectsRect里面,不是马上把对象添加到targetsToDelete里面,而是改成下面的:

//[targetsToDelete addObject:target];
monsterHit = TRUE;
Monster
*monster = (Monster *)target;
monster.hp
--;
if (monster.hp <=0) {
[targetsToDelete addObject:target];
}
break;

  这里,我们不是马上杀死怪物,而是减少它的HP,而且只有当它的生命值小于0的时候,才kill它。注意,如果projectile击中一个怪物的话 我们就跳出循环,这意味着一个飞盘射击一次只能打一个怪物。

  最后,我们把projectilesToDelete测试改成下面所示:

if (monsterHit) {
[projectilesToDelete addObject:projectile];
[[SimpleAudioEngine sharedEngine] playEffect:
@"explosion.caf"];
}

  编译并运行代码,如果一切顺利,那么你将会看到两种不同类型的怪物在屏幕上飞过---这使得我们的炮塔的生活更加富有挑战了!

多个关卡

  为了使游戏支持多个关卡,首先我们需要重构。这个重构的工作非常简单,但是在这个项目里,有许多工作要做。如果把所有的内容都放在这个帖子上,那将会是一篇又长又乏味的帖子。

  相反,我会从一个更高的角度来谈谈我做了什么,并且提供一个功能完整的样例工程。

  抽象出一个Level类。目前,HelloWorldScene类里面把“level”的概念硬编码进去了,比如发射哪种类型的monster,发射频率如何等等。因此,我们的第一步就是要把这些信息提取出来,放到一个Level类里面。这样,在HelloWorldScene里面我们就可以为不同的关卡重用相同的逻辑。

  重用场景。目前,我们每一次转换场景(scene)的时候都是重新创建了一个新的场景类。这里有一个缺点就是效率问题。每一次在场景对象的init方法里加载资源,这会影响游戏frame。

  因为我们是一个简单的游戏,我们需要做的就是,每一个scene创建一个实例,并且提供一个reset方法来清除任何老的状态(比如上一关中的飞盘或者怪物)。

  使用应用程序委托来当做跳板。目前,我们并没有任何全局的状态,比如:我们在哪一个关卡或者当前关卡的设置是什么。每一个场景仅仅是硬编码它需要跳转的下一个场景是谁。

  我们将会修改这些内容,使用App Delegate来存储指向一些全局状态(比如关卡信息)的指针。因为,所有的场景(scene)都可以很方便地得到delegate对象。我们也会在App Delegate类里面放置一些方法,用来实现不同场景之间的切换的集中控制。并且减少场景之间的相互依赖。

  好了,上面就是我所做的主要的重构内容---更多细节可查看样例工程。记住,这只是实现功能的方式之一,如果你有其它更好的组织场景和游戏对象的方法,请在这里分享出来吧!

  不管怎么说,下载代码,运行看看吧。我们有一个非常不错的游戏了----一个旋转的炮塔,成千上万的不同类型的敌人,多个关卡,win/lose场景,当然,还有很棒的音效!

总结

  老样子,这里你可以下载到本系列教程目前为止完整的源代码

  现在,你知道如何制作一个简单的游戏了,为什么不更深入一点呢?学一学如何使用cocos2d来制作基于tile的游戏?毕竟,谁不喜欢忍者吃西瓜呢?

  我希望你能喜欢这个系列的教程,希望这些教程能对你做项目有所帮助。

ps:接下来,我们尝试翻译一些有关box2d方面的教程,敬请期待!

 

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

posted @ 2011-03-28 17:57 子龙山人 阅读(3561) 评论(5) 编辑

(译)如何使用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 @ 2011-03-28 16:53 子龙山人 阅读(5477) 评论(13) 编辑

公告

统计