如何使用cocos2d来做一个简单的iphone游戏教程[2]

分类栏目:cocos教程

174

发射飞盘

在这里,我们的忍者需要有一些行动了–因此让我们增加一些射击吧!这里有许许多多实现射击的方式,但是在这个游戏里面,我们想让用户触摸一下屏幕,然后飞盘就会从player开始,沿着你触摸的位置发射出来。

我们使用CCMoveTo action来实现这个功能。但是,为了使用这个功能,我们必须首先来做一些数学题。这是因为,CCMoveTo需要我们为飞盘指定目的地。但是我们又不能使用触摸点,因为触摸点仅仅代表飞盘飞的方向。我们实际上想让子弹超过触摸点,然后飞出屏幕之外去。

下面这张图解释了这个问题:

如何使用cocos2d来做一个简单的iphone游戏教程[2]

因此,就像你看到的,在触摸点和player之间有一个小的三角形,由origin点,offx和offy组成。我们只需要画一个更大的三角形,同时使用一样的比率就行了。然后我们就可以根据比例算出飞盘飞出屏幕的位置。

好了,让我们看看代码怎么写。首先我们需要让layer能接收touch事件。在你的init方法面添加下面一行代码:

self.isTouchEnabled = YES;

由于我们激活了layer的touch,因此我们能够接收到touch事件的回调。这里,我们实现ccTouchesEnded方法,这是在用户完成一次touch之后调用的,代码如下:

- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {// 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];
CCSprite *projectile = [CCSprite spriteWithFile:@"Projectile.png"
rect:CGRectMake(0, 0, 20, 20)];
projectile.position = ccp(20, winSize.height/2);// Determine offset of location to projectile
int offX = location.x - projectile.position.x;
int offY = location.y - projectile.position.y;// Bail out if we are shooting down or backwards
if (offX <= 0) return;// Ok to add now – we’ve double checked position
[self addChild:projectile];

 

// Determine where we wish to shoot the projectile to
int realX = winSize.width + (projectile.contentSize.width/2);
float ratio = (float) offY / (float) offX;
int realY = (realX * ratio) + projectile.position.y;
CGPoint realDest = ccp(realX, realY);

// Determine the length of how far we’re shooting
int offRealX = realX - projectile.position.x;
int offRealY = realY - projectile.position.y;
float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));
float velocity = 480/1; // 480pixels/1sec
float realMoveDuration = length/velocity;

// Move projectile to actual endpoint
[projectile runAction:[CCSequence actions:
[CCMoveTo actionWithDuration:realMoveDuration position:realDest],
[CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)],
nil]];

}

在第一部分,我们选择一个touch来处理,获得它在当前view中的位置,然后调用convertToGL把坐标转换成我们当前层的坐标系中去。这个非常重要,因为我们使用的是landscape模式。

接下来,我们加载飞盘精灵并且设置它的初始位置。然后,我们计算出它需要飞往何处,使用player和touch之间的向量并且根据前面描述的算法计算出来。

注意,这个算法并不完美。我们强迫子弹飞出屏幕x轴的外边–即使在它已经飞出屏幕y轴的外边界了。这里有许多方向来解决这个问题,比如检查飞出屏幕的最短距离,或者使用一个游戏回调函数来检查一个飞盘是否飞出,飞出就移出场景。但是,在这里,我们尽量保持简单。

最后一件事情就是,决定飞盘移动的时间。我们想让子弹以常量速度飞行,不管飞行方向如何。因此,我们不得不再做一点点数学。我们能够使用 Pythagorean Theorem来计算我们移动了多久。记得几何学中,三角形的斜边=两个直角边的平方和再开根号。

一旦我们得到了距离,我们就可以通过除了速度来得到时间。因为速度=距离/时间。换句话说 时间=距离/速度。

余下的部分就和设置我们target一样了。编译并运行,现在忍者可以射击侵犯的敌人了!



碰撞检测

现在,我们可以看到飞镖到处乱飞了!但是,我们的忍者真正想做的,是能够放倒一些怪物。好吧,让我们增加一些代码来检测什么时候我们的飞镖与怪物相撞了。

在cocos2d里面,有许多方法可以解决这个问题,包括使用cocos2d内置的开源物理引擎box2d和chipmunk。然而,为了使事情变得简单一点,在这里我们自己实现了一个简单的碰撞检测。

为了实现这个,我们首先需要当前场景中存在的飞镖和怪物。在HelloWorldScene类里面增加下面的声明:

NSMutableArray *_targets;
NSMutableArray *_projectiles;

然后在init方法里面初使化这些数组:

_targets = [[NSMutableArray alloc] init];
_projectiles = [[NSMutableArray alloc] init];

我们还需要在dealloc函数里面做一些清理工作,防止内存泄漏:

[_targets release];
_targets = nil;
[_projectiles release];
_projectiles = nil;

现在,我们修改addTarget方法,把一个新的target加到targets数组里面,并且为这个target设置一个tag,以便将来使用:

target.tag = 1;
[_targets addObject:target];

然后,修改ccTouchesEnded方法,同样的,把新增加的projectile加到projectiles数组里面,并为之设置一个tag供后面使用:

projectile.tag = 2;
[_projectiles addObject:projectile];

最后,修改你的spriteMoveFinished方法,基于tag标签来从正确的数组中移除相应的sprite。

if (sprite.tag == 1) { // target
[_targets removeObject:sprite];
} else if (sprite.tag == 2) { // projectile
[_projectiles removeObject:sprite];
}

 

编译并运行程序,确保一切都ok。目前来说,应该没有什么可见的差别。但是,接下来我们就会去实现真正的碰撞检测了。

现在,在HelloWorldScene里面增加如下方法:

- (void)update:(ccTime)dt {NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
for (CCSprite *projectile in _projectiles) {
CGRect projectileRect = CGRectMake(
projectile.position.x - (projectile.contentSize.width/2),
projectile.position.y - (projectile.contentSize.height/2),
projectile.contentSize.width,
projectile.contentSize.height);NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
for (CCSprite *target in _targets) {
CGRect targetRect = CGRectMake(
target.position.x - (target.contentSize.width/2),
target.position.y - (target.contentSize.height/2),
target.contentSize.width,
target.contentSize.height);if (CGRectIntersectsRect(projectileRect, targetRect)) {
[targetsToDelete addObject:target];
}
}for (CCSprite *target in targetsToDelete) {
[_targets removeObject:target];
[self removeChild:target cleanup:YES];
}if (targetsToDelete.count > 0) {
[projectilesToDelete addObject:projectile];
}
[targetsToDelete release];
}

 

for (CCSprite *projectile in projectilesToDelete) {
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
}

 

上面的代码应该非常清楚。我们仅仅通过遍历projectiles和targets数组,为每个projectile和target创建边界矩形,然后使用CGRectIntersectsRect来检测碰撞。如果发现有碰撞了,我们就从场景中移除精灵,同时也把它移除出数组。注意,我们不得不添加一个toDelete数组,因为我们不能在遍历一个数组的时候去删除数组中的对象。当然,还有许多方式可以实现类似的逻辑,我只不过挑选了简单的方法。

在你真正完成之前,还差最后一件事情。在你的init方法里面调用下面的函数:

[self schedule:@selector(update:)];

 

编译并运行,现在,当你的飞镖和怪物相碰的时候,他们都会消失啦!

完成触摸事件

我们离制作一个可以玩的游戏(但是非常简单)的目标已经越来越近了。我们仅仅需要增加一些音效和背景音乐(试想哪个游戏没有声音呢!),再增加一点点简单的逻辑就更好了。

如果你之前看我的博文《关于iphone上面的音效编程》的话,你将会非常高兴,因为使用cocos2d向游戏里音效和背景音乐实在是太简单了。

首先,把一些背景音乐和音效拖到工程的resource文件夹中。你可以使用 cool background music I made 或者我的 awesome pew-pew sound effect,或者自制一些。

然后,在HelloWorldScene.m文件里导入下面的头文件:

#import ”SimpleAudioEngine.h”

在你的init方法里加载背景音乐:

[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@”background-music-aac.caf”];

然后,在你的ccTouchesEnded方法里面添加音效代码:

[[SimpleAudioEngine sharedEngine] playEffect:@”pew-pew-lei.caf”];

现在,让我们创建一个新的场景,来作为“You Win”或者“You Lose”的标志。右击Classes文件夹,然后选择File/New File并选择Objective-c class。同时,确保NSObject基类被选中。点击下一步,然后输入GameOverScene作为文件名,同时确保“Also create GameOverScene.h”复选框打上勾。

然后把GameOverScene.h里面的文件替换成下面的代码:

#import ”cocos2d.h”@interface GameOverLayer : CCColorLayer {
CCLabelTTF *_label;
}
@property (nonatomic, retain) CCLabelTTF *label;
@end@interface GameOverScene : CCScene {
GameOverLayer *_layer;
}
@property (nonatomic, retain) GameOverLayer *layer;
@end

 

接下来替换掉GameOverScene.m文件里的内容:

#import ”GameOverScene.h”
#import ”HelloWorldScene.h”@implementation GameOverScene
@synthesize layer = _layer;- (id)init {if ((self = [super init])) {
self.layer = [GameOverLayer node];
[self addChild:_layer];
}
return self;
}- (void)dealloc {
[_layer release];
_layer = nil;
[super dealloc];
}@end

 

@implementation GameOverLayer
@synthesize label = _label;

-(id) init
{
if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {

CGSize winSize = [[CCDirector sharedDirector] winSize];
self.label = [CCLabelTTF labelWithString:@"" fontName:@"Arial" fontSize:32];
_label.color = ccc3(0,0,0);
_label.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:_label];

[self runAction:[CCSequence actions:
[CCDelayTime actionWithDuration:3],
[CCCallFunc actionWithTarget:self selector:@selector(gameOverDone)],
nil]];

}
return self;
}

- (void)gameOverDone {

[[CCDirector sharedDirector] replaceScene:[HelloWorld scene]];

}

- (void)dealloc {
[_label release];
_label = nil;
[super dealloc];
}

@end

 

注意,这里有两个不同的对象:场景和层。场景可以包含任意数量的层,但是此例中只有一个层。这个层只是在屏幕的中间放置了一个label,然后运行了一个action。这个action的作用就是,等待3秒钟,然后调用一个回调函数切换回HelloWorld场景。

最后,让我们增加一些基本的游戏逻辑。首先,让我们来追踪player销毁的飞镖projectiles。接下来,在HelloWorld类里面增加一个成员变量,如下所示:

int _projectilesDestroyed;

 

在HelloWorldScene.m里面,导入我们的GameOverScene类:

#import ”GameOverScene.h”

 

在update方法里,增加(销毁的projectile)计数,同时检测游戏胜利的条件。并在targetsToDelete循环里,紧接着removeChild:target的地方添加如下代码:

_projectilesDestroyed++;
if (_projectilesDestroyed > 30) {
GameOverScene *gameOverScene = [GameOverScene node];
[gameOverScene.layer.label setString:@"You Win!"];
[[CCDirector sharedDirector] replaceScene:gameOverScene];
}

 

最后,让我们这样设计,只要有一个怪物穿过了屏幕左边,你就输了。修改spriteMoveFinished方法,通过在tag==1里面、removeChild:sprite后面添加下面的代码:

GameOverScene *gameOverScene = [GameOverScene node];
[gameOverScene.layer.label setString:@"You Lose :["];
[[CCDirector sharedDirector] replaceScene:gameOverScene];

继续,编译并运行程序。现在,你的游戏可以实现胜利或者失败的场景了!:)

获得源代码

旁边是本教程使用的完整的源代码:simple Cocos2D iPhone game 。

何去何从?

这个项目对于一个cocos2d的初学者来说非常有帮助,而且你还可以自己往项目里面添加更多新的特性。或许你可以尝试一下,添加一个提示框,提示当前你已经打中了多少个怪物了。或者你可以增加一些很酷的动画,比如怪物被击中后不是直接消失,而是用一段动画来模拟死去。(可以参考cocs2d TestBed里面的ActionsTest,EffectsTest和EffectsAdvancedTest)。或者你还可以增加更多的图片和声音资源,或者更多的游戏逻辑。心情发挥吧!