Cocos2d漫游指南第十一章亦真亦幻

原文在此:

http://www.raywenderlich.com/3997/introduction-to-augmented-reality-on-the-iphone

 

示例代码在此:

http://www.raywenderlich.com/downloads/ARSpaceships.zip

 

在这部分的教程中,我们将学习如何为iPhone/iPod touch制作一款简单的增加现实游戏!

 

我们将会用到摄像头,陀螺仪和Cocos2d。怎么样,是不是已经热血沸腾了?

 

在写这篇教程的时候,哥实在是激情万丈,探索这些新的技术实在是太激动人心了!当然,我们不得不遇到一些数学和转换的问题,但是哥保证,任何一个懂得加减乘除的攻城师应该不会对此感到头疼的!

 

当然,为了学习这篇教程,你得有iPhone4,因为我们需要用到gyroscope(陀螺仪)来移动你的视角。

 

同时,你必须对cocos2d的基础知识有所了解,是的,还是从第一章开始学起吧。

 



 

 

 

开始前的准备

 

打开Xcode,选择创建New Project,选择cocos2d模板,把项目命名为ARSpaceships。

 

这个游戏中的部分资源来自之前学习过的激战太空射击游戏,所以让我们下载并解压缩这些文件。

 

下载完文件后,将Fonts, Sounds,Spritesheets这些资源文件都拖到项目的Resources里面。此时你的项目看起来会是这样的:

 

 Cocos2d漫游指南第十一章亦真亦幻_第1张图片

 

摄像头就位!

 

如果现在就编译运行项目,你神马感觉都没有!是的,漆黑一片的夜空,白得煞人的Hello World,一切都是如此的苍白!攻城师朋友们,让我们来点乐子吧!选择AppDelegate.h文件,然后在interface的声明部分添加一个UIView。

 

UIView *overlay;

 

现在让我们切换到AppDelegate.m文件。下滚到EAGLView *glView这一行代码,把pixelFormat(像素格式)调整为kEAGLColorFormatRGBA8,如下所示:

EAGLView *glView =[EAGLView viewWithFrame:[window bounds]

    pixelFormat:kEAGLColorFormatRGBA8 depthFormat:0];

 

如果你不修改这里的像素格式,那么摄像头所获取的图像是无法显示的。既然我们玩的是增强现实游戏,这怎么了得!

 

然后,在[window addSubView: viewController.view]这个代码的下面,我们需要添加如下几行代码:

 

// set the background color of the view

[CCDirector sharedDirector].openGLView.backgroundColor =[UIColor clearColor];

[CCDirector sharedDirector].openGLView.opaque =NO;

 

// set value for glClearColor

glClearColor(0.0, 0.0, 0.0, 0.0);

 

// prepare the overlay view and add it to the window

overlay =[[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

overlay.opaque =NO;

overlay.backgroundColor=[UIColor clearColor];

[window addSubview:overlay];

 

在上面的代码中,我们将openGLView的背景色设置为清除(灰度值和透明度均为0),将视图层的不透明设置为否。

接下来我们设置了glClearColor(四个值分别是红色,绿色,蓝色和透明度)

最后我们创建了一个UIView,并添加到主窗口中,用来在后面显示从摄像头获取的图像。

 

下一步需要在刚才添加的代码之下继续增加新的代码:

 

#define CAMERA_TRANSFORM  1.24299

 

UIImagePickerController *uip;

 

@try{

    uip =[[[UIImagePickerController alloc] init] autorelease];

    uip.sourceType = UIImagePickerControllerSourceTypeCamera;

    uip.showsCameraControls =NO;

    uip.toolbarHidden =YES;

    uip.navigationBarHidden =YES;

    uip.wantsFullScreenLayout =YES;

    uip.cameraViewTransform = CGAffineTransformScale(uip.cameraViewTransform,

        CAMERA_TRANSFORM, CAMERA_TRANSFORM);

}

@catch(NSException* e){

[uip release];

    uip =nil;

}

@finally{

if(uip){

[overlay addSubview:[uip view]];

[overlay release];

}

}

 

[window bringSubviewToFront:viewController.view];

 

首先我们定义了摄像头的缩放常量值。摄像头所获取图像的长宽比是4:3,而iphone屏幕的长宽比是3:4,所以我们需要调整摄像头获取图像的比例,以弥补这个差距。

然后我们创建了一个UIImagePickerController,并设置了部分属性,然后缩放了它的比例,并将其添加到overlay视图中。注意这里使用了异常处理,不太了解的朋友可以看看Programming in Objective-C中的相关内容。

最后,我们需要将viewController.view(里面包含了cocos2d的显示)设置到前面,这样的话它会显示到摄像头视图的前面。

 

现在让我们来编译运行,你可以看到来自摄像头的图像会作为背景显示在Hello World的后面。不得不说,如果你够用心,可以弄个红心或者I love you神马的,拿这个来讨好你的小女友!

 

Cocos2d漫游指南第十一章亦真亦幻_第2张图片

 

 

摇一摇,晃一晃,摆一摆!

 

现在这款游戏的现实部分已经搞定了,我们可以集中精力攻克教程中更难的部分了!

 

首先我们需要把CoreMotion这个框架添加到项目中。点击项目名,选择ARSpaceships target,选择Build Phases选项,展开Link Binary With Libraries。

 Cocos2d漫游指南第十一章亦真亦幻_第3张图片

 

点击 + 按钮,选择CoreMotion.framework,然后点击Add 按钮。现在一切就绪,我们可以在项目里面使用陀螺仪了!

 

打开HelloWorldLayer.h文件,在顶部添加下面的代码:

#include <CoreMotion/CoreMotion.h>

#import <CoreFoundation/CoreFoundation.h>

 

在interface的声明部分添加以下变量:

CMMotionManager *motionManager;

CCLabelTTF *yawLabel;

CCLabelTTF *posIn360Label;

 

在interface的声明下面添加属性语句:

@property(nonatomic, retain) CMMotionManager *motionManager;

 

接下来我们要进入最精彩的部分了!打开HelloWorldLayer.m文件,在init方法的if((self = [super init]))语句中,删除创建”Hello World”标签的语句,并使用下面的代码来设置新的标签。

// add and position the labels

yawLabel =[CCLabelTTF labelWithString:@"Yaw: " fontName:@"Marker Felt" fontSize:12];

posIn360Label =[CCLabelTTF labelWithString:@"360Pos: " fontName:@"Marker Felt" fontSize:12];

yawLabel.position =  ccp(50, 240);

posIn360Label.position =  ccp(50, 300);

[self addChild: yawLabel];

[self addChild:posIn360Label];

 

 

上面的代码平淡无奇,添加了两个标签,设置了字体和标签上的文字,仅此而已。至于标签的位置,我们都放在屏幕的左侧。

 

接下来我们需要设置motion manager,将使用它来启动陀螺仪。

self.motionManager =[[[CMMotionManager alloc] init] autorelease];

motionManager.deviceMotionUpdateInterval =1.0/60.0;

if(motionManager.isDeviceMotionAvailable){

[motionManager startDeviceMotionUpdates];

}

 

[self scheduleUpdate];

 

使用上面的代码,我们对motion manager进行了初始化。同时,设置更新频率为每秒60次。如果设备带有陀螺仪,就会开始更新。最后我们使用定时器进行更新。

 

别忘了在文件的顶部添加了synthesize语句:

@synthesize motionManager;

 

 

同时,由于我们这里使用定时器更新,所以需要添加一个update方法。在init方法的下面添加这样一个方法:

 

-(void)update:(ccTime)delta {

    CMDeviceMotion *currentDeviceMotion = motionManager.deviceMotion;

    CMAttitude *currentAttitude = currentDeviceMotion.attitude;

 

// 1: Convert the radians yaw value to degrees then round up/down

float yaw = roundf((float)(CC_RADIANS_TO_DEGREES(currentAttitude.yaw)));

 

// 2: Convert the degrees value to float and use Math function to round the value

[yawLabel setString:[NSString stringWithFormat:@"Yaw: %.0f", yaw]];

 

// 3: Convert the yaw value to a value in the range of 0 to 360

int positionIn360 = yaw;

if(positionIn360 <0){

        positionIn360 =360+ positionIn360;

}

 

[posIn360Label setString:[NSString stringWithFormat:@"360Pos: %d", positionIn360]];

 

}

 

现在让我们来编译和运行程序。你可以在对应的标签上看到Yaw和positionIn360的数值变化。

 

冲破迷雾!

 

好吧,数值是看到了,但是---究竟是怎么实现的?上面代码好像看不懂也!!!

 

让我们花点时间好好解释一下吧。

首先从iTunes appstore中下载一个免费的Gyrosocope app(http://itunes.apple.com/us/app/gyroscope/id381953722?mt=8),运行这个应用,当我们移动iPhone的时候,你会看到很炫的视觉效果!

 

 

在上面的数值中,需要关注的是Yaw(摇摆,偏航)。Yaw代表的是左右摇晃的运动。在这款应用中是使用degree(角度)为单位的,而从motion manager中获得的数值是以radian(弧度)为单位标示的。所以我们用了CC_RADIANS_TO_DEGRESS方法来完成这个转换工作。

 

所以,在第一部分的代码中,我们获取了以弧度为单位的yaw value(摆动值),将其转换为角度,然后将数值赋予yaw 这个变量。

第二部分的代码就相对简单了,只是把偏航值显示在屏幕上。运行程序的时候,你可以看到数值的变动是从0变到180,然后从-180变回0。

 

 

 

再来看第三部分的代码,这个positionIn360变量到底是个神马东西?!好吧,其实没别的意思,哥是图方便,这样的话把飞碟放在屏幕上时会更简单。

 

这里所使用的逻辑很简单。如果偏航值是正,那么什么都不需要做。如果偏航值是负值,我们需要将其加上360。而最后一行代码不过是把这个数值显示在屏幕上而已。

 

 

灯光就位!摄像头就位!开机!

 

现在我们多多少少了解了下陀螺仪的相关知识(更多可以参考CMMotionManager类的官方参考文档-在xcode的documentations中搜索即可),该是添加太空飞船的时候了!

首先让我们创建一个新文件。左键点击ARSpaceships,然后选择New File,选择iOS\Cocoa Touch\Objective-C class,然后点击Next。确保选中Subclass of NSObject,然后点击Next,保存名称为EnemyShip.m,然后点击Save。

 

使用下面的代码来替代EnemyShip.h中的内容:

 

 

#import "cocos2d.h"

 

@interface EnemyShip : CCSprite {

int yawPosition;

int timeToLive;

}

 

@property(readwrite)int yawPosition;

@property(readwrite)int timeToLive;

 

@end

 

然后用以下代码替代EnemyShip.m的内容:

#import "EnemyShip.h"

 

 

@implementation EnemyShip

 

@synthesize yawPosition, timeToLive;

 

-(id)init {

    self =[super init];

if(self){

        yawPosition =0;

        timeToLive =0;

         }

return self;

}

 

@end

 

现在让我们重新切换到 HelloWorldLayer.h文件。在文件的顶部添加以下代码:

#import "EnemyShip.h"

 

 

然后在声明部分添加:

 

NSMutableArray*enemySprites;

int enemyCount;

CCSpriteBatchNode *batchNode;

 

最后,在interface声明部分的下面添加属性说明和方法定义,至于具体的作用当然要到后面来详细解释罗:

 

@property(readwrite)int enemyCount;

 

-(EnemyShip *)addEnemyShip:(int)shipTag;

-(void)checkEnemyShipPosition:(EnemyShip *)enemyShip withYaw:(float)yawPosition;

-(void)updateEnemyShipPosition:(int)positionIn360 withEnemy:(EnemyShip *)enemyShip;

-(void)runStandardPositionCheck:(int)positionIn360 withDiff:(int)difference withEnemy:(EnemyShip *)enemyShip;

 

让我们切换到HelloWorldLayer.m文件,并做以下修改:

// Place after the #import statement

#include <stdlib.h>

 

// Place after the other @synthesize statement

@synthesize enemyCount;

#define kXPositionMultiplier 15

#define kTimeToLive 100

 

// Add to the bottom of init

batchNode =[CCSpriteBatchNode batchNodeWithFile:@"Sprites.pvr.ccz"];

[self addChild:batchNode];  

[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"Sprites.plist"];

 

通过以上代码,我们载入了在教程开始所添加的精灵表单。

 

接下来我们要添加一个方法让太空飞船出现在屏幕上。在dealloc方法的上面添加以下方法:

-(EnemyShip *)addEnemyShip:(int)shipTag {

 

    EnemyShip *enemyShip =[EnemyShip spriteWithSpriteFrameName:@"enemy_spaceship.png"];

 

// Set position of the space ship randomly

int x = arc4random()60;

    enemyShip.yawPosition = x;  

 

// Set the position of the space ship off the screen, but in the center of the y axis

// we will update it in another method

[enemyShip setPosition:ccp(5000, 160)];

 

// Set time to live on the space ship

    enemyShip.timeToLive = kTimeToLive;

    enemyShip.visible =true;

 

[batchNode addChild:enemyShip z:3 tag:shipTag];

 

return enemyShip;

}

 

这个方法接收了一个标记值,并返回一个EnemyShip 精灵对象。首先我们从精灵表单中创建了一个EnemyShip精灵,然后使用arc4random方法来获取一个范围在0-360之间的随机整数。最后我们设置了飞船的位置,将timeToLive的数值设置为100,把飞船添加为batchNode的子节点,并在结束处返回所添加的飞船对象。

 

在addEnemyShip方法的下面,让我们添加checkEnemyShipPosition方法的代码:

 

-(void)checkEnemyShipPosition:(EnemyShip *)enemyShip withYaw:(float)yawPosition {

// Convert the yaw value to a value in the range of 0 to 360

int positionIn360 = yawPosition;

if(positionIn360 <0){

        positionIn360 =360+ positionIn360;

}

 

BOOL checkAlternateRange =false;

 

// Determine the minimum position for enemy ship

int rangeMin = positionIn360 -23;

if(rangeMin <0){

        rangeMin =360+ rangeMin;

        checkAlternateRange =true;

}

 

// Determine the maximum position for the enemy ship

int rangeMax = positionIn360 +23;

if(rangeMax >360){

        rangeMax = rangeMax -360;

        checkAlternateRange =true;

}

 

if(checkAlternateRange){

if((enemyShip.yawPosition < rangeMax || enemyShip.yawPosition > rangeMin ) || (enemyShip.yawPosition > rangeMin || enemyShip.yawPosition < rangeMax)){

[self updateEnemyShipPosition:positionIn360 withEnemy:enemyShip];

}

}else{

if(enemyShip.yawPosition > rangeMin && enemyShip.yawPosition < rangeMax){

[self updateEnemyShipPosition:positionIn360 withEnemy:enemyShip];

}

}

}

 

在上面的代码中,一大堆的alternate,min和max是不是让你头大了?!其实,没那么复杂。

首先我们检查设备的yaw position(偏航位置),并将其转换为0-360之间的数值(一个整圆)。



 

 

由于我们所使用的数轴两端是0-360,所以需要确认是否设备的positionIn360在某一端上。这里我们使用23这个指定值代表显示在屏幕一半位置处的角度,如下图所示:



 

经过这种处理后,我们只需要关心0-23和337-360之间的数值。

 

此时,如果飞船的偏航位置在图中46度的范围内,就更新飞船的位置。checkAlternateRange的判断语句用于确定何时更新飞船的位置。

 

如果checkAlternateRange为True,我们需要检查是否飞船位置在最小和最大值之间。判断语句中的这些检查看起来令人抓狂,但是如果我们用实际的数值来验证,就明白这样做的道理了。

 

让我们假定:

positionIn360 = 20

rangeMin = 357

rangeMax = 20

enemyShip.yawPosition = 359

 

因为我们要考虑到数轴的两端,所以最小的范围值rangeMin要大于最大范围值rangeMax。接下来我们要完成所有的检查,发现飞船的位置大于rangeMin,这样我们会将飞船显示在屏幕上。

 

Else部分的语句看起来更直接一些。它只是检查飞船的位置是否在min和max之间。

 

接下来,让我们在checkEnemyShipPosition方法的下面添加以下方法:

-(void)updateEnemyShipPosition:(int)positionIn360 withEnemy:(EnemyShip *)enemyShip {

int difference =0;

if(positionIn360 <23){

// Run 1

if(enemyShip.yawPosition >337){

            difference =(360- enemyShip.yawPosition)+ positionIn360;

int xPosition =240+(difference * kXPositionMultiplier);

[enemyShip setPosition:ccp(xPosition, enemyShip.position.y)];

}else{

// Run Standard Position Check

[self runStandardPositionCheck:positionIn360 withDiff:difference withEnemy:enemyShip];

}

}elseif(positionIn360 >337){

// Run 2

if(enemyShip.yawPosition <23){

            difference = enemyShip.yawPosition +(360- positionIn360);

int xPosition =240-(difference * kXPositionMultiplier);

[enemyShip setPosition:ccp(xPosition, enemyShip.position.y)];

}else{

// Run Standard Position Check

[self runStandardPositionCheck:positionIn360 withDiff:difference withEnemy:enemyShip];

}

}else{

// Run Standard Position Check

[self runStandardPositionCheck:positionIn360 withDiff:difference withEnemy:enemyShip];

}

}

 

 

在这个方法中,我们需要判断设备的positionIn360是否在三个范围的一个中。在第一个测试中,判断positionIn360是否小于23,如果是,再判断是否有飞船出现在数轴的另一端(大于337)。

在第二个测试中,判断positionIn360是否大于337。如果是,再判断是否有飞船出现在数轴的另一端(小于23),和上一个测试完全相反。

第三个测试(也是最后一个),判断positionIn360是否在23和337之间。我们会调用runStandardPositionCheck这个方法。

具体的代码实现如下:

-(void)runStandardPositionCheck:(int)positionIn360 withDiff:(int)difference withEnemy:(EnemyShip *)enemyShip {

if(enemyShip.yawPosition > positionIn360){

        difference = enemyShip.yawPosition - positionIn360;

int xPosition =240-(difference * kXPositionMultiplier);

[enemyShip setPosition:ccp(xPosition, enemyShip.position.y)];

}else{

        difference = positionIn360 - enemyShip.yawPosition;

int xPosition =240+(difference * kXPositionMultiplier);

[enemyShip setPosition:ccp(xPosition, enemyShip.position.y)];

}

}

 

在这个方法中,我们查看enemyShip的位置是否在设备的positionIn360的左边或右边。如果enemyShip的位置的坐标值小于positionIn360,则出现在屏幕的左侧。如果enemyShip的位置的坐标值大于positionIn360,则出现在屏幕的右侧。

 

等等!你是不是忘了解释difference这个变量?好吧,事实是这样的:

如果飞船的偏航值在屏幕的范围内(从positionIn360-23到positionIn360+23),我们会首先计算出它在屏幕的哪一侧。如果它大于positionIn360,就在屏幕的右侧,反之就在屏幕的左侧。

Difference变量用于测量设备的positionIn360和飞船偏航值间的角度差。一旦得出difference的值,我们用一个指定的乘数乘以difference。这个乘数代表每个角度的像素量。这里我们选择的数值是15。

 

接下来,根据在屏幕的哪一册,我们会使用240(屏幕宽度除以2)来加上或减去这个计算出的数值。

 

以上就是updateEnemyShipPosition方法的具体实现。

 

现在,该准备的方法都准备好了,接下来我们就需要调用这些方法了。

 

在init方法的底部,添加以下代码,从而在屏幕上添加五艘飞船。

 

 

// Loop through 1 - 5 and add space ships

enemySprites =[[NSMutableArray alloc] init];

for(int i =0; i <5; ++i){

    EnemyShip *enemyShip =[self addEnemyShip:i];

[enemySprites addObject:enemyShip];

    enemyCount +=1;

}

 

同时,因为我们把飞船添加到了屏幕中,就需要随时更新它们的位置。在update方法的结尾处添加以下代码:

// Loop through array of Space Ships and check the position

for(EnemyShip *enemyShip in enemySprites){

[self checkEnemyShipPosition:enemyShip withYaw:yaw];

}

 

 

同时,乘我们头脑还清醒,在dealloc方法的下面添加以下代码清除enemySpritesArray:

[enemySprites release];

 

编译运行游戏!现在你可以看到有5艘飞船出现在 你的视野之中!

 

 

 

发射激光,战斗打响!

 

现在我们的增强现实游戏其实已经挺酷了,不是吗?!不过这些外星飞船在眼前晃来晃去感觉真不爽,这可是哥自己的地盘呀!

 

好吧,让我们加点绚丽至极的激光和爆炸效果吧!

 

在开始之前,先让我们干掉屏幕上的那些标签吧——它们只是用来调试游戏用的。切换到HelloWorldLayer.m中,注释掉所有和yawLabel和posIn360Label相关的东西。然后编译运行,确保没有误删掉有用的代码!

 

有趣的东西来了,来给游戏加点炮火吧!首先我们需要添加一个方法,来检查玩家的开火区域是否击中了飞船。在HelloWorldLayer.h文件中,在@end之前添加以下代码:

-(BOOL) circle:(CGPoint) circlePoint withRadius:(float) radius collisionWithCircle:(CGPoint) circlePointTwo collisionCircleRadius:(float) radiusTwo;

 

然后切换到HelloWorldLayer.m文件,在dealloc方面前添加以下方法:

-(BOOL) circle:(CGPoint) circlePoint withRadius:(float) radius collisionWithCircle:(CGPoint) circlePointTwo collisionCircleRadius:(float) radiusTwo {

         float xdif = circlePoint.x - circlePointTwo.x;

         float ydif = circlePoint.y - circlePointTwo.y;

 

         float distance =sqrt(xdif*xdif+ydif*ydif);

         if(distance <= radius+radiusTwo)returnYES;

 

         returnNO;

}

 

这个方法用来判断两个点的半径范围内是否重叠。所输入的参数是飞船的位置和屏幕的中心点。两个点的半径都设置为50。

 

首先计算出两点间的x和y坐标差。

接下来计算出两点间的距离。你应该还记得勾股定理吧,这里就不多介绍了。

 

然后我们需要在屏幕上添加一个瞄准镜,帮我们判断开火的位置。从这里下载项目所需的资源(http://www.raywenderlich.com/downloads/ARSpaceshipsResources.zip),然后把这个scope.png文件拖到Resources文件夹里。

 

切换到HelloWorldLayer.m文件,找到init方法,然后在[self scheduleUpdate];的前面添加以下代码:

 

// Add the scope crosshairs

CCSprite *scope =[CCSprite spriteWithFile:@"scope.png"];

scope.position = ccp(240, 160);

[self addChild:scope z:15];

 

// Allow touches with the layer

[self registerWithTouchDispatcher];

 

编译运行游戏,你会看到瞄准镜出现在屏幕的正中央!

 

 Cocos2d漫游指南第十一章亦真亦幻_第4张图片

 

 

太酷了!现在让我们添加一些爆炸效果,这样当玩家触碰屏幕的时候会更热血澎湃!首先把Explosition.plist添加到Resources里面。

 

好吧,这个文件是个神马东东,是干嘛的?

它是我用Particle Designer(http://particledesigner.71squared.com/)设计的粒子效果。这里我就不讲如何创建粒子效果了,但如果你亲自试一下,就会发现自己要做的一切不过是选择一种粒子系统,调整一些参数,然后导出为plist文件。是的,就这么简单!

 

Ok,现在让我们在dealloc方法的前面添加以下代码:

 

-(void)ccTouchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {

    CGPoint location = CGPointMake(240,160);

 

// 1

for(EnemyShip *enemyShip in enemySprites){

if(enemyShip.timeToLive >0){

// Check to see if yaw position is in range

BOOL wasTouched =[self circle:location withRadius:50 collisionWithCircle:enemyShip.position collisionCircleRadius:50];

 

if(wasTouched){

                enemyShip.timeToLive =0;

                enemyShip.visible =false;

                enemyCount -=1;

}

}

}

 

// 2

    CCParticleSystemQuad *particle =[CCParticleSystemQuad particleWithFile:@"Explosion.plist"];

    particle.position = ccp(240,160);

[self addChild:particle z:20];

    particle.autoRemoveOnFinish =YES;

 

// 3

if(enemyCount ==0){

// Show end game

        CGSize winSize =[CCDirector sharedDirector].winSize;

        CCLabelBMFont *label =[CCLabelBMFont labelWithString:@"You win!" fntFile:@"Arial.fnt"];

        label.scale =2.0;

        label.position = ccp(winSize.width/2, winSize.height/2);

[self addChild:label z:30];

}

}

 

上面代码的第一部分使用我们之前添加的碰撞检测方法,用来判断飞船是否在瞄准镜的范围内。如果飞船被击中,我们会设置飞船的属性,将其隐藏,并减少enemyCount的变量值。

在第二部分,我们在屏幕的中间添加了粒子效果。

在最后一部分,我们添加了一个游戏逻辑,当enemyCount变量等于0的时候,会显示一个标签,提醒玩家已经赢得了游戏。

 

到此为止,游戏似乎有点无聊,我们需要给飞船添加一些最基本的AI。在update方法的最后添加以下代码:

 

// Loop through array of Space Ships and if the timeToLive is zero

// change the yawPosition of the sprite

for(EnemyShip *enemyShip in enemySprites){

        enemyShip.timeToLive--;

if(enemyShip.timeToLive ==0){

int x = arc4random()60;

[enemyShip setPosition:ccp(5000, 160)];

            enemyShip.yawPosition = x;

            enemyShip.timeToLive = kTimeToLive;

}

}

 

 

使用这个方法,我们遍历了enemySprites数组,并更新timeToLive属性。然后会判断是否属性等于0,如果是,就会让飞船出现了另一个yawPosition,并重置timeToLive。编译运行游戏,现在有点难度了吧!

 

震耳欲聋!

 

没有音效的游戏会少了很多乐趣,所以让我们再来加点音乐元素吧!

 

在HelloWorldLayer.m的顶部添加一行代码导入音效引擎:

#import "SimpleAudioEngine.h"

 

向下滚动代码,在init方法中if语句的最后添加以下代码:

[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"SpaceGame.caf" loop:YES];

[[SimpleAudioEngine sharedEngine] preloadEffect:@"explosion_large.caf"];

[[SimpleAudioEngine sharedEngine] preloadEffect:@"laser_ship.caf"];

 

以上代码会加载背景音乐和音效。

 

接下来继续向下滚动代码,在ccTouchesBegan方法中,在该方法开始的部分添加以下代码:

[[SimpleAudioEngine sharedEngine] playEffect:@"laser_ship.caf"];

 

当玩家触碰屏幕的时候,这行代码会发出激光的音效。

 

继续在ccTouchesBegan方法中,在enemyShip in enemySprites的for 循环语句中,在(wasTouched)的if语句中添加以下代码:

[[SimpleAudioEngine sharedEngine] playEffect:@"explosion_large.caf"];

 

这样,当飞船被击中时,会发出爆炸的音效!

 

终于,终于,终于搞定了,一个完整的增强现实游戏出现在你的面前!

 

更多帮助你深入学习的文档:

苹果官方的core motion文档:http://developer.apple.com/library/ios/#documentation/CoreMotion/Reference/CoreMotion_Reference/_index.html

 

苹果官方的UIImagePickerController类文档:

http://developer.apple.com/library/ios/#documentation/uikit/reference/UIImagePickerController_Class/UIImagePickerController/UIImagePickerController.html<br />

 

关于摄像头和长宽比的更多文档:

http://stackoverflow.com/questions/3407986/uiimagepickercontroller-cameraviewtransform-acts-differently-in-ios-4

http://gotoandplay.freeblog.hu/archives/2010/07/06/3Dcompass_augmented_reality_085_-_fullscreen_camera_preview/

http://www.musicalgeometry.com/?p=821

你可能感兴趣的:(Cocos2d漫游指南第十一章亦真亦幻)