• 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的基础知识有所了解,是的,还是从第一章开始学起吧。

     

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

     

     

     

    开始前的准备

     

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

     

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

     

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

     

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

     

    摄像头就位!

     

    如果现在就编译运行项目,你神马感觉都没有!是的,漆黑一片的夜空,白得煞人的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漫游指南第十一章亦真亦幻

     

     

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

     

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

     

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

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

     

    点击 + 按钮,选择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的时候,你会看到很炫的视觉效果!

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

     

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

     

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

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

     

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

     

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

     

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

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

     

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

     

    现在我们多多少少了解了下陀螺仪的相关知识(更多可以参考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之间的数值(一个整圆)。

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

     

     

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

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

     

    经过这种处理后,我们只需要关心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艘飞船出现在 你的视野之中!

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

     

     

    发射激光,战斗打响!

     

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

     

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

     

    在开始之前,先让我们干掉屏幕上的那些标签吧——它们只是用来调试游戏用的。切换到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漫游指南第十一章亦真亦幻

     

     

    太酷了!现在让我们添加一些爆炸效果,这样当玩家触碰屏幕的时候会更热血澎湃!首先把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

  • 相关阅读:
    redis学习(二)-高级特性
    redis学习(一)-基础知识
    设计模式类型
    装饰者设计模式
    udp代理
    docker: unrecognized service
    centos6.x 编译安装zabbix_proxy 2.2.5
    写了一个shell,删除15天以上日志
    tempo 删除团队失败
    github批量删除organization下的private repo
  • 原文地址:https://www.cnblogs.com/iapp/p/3631836.html
Copyright © 2020-2023  润新知