Cocos2d-x制作跨平台太空射击游戏

http://www.cocoachina.com/gamedev/gameengine/2012/0502/4210.html

 

Cocos2d-x制作跨平台太空射击游戏

在这篇教程里,我将向你展示如何利用 How to Make a Space Shooter iPhone Game里创建的工程制作一个太空游戏。

这里有一个主要的区别——这次是用跨平台的cocs2d-x开发。

这就意味在教程里开发的游戏可以在你的iphone和andorid上运行。当然,再稍微做修改,你可以让它在Windows、Linux或者Mac下运行。

这篇教程基于我们在《COCOS2D-X跨ANDROID&IOS平台开发入门教程PART-1》里所创建的内容。如果你还没有准备好,那么最好先弄懂前面的工程,然后再继续。

深吸一口气,我们就要开始啦!


Getting Started

第一件事情就是下载并解压 space game resources ZIP file

就像我们在之前教程里面添加两个项目里都可以使用的C++类一样,我们需要以同样的方式来添加这些资源文件使ios和andriod项目都可以引用同样的资源。

我们把这些文件添加到Android工程的Resource目录下面,然后在ios项目里面引用这个目录。

为了方便添加图片和其它资源到我们的工程里,我们需要把它们添加到 $PROJECT_HOME\Resources目录(请记住 $PROJECT_HOME是你Andriod Cocosd-X project-samplecocos2dxandroid的位置)。然而,我们的Eclipse工程只会显示$PROJECT_HOME\android目录下的文件,所以这确实是一个问题!

幸运的是这里有一个简单的变通方案:我们在$PROJECT_HOME\Resources 目录下面建立一个符号链接,指向$PROJECT_HOME\android\Resources目录,这样Eclipse就可以看到它们了。

接着,打开终端,在$PROJECT_HOME\android目录下运行如下的命令:

  1. ln -s ../Resources ./Resources. 

现在拷贝文件到Resources文件夹。请注意,由于跨平台可移植性的原因,你需要避免使用层级式的子目录。尽管子目录在iOS下运行起来很好,但是它们不一定在Android上运行地很好。举例来说,如果你有一个Sprites.pvr.ccz文件在一个SpriteSheet子目录里,在Android里面使用CCSpriteBatchNode::bathNodeWithFile方法将会调用失败并返回一个空指针。

所以,从 space game resources ZIP file里面把单个的文件拷贝到Resource文件夹下去,请记得不要创建任何子目录,仅仅拷贝一个个的文件过来就可以了。在资源文件里存在一个字体的子文件夹,从字体文件夹里把所有的文件拷贝到Resources里时,直接替换就可以了。此外,在压缩文件里有个Classes子文件夹,你不必把它添加到Resources目录下,把它删除就行了。当所有的准备工作完成后就会是下面的样子:

Cocos2d-x制作跨平台太空射击游戏

接下来,让我们在iOS工程里引用这些文件,打开你的Xcode工程,创建一个新的Group叫做SharedResources。选择新的Group,在Inspector里点击按钮选择路径,然后把你在Android工程里的资源文件夹选择上。

Cocos2d-x制作跨平台太空射击游戏

右键点击SharedResources组,选择添加文件,从Android文件夹里添加所有的文件。目前,你完成所有的项目配置了!

 

增加一个太空飞船

让我们试试,看是否工作!打开Classes\HelloWorldScene.h,在HelloWorld类开头加入下面代码(在已有public:行的上面)

  1. private: 
  2.  
  3.     cocos2d::CCSpriteBatchNode * _batchNode; 
  4.  
  5.     cocos2d::CCSprite * _ship; 

上面的代码创建了两个私有实例变量 – 一个是sprite batch node,一个是太空飞船精灵

现在切换到HelloWorldScene.cpp,在init方法里,删除从注释“2. add a menu item”到方法最后的所有代码,加入下面代码:

  1. _batchNode = CCSpriteBatchNode::batchNodeWithFile("Sprites.pvr.ccz"); 
  2.  
  3. this->addChild(_batchNode); 
  4.  
  5. CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("Sprites.plist"); 
  6.  
  7. _ship = CCSprite::spriteWithSpriteFrameName("SpaceFlier_sm_1.png"); 
  8.  
  9. CCSize winSize = CCDirector::sharedDirector()->getWinSize(); 
  10.  
  11. _ship->setPosition(ccp(winSize.width * 0.1, winSize.height * 0.5)); 
  12.  
  13. _batchNode->addChild(_ship, 1); 
  14.  
  15. return true; 

注意这些代码与你过去使用的Objective-C版的cocos2d 非常类似。API是有很多相同的地方的,仅仅是有一些与C++的语法不同。

在Android模拟器中编译运行,你应该可以看到你的船出现在屏幕上

Cocos2d-x制作跨平台太空射击游戏

最妙的事情是在iOS上也能运行

Cocos2d-x制作跨平台太空射击游戏

 

增加视差滚动

接下来,我们会加入宇宙背景,使它以视差滚动这种很酷的方式来滚动。

首先,我们不得不在所有的类名前面加上cocos2d::这个名字空间,这太烦人了!所以在HelloWorldScene.h类声明前加入下面行:

  1. USING_NS_CC ; 

接着在HelloWorld的private部分加入一些新的变量(注意我们不再需要加cocos2d前缀):

  1. CCParallaxNode *_backgroundNode; 
  2.  
  3. CCSprite *_spacedust1; 
  4.  
  5. CCSprite *_spacedust2; 
  6.  
  7. CCSprite *_planetsunrise; 
  8.  
  9. CCSprite *_galaxy; 
  10.  
  11. CCSprite *_spacialanomaly; 
  12.  
  13. CCSprite *_spacialanomaly2; 

然后,在HelloWorldScene.cpp的init方法中,return语句前加入下面代码:

  1. // 1) Create the CCParallaxNode 
  2.  
  3. _backgroundNode = CCParallaxNode::node() ; //1 
  4.  
  5. this->addChild(_backgroundNode,-1) ; 
  6.  
  7. // 2) Create the sprites we'll add to the CCParallaxNode 
  8.  
  9. _spacedust1 = CCSprite::spriteWithFile("bg_front_spacedust.png"); 
  10.  
  11. _spacedust2 = CCSprite::spriteWithFile("bg_front_spacedust.png"); 
  12.  
  13. _planetsunrise = CCSprite::spriteWithFile("bg_planetsunrise.png"); 
  14.  
  15. _galaxy = CCSprite::spriteWithFile("bg_galaxy.png"); 
  16.  
  17. _spacialanomaly = CCSprite::spriteWithFile("bg_spacialanomaly.png"); 
  18.  
  19. _spacialanomaly2 = CCSprite::spriteWithFile("bg_spacialanomaly2.png");  
  20.  
  21. // 3) Determine relative movement speeds for space dust and background 
  22.  
  23. CCPoint dustSpeed = ccp(0.1, 0.1); 
  24.  
  25. CCPoint bgSpeed = ccp(0.05, 0.05); 
  26.  
  27. // 4) Add children to CCParallaxNode 
  28.  
  29. _backgroundNode->addChild(_spacedust1, 0 , dustSpeed , ccp(0,winSize.height/2) ); // 2 
  30.  
  31. _backgroundNode->addChild(_spacedust2, 0 , dustSpeed , ccp( _spacedust1->getContentSize().width,winSize.height/2)); 
  32.  
  33. _backgroundNode->addChild(_galaxy,-1, bgSpeed , ccp(0,winSize.height * 0.7)); 
  34.  
  35. _backgroundNode->addChild(_planetsunrise,-1 , bgSpeed,ccp(600,winSize.height * 0)); 
  36.  
  37. _backgroundNode->addChild(_spacialanomaly,-1, bgSpeed,ccp(900,winSize.height * 0.3)); 
  38.  
  39. _backgroundNode->addChild(_spacialanomaly2,-1, bgSpeed,ccp(1500,winSize.height * 0.9)); 

再一次说明,这段代码与我们的《如何使用cocos2d制作一个太空射击游戏》非常类似,只是有些小的句法变化。你可以比较一下2个教程在句法上的不同。

Android模拟器中编译运行,你应该可以看到一个宇宙场景的启动

Cocos2d-x制作跨平台太空射击游戏

同样的,也能在iphone上运行

Cocos2d-x制作跨平台太空射击游戏

现在使背景滚动,在HelloWorldScene.h中预先声明update方法 – 你可以在private或public部分加入下面代码,但是既然update方法是内部使用的,所以作为一个private方法更恰当:

  1. // scheduled Update 
  2.  
  3. void update(cocos2d::ccTime dt); 

然后在HelloWorldScene.cpp 最后加入下面的方法实现

  1. void HelloWorld::update(ccTime dt) { 
  2.  
  3.   CCPoint backgroundScrollVert = ccp(-1000,0) ; 
  4.  
  5.   _backgroundNode->setPosition(ccpAdd(_backgroundNode->getPosition(),ccpMult(backgroundScrollVert,dt))) ; 
  6.  

最后,在init方法末尾(但是在最后的return语句前)调用scheduleUpdate方法

  1. this->scheduleUpdate(); 

编译运行,你将会看到背景滚动!

 

持续滚动

这时,你应该注意到了背景滚出屏幕后没有循环,那么我们来修这个bug

在我们的 《如何使用cocos2d制作一个太空射击游戏》教程中,我们通过Objective-c的分类(category)扩展了CCParallaxNode类来实现。

不幸的是,C++中是不存在分类的,所以我们需要借助继承来实现之。

我们将去定义一个CCParallaxNode扩展类来扩展标准的CCParallaNode。这样做虽然不如Objective-C优雅,但是有时我们需要为软件可移植性做一些牺牲。

在Xcode中,在Glasses group上单击右键,选择New File。选择iOS\C and C++\C++文件末尾,点击Next,为他命名为CCParallaxNodeExtras.cpp,存储到$PROJECT_HOME\Classes,然后点击创建。然后重复上述过程,但要选择iOS\C and C++\Header文件模板,来创建CCParallaxNodeExtras.h

用下面的代码替换CCParallaxNodeExtras.h中的内容

  1. #ifndef 
  2.  
  3. Cocos2DxFirstIosSample_CCParallaxNodeExtras_h #define 
  4.  
  5. Cocos2DxFirstIosSample_CCParallaxNodeExtras_h #include “cocos2d.h” USING_NS_CC; class CCParallaxNodeExtras : public CCParallaxNode 
  6.  
  7. { public : // Need to provide our own constructor CCParallaxNodeExtras() ; // just to avoid ugly later cast and also for safety 
  8.  
  9. static CCParallaxNodeExtras * node() ; // Facility method (we expect to have it soon in COCOS2DX) void incrementOffset(CCPoint 
  10.  
  11. offset,CCNode* node) ; } ; #endif 

这里我们为CCParallaxNode添加一个新的方法(incrementOffset),我们用它来更新parallax node 每个 child 的位置。当背景移动出屏幕左端的时候我们会使用它来将背景放到屏幕的右端,来实现持续的滚动。 将下面的代码替换到CCParallaxNodeExtras.cpp

  1. #include "CCParallaxNodeExtras.h"  
  2.  
  3. // Hack to access CCPointObject (which is not a public class) 
  4.  
  5. class CCPointObject  : CCObject { 
  6.  
  7.     CC_SYNTHESIZE(CCPoint, m_tRatio, Ratio) 
  8.  
  9.     CC_SYNTHESIZE(CCPoint, m_tOffset, Offset) 
  10.  
  11.     CC_SYNTHESIZE(CCNode *,m_pChild, Child) // weak ref 
  12.  
  13. }; 
  14.  
  15. // Need to provide our own constructor 
  16.  
  17. CCParallaxNodeExtras::CCParallaxNodeExtras() { 
  18.  
  19.     CCParallaxNode::CCParallaxNode() ; // call parent constructor 
  20.  
  21.  
  22. CCParallaxNodeExtras * CCParallaxNodeExtras::node() { 
  23.  
  24.     return new CCParallaxNodeExtras() ; 
  25.  
  26.  
  27. void CCParallaxNodeExtras::incrementOffset(CCPoint offset,CCNode* node){ 
  28.  
  29.     for( unsigned int i=0;i < m_pParallaxArray->num;i++) { 
  30.  
  31.         CCPointObject *point = (CCPointObject *)m_pParallaxArray->arr[i]; 
  32.  
  33.         CCNode * curNode = point->getChild() ; 
  34.  
  35.         if( curNode->isEqual(node) ) { 
  36.  
  37.             point->setOffset( ccpAdd(point->getOffset(), offset) ); 
  38.  
  39.             break; 
  40.  
  41.         } 
  42.  
  43.     } 
  44.  

注意,很不幸的是CCPointObject在Cocos2d中并不是公共的类,所以我们需要借住一些小手段来hack。(重定义它为我们的类,并具有相同的签名)。

虽然它能工作的很好,但是他的缺点是,如果CCPointObject改动了,你这里也要跟着改动,否则程序会崩溃。

代码的重点是incrementOffset方法,他和 《如何使用cocos2d制作一个太空射击游戏》中的实现相同,只是用了不同的语言。

下一步,选择HelloWorldScene.h,在文件顶部的#include 语句之后添加这些代码:

  1. #include "CCParallaxNodeExtras.h" 

然后像下面的代码一样,将private区域中_backgroundNode定义由CCParallaNode改为CCParallaxNodeExtras

  1. CCParallaxNodeExtras *_backgroundNode; 

然后选择HelloWorldScene.cpp 用下面的代码替换掉section #1(_backgroundNode创建的地方)的第一行。

  1. _backgroundNode = CCParallaxNodeExtras::node() ; 

最后,添加下面的代码,到update 方法的底部。

  1. CCArray *spaceDusts = CCArray::arrayWithCapacity(2) ; 
  2.  
  3. spaceDusts->addObject(_spacedust1) ; 
  4.  
  5. spaceDusts->addObject(_spacedust2) ; 
  6.  
  7. for ( int ii = 0  ; ii count() ; ii++ ) { 
  8.  
  9.     CCSprite * spaceDust = (CCSprite *)(spaceDusts->objectAtIndex(ii)) ; 
  10.  
  11.     float xPosition = _backgroundNode->convertToWorldSpace(spaceDust->getPosition()).x  ; 
  12.  
  13.     float size = spaceDust->getContentSize().width ; 
  14.  
  15.     if ( xPosition < -size ) {         _backgroundNode->incrementOffset(ccp(spaceDust->getContentSize().width*2,0),spaceDust) ; 
  16.  
  17.     } 
  18.  
  19.  
  20. CCArray *backGrounds = CCArray::arrayWithCapacity(4) ; 
  21.  
  22. backGrounds->addObject(_galaxy) ; 
  23.  
  24. backGrounds->addObject(_planetsunrise) ; 
  25.  
  26. backGrounds->addObject(_spacialanomaly) ; 
  27.  
  28. backGrounds->addObject(_spacialanomaly2) ; 
  29.  
  30. for ( int ii = 0 ; ii count() ; ii++ ) { 
  31.  
  32.     CCSprite * background = (CCSprite *)(backGrounds->objectAtIndex(ii)) ; 
  33.  
  34.     float xPosition = _backgroundNode->convertToWorldSpace(background->getPosition()).x ; 
  35.  
  36.     float size = background->getContentSize().width ; 
  37.  
  38.     if ( xPosition < -size ) {         _backgroundNode->incrementOffset(ccp(2000,0),background) ; 
  39.  
  40.     } 
  41.  

你可以看到,NSArray的替代品是基于STL (C++标准库) 实现的CCArray。我们用可以帮我们自动释放对象的arrayWithCapacity构造函数。注意这里同样也有一个更先进的CCMutableArray来用于存储集合元素。

最后的修改-因为你添加了新文件,你需要把它添加到android工程的Makefile来让他编译正确。于是用Eclipse打开Classes\Android.mk然后改动行LOCAL_SRC_FILES为下面的代码。

  1. LOCAL_SRC_FILES :AppDelegate.cpp \ 
  2.  
  3.                    HelloWorldScene.cpp \ 
  4.  
  5.                    CCParallaxNodeExtras.cpp 

编译运行,现在背景可以无限滚动了!

 

添加星星

添加星星是非常简单的,只是添加下面的代码到HelloWorldScene.cpp的init方法的return之前。

  1. HelloWorld::addChild(CCParticleSystemQuad::particleWithFile("Stars1.plist")) ; 
  2.  
  3. HelloWorld::addChild(CCParticleSystemQuad::particleWithFile("Stars2.plist")) ; 
  4.  
  5. HelloWorld::addChild(CCParticleSystemQuad::particleWithFile("Stars3.plist")) ; 

编译运行,漂亮!他已经开始看起来像一个太空游戏了。

Cocos2d-x制作跨平台太空射击游戏
 
Cocos2d-x制作跨平台太空射击游戏

 

使用加速计来移动飞船

在之前的Cocos2D space shooter tutorial里面,我们使用ios的加速计api来检测加速计输入。很明显,ios下面的加速计api是不可以跨平台的,那么我们怎么办呢?

幸运的是,cocos2d-x对加速计进行了封装,我们可以不用关心具体平台api,直接使用抽象后的加速计api就可以了。让我们看看它是怎么工作的吧。

首先,在HelloWorldScnee.H头文件里面添加一个新的私有成员变量:

  1. float _shipPointsPerSecY; 

然后,添加一个新的public方法声明:

  1. virtual void didAccelerate(CCAcceleration* pAccelerationValue); 

然后在HelloWorldScene.cpp的init方法的return语句之前添加下列代码:

  1. this->setIsAccelerometerEnabled(true); 

接下来,在HelloWorldScene.CPP文件底部添加下面这些新方法定义:

  1. void HelloWorld::didAccelerate(CCAcceleration* pAccelerationValue) { 
  2.  
  3. #define KFILTERINGFACTOR 0.1 
  4.  
  5. #define KRESTACCELX -0.6 
  6.  
  7. #define KSHIPMAXPOINTSPERSEC (winSize.height*0.5) 
  8.  
  9. #define KMAXDIFFX 0.2 
  10.  
  11.    double rollingX ; 
  12.  
  13.    // Cocos2DX inverts X and Y accelerometer depending on device orientation 
  14.  
  15.   // in landscape mode right x=-y and y=x !!! (Strange and confusing choice) 
  16.  
  17.   pAccelerationValue->x = pAccelerationValue->y ; 
  18.  
  19.   rollingX = (pAccelerationValue->x * KFILTERINGFACTOR) + (rollingX * (1.0 - KFILTERINGFACTOR)); 
  20.  
  21.   float accelX = pAccelerationValue->x - rollingX ; 
  22.  
  23.   CCSize winSize = CCDirector::sharedDirector()->getWinSize(); 
  24.  
  25.   float accelDiff = accelX - KRESTACCELX; 
  26.  
  27.   float accelFraction = accelDiff / KMAXDIFFX; 
  28.  
  29.   _shipPointsPerSecY = KSHIPMAXPOINTSPERSEC * accelFraction; 
  30.  

最后,在update方法的底部添加如下代码:

  1. CCSize winSize = CCDirector::sharedDirector()->getWinSize(); 
  2.  
  3. float maxY = winSize.height - _ship->getContentSize().height/2; 
  4.  
  5. float minY = _ship->getContentSize().height/2; 
  6.  
  7. float diff = (_shipPointsPerSecY * dt) ; 
  8.  
  9. float newY = _ship->getPosition().y + diff; 
  10.  
  11. newY = MIN(MAX(newY, minY), maxY); 
  12.  
  13. _ship->setPosition(ccp(_ship->getPosition().x, newY)); 

这里的didAccelerate回调函数包含一个CCAcceleration对象,它包含加速计的x、y和z三个方向的数据。我们目前只需要使用x方向的加速计数据就行了,因为我们是沿着设备的x轴进行运动的。

注意: cocos2d-x会根据你的设备是处于portait模式还是landscape模式来切换加速计的x和y方向的值。

如果是Landscape right(也就是我们目前的情况),接收到的x值其实是-y,而y值是x。如果是Landscape left那么接收到的x值是y,而y值是-x。

有点头晕了?呵呵

编译,然后在你的iphone和android设备上测试一下吧,现在你可以倾斜你的设备来移动飞船啦!当然,此时,你不能在模拟器上进行测试,必须使用真机。

 

添加陨石

是时候给游戏添加一些陨石了!首先在 HelloWorldScene.h里面添加下面一些实例变量:

  1. CCMutableArray *  _asteroids ; 
  2.  
  3. int _nextAsteroid ; 
  4.  
  5. float _nextAsteroidSpawn ; 

然后,添加一些新的公有方法声明:

  1. float randomValueBetween( float low , float high ); 
  2.  
  3. void setInvisible(CCNode * node); 
  4.  
  5. float getTimeTick(); 

接下来,回到HelloWorldScene.CPP,然后在文件最后添加刚刚的那些辅助方法的实现:

  1. float HelloWorld::randomValueBetween( float low , float high ) { 
  2.  
  3.   return (((float) arc4random() / 0xFFFFFFFFu) * (high - low)) + low; 
  4.  
  5.  
  6. float HelloWorld::getTimeTick() { 
  7.  
  8.   timeval time; 
  9.  
  10.   gettimeofday(&amp;time, NULL); 
  11.  
  12.   unsigned long millisecs = (time.tv_sec * 1000) + (time.tv_usec / 1000); 
  13.  
  14.   return (float) millisecs; 
  15.  

randomValueBetween 是一个可以获得指定范围内的随机浮点数的辅助方法。而 getTimeTick 是一种可移植的方式来得到毫秒级别的时间。

接下来,在init方法的最后创建一个陨石的数组:

  1. #define KNUMASTEROIDS 15 
  2.  
  3. _asteroids = new CCMutableArray(); 
  4.  
  5. for(int i = 0; i < KNUMASTEROIDS; ++i) {     CCSprite *asteroid = CCSprite::spriteWithSpriteFrameName("asteroid.png");      
  6.  
  7. asteroid->setIsVisible(false) ; 
  8.  
  9.     _batchNode->addChild(asteroid); 
  10.  
  11.     _asteroids->addObject(asteroid); 
  12.  

这里,我们使用cocos2d-x的CCMutableArray类来存储ccsprite的数组。注意,我们这里手动调用new操作符,而不是使用arrayWithCapacity来创建对象,这样可以避免使用autorelease机制。

最后,在update方法底部添加下列代码:

  1. float curTimeMillis = getTimeTick(); 
  2.  
  3. if (curTimeMillis > _nextAsteroidSpawn) { 
  4.  
  5.      float randMillisecs = randomValueBetween(0.20,1.0) * 1000; 
  6.  
  7.     _nextAsteroidSpawn = randMillisecs + curTimeMillis; 
  8.  
  9.      float randY = randomValueBetween(0.0,winSize.height); 
  10.  
  11.     float randDuration = randomValueBetween(2.0,10.0); 
  12.  
  13.      CCSprite *asteroid = _asteroids->getObjectAtIndex(_nextAsteroid); 
  14.  
  15.     _nextAsteroid++; 
  16.  
  17.      if (_nextAsteroid >= _asteroids->count()) 
  18.  
  19.         _nextAsteroid = 0
  20.  
  21.      asteroid->stopAllActions(); 
  22.  
  23.     asteroid->setPosition( ccp(winSize.width+asteroid->getContentSize().width/2, randY)); 
  24.  
  25.     asteroid->setIsVisible(true) ; 
  26.  
  27.     asteroid->runAction ( CCSequence::actions ( 
  28.  
  29.         CCMoveBy::actionWithDuration(randDuration,ccp(-winSize.width-asteroid->getContentSize().width,0)) , 
  30.  
  31.         CCCallFuncN::actionWithTarget(this,callfuncN_selector(HelloWorld::setInvisible)) , 
  32.  
  33.         NULL // DO NOT FORGET TO TERMINATE WITH NULL (unexpected in C++) 
  34.  
  35.         ) ) ; 
  36.  

再一次说明,这里的代码跟之前的cocos2d代码太像了!注意,这里的callfuncN_selector调用跟objc里面的selector机制太像了!

注意:由于CCSequence::actions方法的参数列表采用的是变长参数,你其实可以忽略掉最后一个NULL参数,因为对于c++来讲,这个参数没有意义。

但是,考虑到兼容性的原因,因为cocos2d-x开发者想保持跟cocos2d的高度一致,所以,这里需要一个NULL终止符。如果你在你的代码里面不提供的话,那么你的程序将会崩溃。

最后一步就是添加setInvisible回调函数的实现:

  1. void HelloWorld::setInvisible(CCNode * node) { 
  2.  
  3.   node->setIsVisible(false) ; 
  4.  

编译并运行,有陨石!下面是iphone3GS的运行效果截图:

Cocos2d-x制作跨平台太空射击游戏

下面是Android Samsung Galaxy S的运行效果截图:

Cocos2d-x制作跨平台太空射击游戏

 

发射激光

是时候让我们的飞船发射激光了!在HelloWorldScene.h里面添加下列私有成员变量:

  1. CCMutableArray *  _shipLasers ; 
  2.  
  3. int _nextShipLaser ; 

现在,添加一个新的公有方法声明:

  1. virtual void ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event); 

然后,在HelloWorldScene.cpp的init方法的return语句之前添加下列代码:

  1. #define KNUMLASERS 5 
  2.  
  3. _shipLasers = new CCMutableArray(); 
  4.  
  5. for(int i = 0; i < KNUMLASERS; ++i) {     CCSprite *shipLaser = CCSprite::spriteWithSpriteFrameName("laserbeam_blue.png");      
  6.  
  7. shipLaser->setIsVisible(false) ; 
  8.  
  9.     _batchNode->addChild(shipLaser); 
  10.  
  11.     _shipLasers->addObject(shipLaser); 
  12.  
  13.  
  14. this->setIsTouchEnabled(true) ; 

最后,在文件的底部实现ccTouchesBegan方法;

  1. void HelloWorld::ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event) 
  2.  
  3.  
  4.     CCSize winSize = CCDirector::sharedDirector()->getWinSize() ;  
  5.  
  6.   
  7.  
  8.     CCSprite *shipLaser = _shipLasers->getObjectAtIndex(_nextShipLaser++); 
  9.  
  10.     if ( _nextShipLaser >= _shipLasers->count() ) 
  11.  
  12.         _nextShipLaser = 0
  13.  
  14.     shipLaser->setPosition( ccpAdd( _ship->getPosition(), ccp(shipLaser->getContentSize().width/2, 0))); 
  15.  
  16.     shipLaser->setIsVisible(true) ; 
  17.  
  18.     shipLaser->stopAllActions() ; 
  19.  
  20.     shipLaser->runAction( CCSequence::actions ( 
  21.  
  22.         CCMoveBy::actionWithDuration(0.5,ccp(winSize.width, 0)), 
  23.  
  24.         CCCallFuncN::actionWithTarget(this,callfuncN_selector(HelloWorld::setInvisible)) , 
  25.  
  26.         NULL  // DO NOT FORGET TO TERMINATE WITH NULL 
  27.  
  28.         ) ) ; 
  29.  

编译并运行,现在你可以发射激光了!下面是iphone 3GS下面的运行截图:

Cocos2d-x制作跨平台太空射击游戏

下面是Android Samsung Galaxy S的运行截图:

Cocos2d-x制作跨平台太空射击游戏

 

简单的碰撞检测

接下来,我们想添加一些代码来检测激光和小行星间的碰撞,以及当小行星被撞击时爆炸。完成这项功能的代码和《如何使用cocos2d制作一个太空射击游戏》中的相当相似,但是用到了一些新的语法。

注意,我们会介绍C++迭代器来处理CCMutableArray。首先,在HelloWorldScene.h添加一个新的私有变量:

  1. int _lives ; 

接着,在HelloWorldScene.h的update函数的底部添加如下代码:

  1. // Asteroids 
  2.  
  3. CCMutableArray::CCMutableArrayIterator itAster , itLaser ; 
  4.  
  5. for ( itAster = _asteroids->begin() ; itAster != _asteroids->end() ; itAster++ ) { 
  6.  
  7.     CCSprite *asteroid = (CCSprite *)*itAster; 
  8.  
  9.     if ( ! asteroid->getIsVisible() ) 
  10.  
  11.         continue ; 
  12.  
  13.     for ( itLaser = _shipLasers->begin() ; itLaser != _shipLasers->end() ; itLaser++ ) { 
  14.  
  15.         CCSprite *shipLaser = (CCSprite *)*itLaser; 
  16.  
  17.         if ( ! shipLaser->getIsVisible() ) 
  18.  
  19.             continue ; 
  20.  
  21.         if ( CCRect::CCRectIntersectsRect(shipLaser->boundingBox(), asteroid->boundingBox()) ) { 
  22.  
  23.             shipLaser->setIsVisible(false) ; 
  24.  
  25.             asteroid->setIsVisible(false) ; 
  26.  
  27.             continue ; 
  28.  
  29.         } 
  30.  
  31.     } 
  32.  
  33.     if ( CCRect::CCRectIntersectsRect(_ship->boundingBox(), asteroid->boundingBox()) ) { 
  34.  
  35.         asteroid->setIsVisible(false) ; 
  36.  
  37.         _ship->runAction( CCBlink::actionWithDuration(1.0, 9)) ; 
  38.  
  39.         _lives-- ; 
  40.  
  41.     } 
  42.  

编译并运行,现在你可以使小行星爆炸了!当然,你会发现当激光碰撞到小行星,他们并没有爆炸就消失了。这是因为我们没有为爆炸添加粒子效果。之前我们已经将星星添加到了粒子系统中,根据最初的教程,把爆炸加入粒子系统将是相当简单的任务。把这个当成额外的课外作业吧!

 

胜利/失败检测

将胜利或失败的检测代码转换到Cocos2D-X也是直截了当的。

切换回HelloWorldScene.h并在类的声明前添加一个枚举变量:

  1. typedef enum { 
  2.  
  3.   KENDREASONWIN, 
  4.  
  5.   KENDREASONLOSE 
  6.  
  7. } EndReason; 

现在在HelloWorld类添加两个私有变量:

  1. double _gameOverTime ; 
  2.  
  3. bool _gameOver ; 

接下来,添加两个私有方法的声明;

  1. void endScene( EndReason endReason ) ; 
  2.  
  3. void restartTapped() ; 

接着,切换到HelloWorldScene.cpp并在init函数的return前添加如下代码:

  1. _lives = 3 ; 
  2.  
  3. double curTime = getTimeTick() ; 
  4.  
  5.  _gameOverTime = curTime + 30000 ; 

在update的结尾,添加如下代码:

  1. if ( _lives stopAllActions() ; 
  2.  
  3.     _ship->setIsVisible(false) ; 
  4.  
  5.     this->endScene(KENDREASONLOSE) ; 
  6.  
  7. } else if ( curTimeMillis >= _gameOverTime ) { 
  8.  
  9.     this->endScene(KENDREASONWIN) ; 
  10.  

最后,在文件的结尾添加新方法的实现:

  1. void HelloWorld::restartTapped() { 
  2.  
  3.   CCDirector::sharedDirector()->replaceScene 
  4.  
  5.      ( CCTransitionZoomFlipX::transitionWithDuration(0.5, this->scene())); 
  6.  
  7.   // reschedule 
  8.  
  9.   this->scheduleUpdate() ; 
  10.  
  11.  
  12. void HelloWorld::endScene( EndReason endReason ) { 
  13.  
  14.   if ( _gameOver ) 
  15.  
  16.     return ; 
  17.  
  18.   _gameOver = true ; 
  19.  
  20.    CCSize winSize = CCDirector::sharedDirector()->getWinSize() ; 
  21.  
  22.   char message[10] = "You Win"  ; 
  23.  
  24.   if ( endReason == KENDREASONLOSE ) 
  25.  
  26.     strcpy(message,"You Lose") ; 
  27.  
  28.   CCLabelBMFont * label ; 
  29.  
  30.   label = CCLabelBMFont::labelWithString(message ,"Arial.fnt" ); 
  31.  
  32.   label->setScale(0.1) ; 
  33.  
  34.   label->setPosition( ccp( winSize.width/2 , winSize.height*0.6)) ; 
  35.  
  36.   this->addChild(label) ;   
  37.  
  38.    CCLabelBMFont * restartLabel ; 
  39.  
  40.   restartLabel = CCLabelBMFont::labelWithString("Restart" ,"Arial.fnt" ); 
  41.  
  42.   CCMenuItemLabel *restartItem = CCMenuItemLabel::itemWithLabel(restartLabel, this, menu_selector(HelloWorld::restartTapped) ); 
  43.  
  44.   restartItem->setScale(0.1) ; 
  45.  
  46.   restartItem->setPosition( ccp( winSize.width/2 , winSize.height*0.4)) ; 
  47.  
  48.    CCMenu *menu = CCMenu::menuWithItems(restartItem, NULL); 
  49.  
  50.   menu->setPosition(CCPointZero); 
  51.  
  52.   this->addChild(menu) ; 
  53.  
  54.    // clear label and menu 
  55.  
  56.   restartItem->runAction( CCScaleTo::actionWithDuration(0.5, 1.0)) ; 
  57.  
  58.   label ->runAction( CCScaleTo::actionWithDuration(0.5, 1.0)) ; 
  59.  
  60.   // Terminate update callback 
  61.  
  62.   this->unscheduleUpdate() ; 
  63.  

注意最初的代码版本和Cocos2D-X代码版本的一个区别是我们不得不在游戏开始和结束时定时或取消定时对update的回调。

编译并运行,如果你的船撞击太多次就会死!

 

免费的音乐和音效

安卓不支持CAF格式的声音文件,所以我们要做的第一件事就是把原始声音文件替换为一种可在安卓上使用的格式。

打开终端,输入如下命令(不要忘记在第一个命令中用安卓项目的真实路径替换$PROJECT_HOME,否则命令将无法运行):

  1. cd $PROJECT_HOME/Resources 
  2.  
  3. afconvert -f WAVE -d UI8 SpaceGame.caf SpaceGame.wav 
  4.  
  5. afconvert -f WAVE -d UI8 explosion_large.caf explosion_large.wav 
  6.  
  7. afconvert -f WAVE -d UI8 laser_ship.caf laser_ship.wav 

就像你可能猜到的那样,上面的命令将把文件从CAF格式转换为WAV格式。

一旦你转换完文件,回到你的Xcode项目并将转化好的文件添加到你的Resources文件夹(从Android目录中)——同你之前添加图片和其他资源一样。

困难的部分已经完成,现在播放这些声音吧!在HelloWorldScene.cpp的顶部添加如下代码:

  1. #include "SimpleAudioEngine.h" 
  2.  
  3. using namespace CocosDenshion ; 

这里,我们导入了SimpleAudioEngine的头文件,同时声明了相应的名字空间。

下面的代码就很简单了,跟之前的教程没区别,只是语法表示上的差异。我们只需要在init方法的最后添加下列代码即可:

  1. SimpleAudioEngine::sharedEngine()->playBackgroundMusic("SpaceGame.wav",true) ; 
  2.  
  3. SimpleAudioEngine::sharedEngine()->preloadEffect("explosion_large.wav") ; 
  4.  
  5. SimpleAudioEngine::sharedEngine()->preloadEffect("laser_ship.wav") ; 

接下来,在update行星部分用CGRectIntersectsRect检测是否一束激光和行星相撞的地方添加如下代码

(当飞船和激光碰撞你也应该添加一种音效,这将在相同的模块进行下个CGRectIntersectsRect测试,但飞船不能爆炸,你可能想要设置另一种声音: )):

  1. SimpleAudioEngine::sharedEngine()->playEffect("explosion_large.wav") ; 

最后,在ccTouchesBegan函数添加如下代码:

  1. SimpleAudioEngine::sharedEngine()->playEffect("laser_ship.wav") ; 

编译并运行,享受这些音效吧!下面是运行在iPhone 3GS的最终的项目:

Cocos2d-x制作跨平台太空射击游戏

在安卓的Samsung Galaxy S中运行:

Cocos2d-x制作跨平台太空射击游戏

 

特定平台的错误和调整

Cocos 2D-X的开发者们已经做得很不错了,但是还不够完美,特别是在确保跨平台的兼容性上。如果你在各个平台上玩一下你的应用,你会发现它可以很流畅地运行在iOS平台上。但是,在Android上,你会发现它存在一下一些问题:

1. 当关闭或是从后台回到游戏里面时,通过CCParticelSystemQuad设置的sprites没有正确地被重绘,你看到的是一个空白的区域。这个算是Cocos2D-x 在Android上的一个问题。

2. 有时候在滚动的时可见区域的刷新过慢。

3. Cocos2D-x 在.11版本下,菜单选择会引起崩溃(幸好在.12版本的时候已经修复)

那么这些问题都是Cocos2D-X本身的一些问题,超出了本教程的范围(同时他们应该会在新的版本中修复这些,相信cocos2D-x团队会关注本教程的,所以大家拭目以待。Iven注)。

还有,你也许已经发现了星星只能显示在屏幕上有限的区域。这个是因为当粒子系统根据例子设计创建的时候,他们是假定根据iPhone屏幕的尺寸建立的。

要使他们在Android设备上正确工作,只有打开粒子系统,然后根据Android的屏幕尺寸进行编辑。大部分设备都要进行这样的设置:source pos Y720, variance 720, and sourcePos X 1088。然而,你也许还要根据你设备的屏幕尺寸,稍微调整一下这些设置。

 

为多个设备和分辨率开发

当你在iOS平台开发的时候,你只有两种屏幕比例:iPhone和iPad,同时每个有普通/高清两种分辨率。

然而,这对于Android来说要复杂的多,由于它有很多不同厂商生产的各种屏幕尺寸的设备。如果你有一个不同于iPhone屏幕尺寸的设备,那么游戏看上去会很不一样。但是不管用户用的是什么样的屏幕尺寸,我们都要让游戏看起来是OK的。

一般的方法是:可以基于屏幕的尺寸根据以下公式对图画位置进行变换:

  1. factor = min(targetWidth / DesignWidth, targetHeight / DesignHeight) 

当对不同屏幕尺寸进行布局的时候,你需要动态的替换这些图片坐标,而不是硬编码坐标。

很不幸的是这篇教程已经够长了,但也只是对Cocos2D-X的一个简介,这里有一个练习留给你们!

 

何去何从?

恭喜你,你现在已经亲手体验了Cocos2D-X,同时已经创建了一个简单的跨平台游戏!

你可能感兴趣的:(cocos2d-x)