热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

(译)如何使用cocos2d制作一个太空射击游戏

原文链接地址:http:www.raywenderlich.com3611how-to-make-a-space-shooter-iphone-game程序截图:在这个教

原文链接地址:http://www.raywenderlich.com/3611/how-to-make-a-space-shooter-iphone-game

程序截图:

  在这个教程里面,你将会学习到如何为iPhone开发一个太空射击游戏!

  你可以使用加速计(重力感应)来控制飞船的移动,并且可以点击屏幕来发射激光武器。

  如果你对于如何制作iphone游戏完全陌生的话,这个教程可以帮助你!你将会学习到,如何从头至尾构建一个完整的游戏,不需要任何的经验!

  假如你对cocos2d编程完全陌生的话,那么你可能需要先学习一下相关的教程了。

  这个教程对于中级开发者来说也非常好,因为它覆盖了一些比较高级的主题,比如视差滚动(parallax scrolling),预分配CCNode,加速计移动以及粒子系统的使用。

  话不多说,直入主题!

 

安装cocos2d

  为了制作这个游戏,你需要成为  iOS developer program的一员(这样的话,你的程序就能够安装到你的iPhone上面去,不过听说网上有人越狱也可以安装,知道的朋友麻烦给个链接,谢谢!)同时,需要安装Xcode和cocos2d框架。

  如果你之前已经安装过cocos2d了,那接下来这部分就不要看了。(如果想使用新的cocos2d版本,只需要把之前安装的目录下面的模板文件全部删除,再按照下面的指令重新安装即可)。如果你重来没有安装过cocos2d的话,那么只需要按照下面的指令序列,一步步地安装到你的mac上面就行了。

  • 首先下载Cocos2D .确定获得最新版本--作者写这篇文章的时候是1.0.0-rc2版本,目前是1.0.0-rc3版本。虽然不是稳定版本,但是,没关系,其实很稳定啦!:)
  • 双击下载下来的文件,并且解压缩到一个安全的位置。
  • 打开Terminal (Applications\Utilities\Terminal), 然后使用cd命令定位到刚刚解压缩的cocos2d文件夹下面去。然后运行./install-templates.sh来安装xcode模板,如下面所示:
$ cd Downloads
$ cd cocos2d
- iphone - 1.0 . 0 - rc2
$ .
/ install - templates.sh  - - u

  如果一切顺利的话,你应该会看到终端里面一系列的输出语句:“ Installing xxx template”。

  然后重新启动Xcode,祝贺你,您已成功安装cocos2d了!

Hello, Cocos2D!

  让我们首先创建一个“Hello World”cocos2d工程。

  打开Xcode,选择 File\New\New Project,然后选 iOS\cocos2d template,接下来点Next,并且把工程命名为SpaceGame,再点Next并选择一个文件夹作为你的工程的保存路径,最后点Create。

  编译并运行工程,你将会看到一个“Hello World”出现在屏幕的正中间。

添加相关资源文件

  为了做这样一个iphone游戏,你将需要一些跟太空主题相关的图片资源和声音资源。

  你可以直接下载我老婆制作的太空游戏资源。

  因此,请直接下载吧,并且把它解压到你的硬盘的某个目录下面去。

  一旦你解压完这些资源以后,把Backgrounds,Fonts,Particles,Sounds和Spritesheets文件夹拖到Resouces分组下面去。(基本上,除了Classes文件夹以外,其它所有的文件压都拖到Resource目录下面去)

  确保 “Copy items into destination group’s folder (if needed)”被复选中,然后点击Finish。

  当你做完这些事之后,你的工程的分组看起来会是下图所示的样子:

  如果你很好奇,你可以随便看看你刚刚向工程里面添加进去了一些什么东西。下面是完整的内容列表:

  • Backgrounds: 一些背景图片,你等下会使用它们来制作一个滚动背景。里面包含星系,太阳,和空间异常(它移动速度比较慢),还有一组空间尘埃图片(它们会出现在背景前面,而且会移动地稍微快一点)
  • Fonts: 使用 Glyph Designer制作的位图字体,我们将使用这些字体来在游戏中显示文字。
  • Particles: 使用  Particle Designer制作的一些特殊的粒子效果。 在这里,我们用来创建星星飞动的效果。
  • Sounds: 一些与太空相关的背景音乐和音效。使用 Garage Band 和 cxfr制作的。
  • Spritesheets: 一张格式为pvr.ccz的大图片,里面包含了游戏中将要用到的许多小图片,比如陨石,太空船等。这个文件使用 Texture Packer制作的---如果你想使用pvr.ccz文件格式的话,你可能就需要使用这个工具。当然pvr.ccz格式的优点就是文件小,加载速度快。

  如果你还没安装上面任何一款工具的话,也不用担心!对于这个教程来说,你完全不需要他们,你可以使用我已经制作好的这些资源就够了。以后,如果有条件,你可以再去试试上面提到的工具。

  下面就是Sprites.pvr.ccz文件,它看起来如下图所示:

  你可能会奇怪,为什么要把所有的这些图片都弄成这样一张大图呢?因为,首先,它可以帮助节省内存,同时还可以提高性能。

  接下来,让我们开始coding吧!:)

添加一个太空船

  首先,让我们在屏幕上添加一艘太空船吧!

  打开HelloWorldLayer.h文件,然后在@interface里面添加两个实例变量:

CCSpriteBatchNode  * _batchNode;
CCSprite 
* _ship;

 

  第一个变量 (_batchNode)是必须的,因为我们将把所有的图片存储在一张图片里面,然后使用这个BatchNode就可以仅使用一次opengl调用来做所有的绘图操作。

  第二个变量(_ship)代表屏幕上的太空飞船。

  接下来,打开HelloWorldLayer.m,并且把init方法改成下面的样子:

- ( id ) init
{
if ( (self = [super init])) {

_batchNode 
=  [CCSpriteBatchNode batchNodeWithFile: @" Sprites.pvr.ccz " ];  //  1
[self addChild:_batchNode];  //  2
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile: @" Sprites.plist " ];  //  3

_ship 
=  [CCSprite spriteWithSpriteFrameName: @" SpaceFlier_sm_1.png " ];  //  4
CGSize winSize  =  [CCDirector sharedDirector].winSize;  //  5
_ship.position  =  ccp(winSize.width  * 0.1 , winSize.height  * 0.5 );  //  6
[_batchNode addChild:_ship z: 1 ];  //  7
}
return  self;
}

 

让我们一句一句地解释上面的代码:

  1. 使用一张大的图片创建一个CCSpriteBatchNode对象来批处理所有的对象的描绘操作。接收的参数是Sprites.pvr.ccz。
  2. 把CCSpriteBatchNode添加到当前层里面去,这样就可以绘制它的所有的孩子对象。
  3. 加载Sprites.plist文件,它里面包含了这张大图里面的所有的小图的位置坐标信息。这样,你以后可以非常方便地使用 spriteWithSpriteFrameName来提取一张张小图片来初使化一些精灵。
  4. 使用 SpaceFlier_sm_1.png图片来创建一个精灵,注意这张图片是大图里的一个子图。
  5. 使用CCDirector来获得屏幕的大小---我们接下来会用到这个大小。
  6. 设置飞船的位置在屏幕宽度的10%,高度的50处。注意,飞船的中心点位置默认是飞船的中心。
  7. 把ship当作batchNode的一个孩子添加进去,这样的话,这些精灵就会被批处理显示出来。

  编译并运行工程,你将会看到你的飞船图片出现在屏幕上面啦!

添加视差滚动

  我们已经有一个很酷的飞船在屏幕上了,但是,它看起来就好像坐在那里一样,毫无生气!我们可以通过往里面添加视差滚动背景来解决这个问题。

  但是,等一下,到底什么是视差滚动了?

  视差滚动,简单来说,就是“移动背景中的一些图片比其它图片慢一点点”,打个比方,一个背景中的物体有远有近,近的背景移动地快(比如地面),远的背景移动地慢(比如天空),这样子就会形成景深不一样的视差效果出来。

  想要在cocos2d里面使用视差滚动效果非常简单。你只需要做3步就ok了:

  1. 创建一个CCParallaxNode,然后把它加到层中去。
  2. 创建你想要滚动的元素,然后通过调用CCParallaxNode的 addChild:parallaxRatio:positionOffset方法把这些元素添加进去。
  3. 移动CCParallaxNode来滚动背景。这样的话,CCParallaxNode就会根据parallaxRatio的不同,或快或慢地移动它里面添加的元素了。

  让我们看看这个过程具体是怎样的。打开HelloWorldLayer.h,然后在@interface里面加入下面代码:

CCParallaxNode  * _backgroundNode;
CCSprite 
* _spacedust1;
CCSprite 
* _spacedust2;
CCSprite 
* _planetsunrise;
CCSprite 
* _galaxy;
CCSprite 
* _spacialanomaly;
CCSprite 
* _spacialanomaly2;

 

  然后,转到HelloWorldLayer.m文件,在init方法的底部加入下面的代码:

//  1) Create the CCParallaxNode
_backgroundNode  =  [CCParallaxNode node];
[self addChild:_backgroundNode z:
- 1 ];

//  2) Create the sprites we'll add to the CCParallaxNode
_spacedust1  =  [CCSprite spriteWithFile: @" bg_front_spacedust.png " ];
_spacedust2 
=  [CCSprite spriteWithFile: @" bg_front_spacedust.png " ];
_planetsunrise 
=  [CCSprite spriteWithFile: @" bg_planetsunrise.png " ];
_galaxy 
=  [CCSprite spriteWithFile: @" bg_galaxy.png " ];
_spacialanomaly 
=  [CCSprite spriteWithFile: @" bg_spacialanomaly.png " ];
_spacialanomaly2 
=  [CCSprite spriteWithFile: @" bg_spacialanomaly2.png " ];

//  3) Determine relative movement speeds for space dust and background
CGPoint dustSpeed  =  ccp( 0.1 0.1 );
CGPoint bgSpeed 
=  ccp( 0.05 0.05 );

//  4) Add children to CCParallaxNode
[_backgroundNode addChild:_spacedust1 z: 0  parallaxRatio:dustSpeed positionOffset:ccp( 0 ,winSize.height / 2 )];
[_backgroundNode addChild:_spacedust2 z:
0  parallaxRatio:dustSpeed positionOffset:ccp(_spacedust1.contentSize.width,winSize.height / 2 )]; 
[_backgroundNode addChild:_galaxy z:
- 1  parallaxRatio:bgSpeed positionOffset:ccp( 0 ,winSize.height  * 0.7 )];
[_backgroundNode addChild:_planetsunrise z:
- 1  parallaxRatio:bgSpeed positionOffset:ccp( 600 ,winSize.height  * 0 )]; 
[_backgroundNode addChild:_spacialanomaly z:
- 1  parallaxRatio:bgSpeed positionOffset:ccp( 900 ,winSize.height  * 0.3 )]; 
[_backgroundNode addChild:_spacialanomaly2 z:
- 1  parallaxRatio:bgSpeed positionOffset:ccp( 1500 ,winSize.height  * 0.9 )];

  编译并运行工程,你将会看到飞船背景了。

  然而,这还不是很有趣,因为还没有任何东西在动!

  为了移动太空尘埃和相关背景层,你只需要移动一样东西就可以了,那就是parallaxNode。对于移动parallax node的每一个y值,灰尘就会移动0.1y值,同时背景会移动0.05y值。

  为了移动parallax节点,你只需要飞一帧更新一下它的位置就可以了。打开HelloWorldLayer.m文件,加入下列的代码:(添加位置注意看注释)

//  Add to end of init method
[self scheduleUpdate];

//  Add new update method
-  ( void )update:(ccTime)dt {

CGPoint backgroundScrollVel 
=  ccp( - 1000 0 );
_backgroundNode.position 
=  ccpAdd(_backgroundNode.position, ccpMult(backgroundScrollVel, dt));

}

 

  编译并运行工程,你会看到,使用parallax来做视差滚动效果真是太简洁了!

  然后,运行几秒钟之后,你会发现一个问题:背景滚动完之后没有了!我们只得到了一个黑色的屏幕!那真是太糟糕了!好,接下来,看看我是怎么解决的吧!

连续地滚动

  我们想要背景保持无限地连续滚动效果。我们的做法就是,当背景移出屏幕的左边的时候,就马上把它移动到屏幕右边的适当的位置上去。

  这里有一个小小的问题,目前CCParallaxNode并不支持直接修改它的每个孩子的offset。你不能够简单地更新它的孩子的坐标点,因为CCParallaxNode每次更新的时候会覆盖那些改变。

  不过没关系,我已经制作了一个CCParallaxNode的分类,它可以用来解决这个问题,这个Category可以在项目的资源文件下面的Classes文件夹中找到。把 CCParallaxNode-Extras.h 和 CCParallaxNode-Extras.m拖到工程中去,同时确保 “Copy items into destination group’s folder”被复选中,然后点击Finish。

  然后,在HelloWorldLayer.m中做下列改变来实现连续滚动的效果:  

//  Add to top of file
#import " CCParallaxNode-Extras.h "

//  Add at end of your update method
NSArray  * spaceDusts  =  [NSArray arrayWithObjects:_spacedust1, _spacedust2, nil];
for  (CCSprite  * spaceDust  in  spaceDusts) {
if  ([_backgroundNode convertToWorldSpace:spaceDust.position].x  < - spaceDust.contentSize.width) {
[_backgroundNode incrementOffset:ccp(
2 * spaceDust.contentSize.width, 0 ) forChild:spaceDust];
}
}

NSArray 
* backgrounds  =  [NSArray arrayWithObjects:_planetsunrise, _galaxy, _spacialanomaly, _spacialanomaly2, nil];
for  (CCSprite  * background  in  backgrounds) {
if  ([_backgroundNode convertToWorldSpace:background.position].x  < - background.contentSize.width) {
[_backgroundNode incrementOffset:ccp(
2000 , 0 ) forChild:background];
}
}

 

  编译并运行工程,这时你可以看到有一个无限连续滚动的背景了!

添加星星

  没有哪一个太空射击游戏是没有星星在旁边飞的!

  同样的,我们也可以创建相应的星星图片,并且把它当作paralla节点的一个孩子添加进去。但是,这里我们不想这么做,因为星星是一个非常好的介绍粒子系统的例子。

  粒子系统能够让你使用同样的精灵创建大量的小对象,并且非常高效。cocos2d给了你很多配置粒子系统的参数, Particle Designer这个工具可以使这些参数的配置可视化。

  但是,这个教程里面,我们不会涉及如何调整粒子系统的各项参数,我们使用已经做好的星星的粒子效果。只需要在init方法中把它简单地添加进来即可:

NSArray  * starsArray  =  [NSArray arrayWithObjects: @" Stars1.plist " @" Stars2.plist " @" Stars3.plist " , nil];
for (NSString  * stars  in  starsArray) { 
CCParticleSystemQuad 
* starsEffect  =  [CCParticleSystemQuad particleWithFile:stars]; 
[self addChild:starsEffect z:
1 ];
}

 

  通过把粒子系统加到层中去,接着它会自动运行起来。编译并运行代码,你现在可以看到好多星星在屏幕上飞过了!:)

使用加速计来移动飞船

  到目前为止,还是很好,但是,如果我们不能控制飞船的移动的话,那就不是一个完整的游戏!

  这里,我们将采用加速计来移动太空飞船。当用户沿着x轴方向倾斜设备的时候,飞船就会上下移动。

  这个功能实际上非常容易实现。首先,在HelloWorldLayer.h里面的@interface里面添加一个成员变量,用来记录飞船沿着y轴方向每秒移动的点的个数。(这个点不一定等于一个实际的像素点,如果是Retina的设备,一个点=2个像素)

float  _shipPointsPerSecY;

 

  然后,在HelloWorldLayer.m中做如下修改:

//  1) Add to bottom of init
self.isAccelerometerEnabled  =  YES;

//  2) Add new method
-  ( void )accelerometer:(UIAccelerometer  * )accelerometer didAccelerate:(UIAcceleration  * )acceleration {

#define  kFilteringFactor 0.1
#define  kRestAccelX -0.6
#define  kShipMaxPointsPerSec (winSize.height*0.5) 
#define  kMaxDiffX 0.2

UIAccelerationValue rollingX, rollingY, rollingZ;

rollingX 
=  (acceleration.x  *  kFilteringFactor)  +  (rollingX  *  ( 1.0 -  kFilteringFactor)); 
rollingY 
=  (acceleration.y  *  kFilteringFactor)  +  (rollingY  *  ( 1.0 -  kFilteringFactor)); 
rollingZ 
=  (acceleration.z  *  kFilteringFactor)  +  (rollingZ  *  ( 1.0 -  kFilteringFactor));

float  accelX  =  acceleration.x  -  rollingX;
float  accelY  =  acceleration.y  -  rollingY;
float  accelZ  =  acceleration.z  -  rollingZ;

CGSize winSize 
=  [CCDirector sharedDirector].winSize;

float  accelDiff  =  accelX  -  kRestAccelX;
float  accelFraction  =  accelDiff  /  kMaxDiffX;
float  pointsPerSec  =  kShipMaxPointsPerSec  *  accelFraction;

_shipPointsPerSecY 
=  pointsPerSec;

}

//  4) Add to bottom of update
CGSize winSize  =  [CCDirector sharedDirector].winSize;
float  maxY  =  winSize.height  -  _ship.contentSize.height / 2 ;
float  minY  =  _ship.contentSize.height / 2 ;

float  newY  =  _ship.position.y  +  (_shipPointsPerSecY  *  dt);
newY 
=  MIN(MAX(newY, minY), maxY);
_ship.position 
=  ccp(_ship.position.x, newY);

 

让我们一点点剖析一下这段代码:

  1. 添加这行代码的作用是让当前的层可以接收到加速计移动事件,当有事件发生的时候,会回调 accelerometer:didAcccelerate这个方法。
  2. 这个方法的前面一部分是直接从Apple的样例代码中copy过来的,所做的事情其实是某种意义上的“滤波”。其实你也不用理解它的原理,基本上就是为了让飞船的移动更加平滑。如果你实在是对此非常感兴趣,可以查看这里来获得一些信息。不管怎么说,在运行完这个滤波之后,我们可以测试下看看,到底有哪些改进。通过实现证明,这种方法确实感觉不错!
  3. 基于每秒沿y轴方向移动的点数,来更新飞船的位置。同时要注意边界值的判断。

  编译并运行工程(一定要编译到真机上面,否则模拟器是没有加速计效果的,你不可能抱着电脑摇吧:))。这时,上下晃动真机,你可以操作你的飞船移动了!

添加陨石

  这个游戏目前看起来还不错,但是,危险和激情在哪里呢?让我们往场景中添加一些陨石吧!

  我们接下来将要采纳的方法其实是非常普遍的做法,我们在右边屏幕之后创建一些陨石,然后利用cocos2d的action把它移动到屏幕的左边去。

  我们可以在每次需要一个陨石的时候,马上创建一个对象,但是,分配内存的操作是非常慢的!所以你最好不要这样做!因此,我们可以预先分配好一堆陨石对象,这样当需要一个陨石对象的时候,就从中抓取一个就ok了。

  好,我们看看具体该怎么做吧。打开HelloWorldLayer.h,然后往类中添加下列成员变量:

CCArray  * _asteroids;
int  _nextAsteroid;
double  _nextAsteroidSpawn;

  接下来在HelloWorldLayer.m里面做如下修改:

//  Add to top of file
#define  kNumAsteroids 15

//  Add to bottom of init
_asteroids  =  [[CCArray alloc] initWithCapacity:kNumAsteroids];
for ( int  i  = 0 ; i  <  kNumAsteroids;  ++ i) {
CCSprite 
* asteroid  =  [CCSprite spriteWithSpriteFrameName: @" asteroid.png " ];
asteroid.visible 
=  NO;
[_batchNode addChild:asteroid];
[_asteroids addObject:asteroid];
}

//  Add new method, above update loop
-  ( float )randomValueBetween:( float )low andValue:( float )high {
return  ((( float ) arc4random()  / 0xFFFFFFFFu *  (high  -  low))  +  low;
}

//  Add to bottom of update loop
double  curTime  =  CACurrentMediaTime();
if  (curTime  >  _nextAsteroidSpawn) {

float  randSecs  =  [self randomValueBetween: 0.20  andValue: 1.0 ];
_nextAsteroidSpawn 
=  randSecs  +  curTime;

float  randY  =  [self randomValueBetween: 0.0  andValue:winSize.height];
float  randDuration  =  [self randomValueBetween: 2.0  andValue: 10.0 ];

CCSprite 
* asteroid  =  [_asteroids objectAtIndex:_nextAsteroid];
_nextAsteroid
++ ;
if  (_nextAsteroid  >=  _asteroids.count) _nextAsteroid  = 0 ;

[asteroid stopAllActions];
asteroid.position 
=  ccp(winSize.width + asteroid.contentSize.width / 2 , randY);
asteroid.visible 
=  YES;
[asteroid runAction:[CCSequence actions:
[CCMoveBy actionWithDuration:randDuration position:ccp(
- winSize.width - asteroid.contentSize.width,  0 )],
[CCCallFuncN actionWithTarget:self selector:@selector(setInvisible:)],
nil]];

}

//  Add new method
-  ( void )setInvisible:(CCNode  * )node {
node.visible 
=  NO;
}

 

关于上面的代码,有几点需要说明一下:

  • CCArray 和NSArray差不多,但是做了速度方面的优化。所以如果可以的话,尽可能多地使用CCArray。
  • 注意,我们添加了15个陨石到batchNode中去了。但是,把它们都设置成了不可见。如果是不可见的,就把它当前是未激活的。
  • 我们使用一个实例变量  (_nextAsteroidSpawn) 来指示下一个陨石出现的时间点。我们会在update循环中一直检测这个变量的值。
  • 如果你对cocos2d的action还很陌生的话,其实action就是一种很简单的,可以让精灵在一段时间做一些事情的对象。比如,你可以让精灵在指定的一段时间内,让它旋转、缩放、移动等等。这里,我们执行了2个action的序列:一个从右边移动到左边的action,还有一个是当前一个action结束时,调用另一个函数把陨石设置为不可见的action。这两个action的顺序是固定的。

  编译并运行代码,这时候你可以看见一些陨石在屏幕上飞过啦!

发射激光

  我不清楚你是怎么想的,当我看到屏幕上有陨石在飞的时候,我的第一感觉就是,把它们干掉!

  因此,让我们给飞船添加发射激光武器的功能吧!这段代码和我们之前添加陨石的代码有点类似,因为我们会创建一组可以重用的激光束,同时使用action来移动这些激光束。

  主要的区别就是,我们将使用touch事件来发射激光。

  首先打开HelloWorldLayer.h,然后在类中添加下面成员变量:

CCArray  * _shipLasers;
int  _nextShipLaser;

 

  然后在HelloWorldLayer.m做如下修改:

//  Add to top of file
#define  kNumLasers 5

//  Add to bottom of init
_shipLasers  =  [[CCArray alloc] initWithCapacity:kNumLasers];
for ( int  i  = 0 ; i  <  kNumLasers;  ++ i) {
CCSprite 
* shipLaser  =  [CCSprite spriteWithSpriteFrameName: @" laserbeam_blue.png " ];
shipLaser.visible 
=  NO;
[_batchNode addChild:shipLaser];
[_shipLasers addObject:shipLaser];
}

self.isTouchEnabled 
=  YES;

//  Add new method
-  ( void )ccTouchesBegan:(NSSet  * )touches withEvent:(UIEvent  * ) event  {

CGSize winSize 
=  [CCDirector sharedDirector].winSize;

CCSprite 
* shipLaser  =  [_shipLasers objectAtIndex:_nextShipLaser];
_nextShipLaser
++ ;
if  (_nextShipLaser  >=  _shipLasers.count) _nextShipLaser  = 0 ;

shipLaser.position 
=  ccpAdd(_ship.position, ccp(shipLaser.contentSize.width / 2 0 ));
shipLaser.visible 
=  YES;
[shipLaser stopAllActions];
[shipLaser runAction:[CCSequence actions:
[CCMoveBy actionWithDuration:
0.5  position:ccp(winSize.width,  0 )],
[CCCallFuncN actionWithTarget:self selector:@selector(setInvisible:)],
nil]];

}

 

  这个例子也向你展示了,在cocos2d里面接收touch事件是多么容易啊---仅需要把isTouchEnabled设置为yes就ok了。然后你需要实现ccTouchesBeban(或者ccTouchesMoved,ccTouchesEnded等等)。

  编译并运行代码,现在你可以发射激光武器了。

基本的碰撞检测

  恩,到目前为止,这看起来有点像一个游戏了,但是,还不够完整,因为没有爆炸!

  而且我天性不听话,喜欢搞破坏,所以是时候往游戏里面添加一些破坏啦!:)

  打开HelloWorldLayer.h,然后添加下面的实例变量:

int  _lives;

 

  然后update方法的最后面添加下面代码:

for  (CCSprite  * asteroid  in  _asteroids) { 
if  ( ! asteroid.visible)  continue ;

for  (CCSprite  * shipLaser  in  _shipLasers) { 
if  ( ! shipLaser.visible)  continue ;

if  (CGRectIntersectsRect(shipLaser.boundingBox, asteroid.boundingBox)) { 
shipLaser.visible 
=  NO;
asteroid.visible 
=  NO; 
continue ;
}
}

if  (CGRectIntersectsRect(_ship.boundingBox, asteroid.boundingBox)) {
asteroid.visible 
=  NO;
[_ship runAction:[CCBlink actionWithDuration:
1.0  blinks: 9 ]]; 
_lives
-- ;
}
}

 

  这里使用了最最简单的方式,只是判断两个精灵的边界矩形是否有交集。注意,边界部分可能有透明,而且边界矩形不能反应精灵实例的轮廓,所以最好的做法是使用前面介绍的box2d的方法。不过没关系,这个游戏,我们这样做就可以了。

  对于更好的使用box2d的方法,请查看《如何只使用box2d来做碰撞检测》。

  编译并运行代码,现在你会看到有东西爆炸啦!

胜利/失败条件检测

  我们差不多快做完了---现在只需要往游戏中添加判断游戏胜利或者失败的条件就可以了。

  我是这样考虑的,只要玩家存活了30秒,就是胜利;如果被陨石打中了3次,那么就是失败。

  因此,在HelloWorldLayer.h中做如下修改:

//  Add before @interface
typedef  enum  {
kEndReasonWin,
kEndReasonLose
} EndReason;

//  Add inside @interface
double  _gameOverTime;
bool  _gameOver;

 

  同时,相应地修改HelloWorldLayer.m:

//  Add at end of init
_lives  = 3 ;
double  curTime  =  CACurrentMediaTime();
_gameOverTime 
=  curTime  + 30.0 ;

//  Add at end of update loop
if  (_lives  <= 0 ) {
[_ship stopAllActions];
_ship.visible 
=  FALSE;
[self endScene:kEndReasonLose];
else if  (curTime  >=  _gameOverTime) {
[self endScene:kEndReasonWin];
}

//  Add new methods above update
-  ( void )restartTapped:( id )sender {
[[CCDirector sharedDirector] replaceScene:[CCTransitionZoomFlipX transitionWithDuration:
0.5  scene:[HelloWorldLayer scene]]]; 
}

-  ( void )endScene:(EndReason)endReason {

if  (_gameOver)  return ;
_gameOver 
= true ;

CGSize winSize 
=  [CCDirector sharedDirector].winSize;

NSString 
* message;
if  (endReason  ==  kEndReasonWin) {
message 
= @" You win! " ;
else if  (endReason  ==  kEndReasonLose) {
message 
= @" You lose! " ;
}

CCLabelBMFont 
* label;
if  (UI_USER_INTERFACE_IDIOM()  ==  UIUserInterfaceIdiomPad) {
label 
=  [CCLabelBMFont labelWithString:message fntFile: @" Arial-hd.fnt " ];
else  {
label 
=  [CCLabelBMFont labelWithString:message fntFile: @" Arial.fnt " ];
}
label.scale 
= 0.1 ;
label.position 
=  ccp(winSize.width / 2 , winSize.height  * 0.6 );
[self addChild:label];

CCLabelBMFont 
* restartLabel;
if  (UI_USER_INTERFACE_IDIOM()  ==  UIUserInterfaceIdiomPad) {
restartLabel 
=  [CCLabelBMFont labelWithString: @" Restart "  fntFile: @" Arial-hd.fnt " ]; 
else  {
restartLabel 
=  [CCLabelBMFont labelWithString: @" Restart "  fntFile: @" Arial.fnt " ]; 
}

CCMenuItemLabel 
* restartItem  =  [CCMenuItemLabel itemWithLabel:restartLabel target:self selector:@selector(restartTapped:)];
restartItem.scale 
= 0.1 ;
restartItem.position 
=  ccp(winSize.width / 2 , winSize.height  * 0.4 );

CCMenu 
* menu  =  [CCMenu menuWithItems:restartItem, nil];
menu.position 
=  CGPointZero;
[self addChild:menu];

[restartItem runAction:[CCScaleTo actionWithDuration:
0.5  scale: 1.0 ]];
[label runAction:[CCScaleTo actionWithDuration:
0.5  scale: 1.0 ]];

}

 

  如果你不理解endScene方法的话,也没关系---那些代码是我过去用来快速地判断游戏胜利或失败的方式。

  真正重要的是,你要理解其它部分的代码---在每一个update循环中,你只检测玩家是否胜利或失败,然后相应地调那个方法就可以了。

  编译并运行代码,看看你会不会输?

免费的音乐和音效

  你懂的,我怎么可能不给你们提供一些很棒的音效和音效呢?

  之前,你已经把相关的音乐和音效加到工程里面来了,因此,我们只需要在HelloWorldLayer.m中做如下修改即可:

//  Add to top of file
#import " SimpleAudioEngine.h "

//  Add to bottom of init
[[SimpleAudioEngine sharedEngine] playBackgroundMusic: @" SpaceGame.caf "  loop:YES];
[[SimpleAudioEngine sharedEngine] preloadEffect:
@" explosion_large.caf " ];
[[SimpleAudioEngine sharedEngine] preloadEffect:
@" laser_ship.caf " ];

//  Add inside BOTH CGRectIntersectsRect tests
[[SimpleAudioEngine sharedEngine] playEffect: @" explosion_large.caf " ];

//  Add inside ccTouchBegan
[[SimpleAudioEngine sharedEngine] playEffect: @" laser_ship.caf " ];

  好了!编译并运行,这就是一个我们从头至尾一步步开发出来的完整的游戏啦!:)

何去何从?

  这里有本项目的完整源代码。

  到目前为止,除了网络编程的教程没有翻译外,其它的cocos2d和box2d相关的教程全部翻译完了。:)以后,随着原作者的博客更新,我也会相应地更新。当然时间上肯定会落后不少,所以我希望大家没事的时候,还是多看看e文版。google,金山词霸等工具都用上,看着看着,就能看懂了。

  最后还是那句话,本人水平有限,翻译不准确的地方,请见谅!


推荐阅读
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • Google在I/O开发者大会详细介绍Android N系统的更新和安全性提升
    Google在2016年的I/O开发者大会上详细介绍了Android N系统的更新和安全性提升。Android N系统在安全方面支持无缝升级更新和修补漏洞,引入了基于文件的数据加密系统和移动版本的Chrome浏览器可以识别恶意网站等新的安全机制。在性能方面,Android N内置了先进的图形处理系统Vulkan,加入了JIT编译器以提高安装效率和减少应用程序的占用空间。此外,Android N还具有自动关闭长时间未使用的后台应用程序来释放系统资源的机制。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
  • macOS Big Sur全新设计大版本更新,10+个值得关注的新功能
    本文介绍了Apple发布的新一代操作系统macOS Big Sur,该系统采用全新的界面设计,包括图标、应用界面、程序坞和菜单栏等方面的变化。新系统还增加了通知中心、桌面小组件、强化的Safari浏览器以及隐私保护等多项功能。文章指出,macOS Big Sur的设计与iPadOS越来越接近,结合了去年iPadOS对鼠标的完善等功能。 ... [详细]
  • 本文介绍了JavaScript进化到TypeScript的历史和背景,解释了TypeScript相对于JavaScript的优势和特点。作者分享了自己对TypeScript的观察和认识,并提到了在项目开发中使用TypeScript的好处。最后,作者表示对TypeScript进行尝试和探索的态度。 ... [详细]
  • 原因:在vm上装7.9到sp3,打补丁到1644,再往后别打了,就打就打不开终端了,这个是java安全问题,以下只是终端的一些命令,记录一下:在系统下查看当前的串口:lsdev-C ... [详细]
  • iOS 苹果开发证书失效的解决方案(Failed to locate or generate matching signing assets)
    从2月14日开始,上传程序的同学可能会遇到提示上传失败的提示.并且打开自己的钥匙串,发现所有的证书全部都显示此证书签发者无效.出现以下情况:Failedtolocateorgene ... [详细]
  • iOS Xcode汇编模式切换的方法介绍
    一、概念 1.汇编指令:模拟器上运行的是Intel指令,而真机上运行的是arm指令, 2.每条汇编指令的格式总是由: 操作码,操作 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了iOS开发--漫谈内存管理相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
author-avatar
别禳莴觞芯_737
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有