使用Cocos2D制作简单iPhone游戏的教程

虫虫 发布于 2012/01/15 10:20
阅读 1K+
收藏 3

作者:Ray Wenderlich

Cocos2D是个用于iPhone的强大开发库,可以为你的iPhone游戏开发节省大量的时间。它带有精灵支持、炫丽的图像效果、动画、实体库、音效引擎等等内容。

我刚刚开始学习使用Cocos2D,尽管有各种各样的Cocos2D初学教程,但我无法找到我真正想要的教程,即制作一款带有动画、碰撞和音频的简 单游戏,不带有过多的高级功能。最终,我自行制作了一款简单的游戏,我觉得需要根据此次开发经验编写教程,以便能为其他初学者所使用。

该教程将知道开发简单iPhone游戏的从头到尾的步骤。你可以按顺序学习,或者直接跳到文章末尾的示例项目上。

下载和安装Cocos2D

你可以从Cocos2D Google Code页面下载Cocos2D。

下载代码后,你需要做的就是安装有用的项目模板。打开末端窗口,输入以下命令:./install-templates.sh -f -u。

必须注意的是,如果你有在非标准directory上安装过XCode,你可以随意将参数输入安装文本中(游戏邦注:就像在同一台电脑上有多个SDK版本的做法一样)。

开始

现在,让我们先开始创建简单的“Hello World”项目,并使用刚刚安装的Cocos2D模板来运行。打开XCode并新建Cocos2D项目,选择cocos2d中的“Application”模板,将项目命名为“Cocos2DSimpleGame”。

NewProject(from raywenderlich.com)

NewProject(from raywenderlich.com)

继续构建并运行这个模板。如果不出差错的话,你将会看到如下画面:

HelloWorld(from raywenderlich.com)

HelloWorld(from raywenderlich.com)

Cocos2D中融入了“界面”的概念,就像游戏中的“关卡”或“屏”一样。比如,游戏主菜单需要一个界面,主动作部分也需要一个界面,游戏结束同 样需要一个界面。界面分为多个层(游戏邦注:类似于Photoshop),曾包含精灵、标签、菜单等节点。节点也可能包含其他节点(游戏邦注:比如精灵中 可能有个子精灵)。

看下上述实例项目,你会发现其中只含有一个层,即HelloWorldLayer,我们将在此开始实施主要的游戏玩法。继续将其打开,你会发现现在初始方法中有个名为“Hello World”的标签。我们把这个标签拿走,用精灵来替换。

添加精灵

在我们添加精灵之前,我们需要某些图片。你可以用自己设计的图片,也可以使用这个项目中使用的图片,包括玩家图片、抛射物图片和目标图片。

获得图片后,将它们拖到XCode的资源文件夹中,确认“Copy items into destination group’s folder (if needed)”前打钩。

现在我们已经有图片了,下一步就需要为玩家图片指定位置。必须注意的是,在Cocos2D中屏幕左下角的坐标为(0, 0),往右或往上移动分别使X轴和Y轴值增加。因为这个项目采用的是风景模式,这意味着右上角的坐标为(480, 320)。

还必须注意的是,当我们设定某个物体的位置时,其位置的参考点所添加的精灵的中心点。因而,如果我们要让玩家图表在横轴上与边界对其,纵轴位于中心,我们应该:

1、位置的X轴坐标设置为“(玩家精灵宽度)/2”

2、位置的Y轴坐标设置为“(窗口高度)/2”

下图清晰地显示所设置的玩家精灵的位置:

Sprite Coordinates(from raywenderlich.com)

Sprite Coordinates(from raywenderlich.com)

接下来,打开Classes文件夹,点击HelloWorldLayer.m,将初始方法用以下代码替换:

-(id) init
{
if( (self=[super init] )) {
CGSize winSize = [[CCDirector sharedDirector] winSize];
CCSprite *player = [CCSprite spriteWithFile:@"Player.png"
rect:CGRectMake(0, 0, 27, 40)];
player.position = ccp(player.contentSize.width/2, winSize.height/2);
[self addChild:player];
}
return self;
}

编辑运行,你会看到精灵显示完好,但背景默认为黑色。对于这个项目来说,白色的背景看起来会好很多。自定义Cocos2D中的层背景的方式之一是使用CCLayerColor。点击HelloWorldLayer.h,将HelloWorld界面声明定义如下:

@interface HelloWorldLayer : CCLayerColor

然后点击HelloWorldLayer.m,对初始方法做以下些许修改就可以将背景设定为白色:

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

继续编辑运行,你应该会看到精灵正位于白色的背景之上。

精灵正位于白色的背景之上(from raywenderlich)

精灵正位于白色的背景之上(from raywenderlich)

移动目标

接下来,我们将在界面中添加某些供忍者攻击的目标。为让游戏更为有趣,我们将目标设定为可以移动,否则游戏难度就太低了!因而,我们先将目标位置设定在右侧屏幕之外,然后为其设定动作,逐渐向左移动。

在最初方法之前添加以下方法:

-(void)addTarget {

CCSprite *target = [CCSprite spriteWithFile:@"Target.png"
rect:CGRectMake(0, 0, 27, 40)];

// Determine where to spawn the target along the Y axis
CGSize winSize = [[CCDirector sharedDirector] winSize];
int minY = target.contentSize.height/2;
int maxY = winSize.height – target.contentSize.height/2;
int rangeY = maxY – minY;
int actualY = (arc4random() % rangeY) + minY;

// Create the target slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
target.position = ccp(winSize.width + (target.contentSize.width/2), actualY);
[self addChild:target];

// Determine speed of the target
int minDuration = 2.0;
int maxDuration = 4.0;
int rangeDuration = maxDuration – minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;

// Create the actions
id actionMove = [CCMoveTo actionWithDuration:actualDuration
position:ccp(-target.contentSize.width/2, actualY)];
id actionMoveDone = [CCCallFuncN actionWithTarget:self
selector:@selector(spriteMoveFinished:)];
[target runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];

}

在这里,我用较为冗长的代码来使其中的动作更容易让人理解。第一部分代表的是我们之前讨论过的内容:我们做些简单的计算来设定所创建物体的位置,并以与设定玩家精灵同样的方式放置在场景中。

这里的新元素就是动作的添加。Cocos2D提供了大量内置动作,你可以用来设定精灵动作,比如移动、跳跃、消失及其他动画动作。在这里,我们在目标上使用了以下三个动作:

CCMoveTo:我们使用CCMoveTo动作来指导物体从屏幕之外逐渐向左移动。你会注意到,我们可以指定移动的间隔时长,这里我们设定时长在2秒至4秒间随意变化。

CCCallFuncN:CCCallFuncN功能允许我们指定在动作发生时物体发出回叫信号。我们指定一种称为“spriteMoveFinished”的回叫信号。

CCSequence:CCSequence动作让那个我们可以设置系列动作按次序发生,每次一个动作。以这种方式,我们可以首先执行CCMoveTo动作,等该动作结束后再执行CCCallFuncN动作。

接下来,便是添加我们在CCCallFuncN动作中提到的回叫功能。我们可以将以下代码添加至addTarget之前:

-(void)spriteMoveFinished:(id)sender {
CCSprite *sprite = (CCSprite *)sender;
[self removeChild:sprite cleanup:YES];
}

该功能的目标在于,一旦精灵移动出屏幕后,就将其从屏幕中移除。这一点非常重要,这样就不会有大量无用的精灵存在于屏幕之上了。应该注意的是,还有其他更好的办法来处理这个问题,比如重复使用精灵,但在这个新手教程中我们采取这种较为简单的做法。

最后需要做的事情是,我们需要调用创造目标的方法!为让游戏变得有趣,我们让目标随时间不断变化。在Cocos2D中,我们可以通过将回叫功能设定为周期性回叫来实现这个目标。在初始方法中添加以下回调代码:

[self schedule:@selector(gameLogic:) interval:1.0];

然后以下列方法来执行回调功能:

-(void)gameLogic:(ccTime)dt {
[self addTarget];
}

现在,如果你编辑并运行项目,你应该会看到目标在屏幕中移动,如下图所示:

目标在屏幕中移动(from raywenderlich)

目标在屏幕中移动(from raywenderlich)

射击抛射物

此时此刻,我们的忍者正希望能够有所动作,因而让我们添加射击动作!执行射击动作的方式有许多种,但是在这款游戏中,我们将设计成,当用户点击屏幕时,忍者就会朝点击点的方向射出抛射物。

接下来,我将使用CCMoveTo动作来实现此目标,以使新手能够容易理解。为了实现这个目标,我们需要做些数学运算。这是因为CCMoveTo需 要我们指定抛射物的落点,但是我们不能只使用接触点,因为接触点代表的只是玩家角色射击的方向而已。我们想保证射出的子弹能够通过接触点然后射出屏幕。

下图显示我们所要做的工作:

保证子弹能够通过接触点然后射出屏幕(from raywenderlich)

保证子弹能够通过接触点然后射出屏幕(from raywenderlich)

因此,你可以看到原点和触点以及X和Y轴平行线组成了一个小三角形。我们只需要以同等的比例来绘制出大三角形,将一个落点设置在屏幕之外。

接下来,要处理的是代码问题。首先,我们要激活层上的触点。在初始方法中添加下列代码:

self.isTouchEnabled = YES;

激活层上的接触之后,我们现在可以接收到接触事件的回调函数。因而,让我们执行ccTouchesEnded方法,当用户接触屏幕时就可以产生回调,使用下列代码:

- (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]];

}

在第一部分中,我们选择了一个触点,获得触点在当前视图中的位置,然后调用convertToGL将坐标转化成当前的布局。这一点很重要,因为我们使用的是风景模式。

接下来,我们装载抛射物精灵,像往常那样设置初始位置。随后,我们决定希望抛射物移向何处,根据之前描述的运算法则,使用玩家和触点间的矢量作为指导方向。

应该注意的是,运算法则并不是很理想。我们强迫子弹一直移动,直到其到达屏幕外的X点。

我们必须做的最后一件事是决定移动的间隔。我们想要让子弹以恒定不变的速率沿某个方向射出,因而我们还得做些数学运算。利用Pythagorean Theorem,我们可以推导出移动的速度。从几何学公式上看,三角形斜边的平方等于两边的平方和。只要我们得到移动的距离,将其除以速度就可以得到间隔 时间。

剩下的工作就是像目标那样设定动作。编辑运行,现在你的忍者可以以恒定的速率射出子弹了。

你的忍者可以以恒定的速率射出子弹了(from raywenderlich)

你的忍者可以以恒定的速率射出子弹了(from raywenderlich)

碰撞检测

现在,我们已经将忍者的武器射击设计完成了。但是在游戏中,忍者想要做的是击落目标。所以,让我们添加某些发现抛射物击中目标的代码。

在Cocos2D中的解决方案多种多样,包括使用物理库Box2D或Chipmunk。但是为保持这个教程的简单性,我们决定自行设置一种简单的碰撞检测。

为实现这个目标,我们首先需要将目标和抛射物设计在当前界面中。

添加以下代码至HelloWorldLayer中:

NSMutableArray *_targets;
NSMutableArray *_projectiles;

然后用初始方法来初始化:

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

清除dealloc方法中的记忆:

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

现在,修改addTarget方法,添加新目标至目标列表并设定供以后使用的标签:

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

然后修改ccTouchesEnded方法,添加新抛射物至抛射物列表并设定供以后使用的标签:

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

最后,修改spriteMoveFinished方法,根据标签从恰当的列表中移除内容:

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

编辑运行项目,保证项目仍然运转良好。现在应该还看不出什么很明显的差别,但是我们需要执行某些接触察觉。

添加下列方法至HelloWorldLayer:

- (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];
}

以上代码看起来应该很清楚。我们只是不断重复抛射物和目标,创造与其界限盒保持一致的矩形,利用CGRectIntersectsRect来寻找交 叉点。如果找到了交叉点,我们将他们从界面和列表中移除。应该注意的是,我们必须将物体添加至“toDelete”列表中,因为你无法在重复物体时将其从 列表中移除。依然有多种方法可以实现这个目标,我还是会阐述较为简单的方法。

在我们开始运行项目之前,还必须做一件事情,添加以下代码至初始方法中,安排该方法以尽量高的频率运行:

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

编辑运行,现在你的抛射物命中目标时,它们应该会同时消失!

末期工作

现在,我们几乎快完成了这款游戏(游戏邦注:尽管游戏较为简单)。我们只需要添加些许音效和音乐以及某些简单的游戏逻辑。

如果你看过我关于iPhone游戏音频编程设计的系列博文,你就会知道Cocos2D开发者用此工作来在游戏中添加基础音效是多么的简单。

首先,拖动背景音乐和射击音效到资源文件夹中。你可以使用我所制作的背景音乐和音效,也可以自行制作。

然后,在HelloWorldLayer.m顶端添加下列导入:

#import “SimpleAudioEngine.h”

在初始方法中,以下列代码来控制背景音乐的播放:

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

在ccTouchesEnded方法中,以下列代码来播放音效:

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

现在,让我们创造一个新界面,用来显示玩家的胜利或失败。点击Classes文件夹,然后点击File\New File,选择Objective-C类,确认子类NSObject已被选择。点击Next,然后输入GameOverScene作为文件名,确认 “Also create GameOverScene.h”已选择。

然后以下列代码替换GameOverScene.h:

#import “cocos2d.h”

@interface GameOverLayer : CCLayerColor {
CCLabelTTF *_label;
}
@property (nonatomic, retain) CCLabelTTF *label;
@end 
@interface GameOverScene : CCScene {
GameOverLayer *_layer;
}
@property (nonatomic, retain) GameOverLayer *layer;
@end

然后以下列代码替换GameOverScene.m:

#import “cocos2d.h”

@interface GameOverLayer : CCLayerColor {
CCLabelTTF *_label;
}
@property (nonatomic, retain) CCLabelTTF *label;
@end 
@interface GameOverScene : CCScene {
GameOverLayer *_layer;
}
@property (nonatomic, retain) GameOverLayer *layer;
@end

Then replace GameOverScene.m with the following code:

#import “GameOverScene.h”
#import “HelloWorldLayer.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:[HelloWorldLayer scene]];

}

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

@end

必须注意的是,这里有两个不同的概念:界面和层。界面可以包含任意数量的层,虽然在这个示例项目中只包含一个。层只是在屏幕中间设立一个标签,安排转化发生3秒之后切回Hello World界面。

最后,让我们添加某些基本的游戏逻辑。首先,让我们先设置玩家已经摧毁的抛射物。将HelloWorldLayer.h中的HelloWorldLayer类做下列修改:

int _projectilesDestroyed;

在HelloWorldLayer.m中,添加GameOverScene类的导入:

#import “GameOverScene.h”

增加数量并在更新方法中检查胜利的状况,使用下列代码:

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

最后,让我们设置成,某只目标触及玩家时玩家就失败。修改spriteMoveFinished方法,添加下列代码到标签中:

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

继续将项目编辑运行,现在你应该可以看到胜利或失败界面。

后续发展

这个项目只是个最基本的示例,你可以使用Cocos2D在项目中添加更多新功能。或许你可以尝试添加条状图显示你还需要摧毁多少个目标才能获得胜 利,也可以为怪物的死亡添加更加绚烂的动画,或出于游戏趣味性目的添加更多音效、艺术或游戏逻辑。你完全可以尽量发挥自己的才华!

加载中
0
gangjialin
gangjialin
教程很经典,入门必看
返回顶部
顶部