This post is also available in: Chinese (Simplified)
This is a blog post by iOS Tutorial Team member Jean-Yves Mengant, an experienced Android / IOS developer and designer.
In this tutorial, I will show you how to build a space game similar to the one we built in the How to Make a Space Shooter iPhone Game.
There will be one major difference – this will be written in beautiful cross-platform C++, using Cocos2D-X!
This means you’ll be able to run this game on both your iPhone and Android by the end of the tutorial. And with a little more work, you could get it running on other platforms as well – from Windows to Linux to the Mac!
This tutorial picks up where our Cocos2D-X for iOS and Android: Getting Started tutorial left off. So if you haven’t already, read that project first and get your project set up the way described there.
So put on your Star Wars CD to set the mood, and let’s get started!
Getting Started
First things first – go ahead and download the space game resources ZIP file and unzip them to your hard drive.
We want to add these in a way so the same files can be used in both the Android and iOS projects, much like we did for the reusable C++ classes. To do this, we’ll add the files to the Android project’s Resources directory, and add relative links to this directory from our iOS project.
To add images and other resources into our project, we need to add them into $PROJECT_HOME\Resources (Remember that $PROJECT_HOME is the location of your Android Cocos2D-X project – samplecocos2dxandroid). However, our Eclipse project will only show us files inside $P$PROJECT_HOME\android, so we have a problem!
Luckily there is an easy workaround: we’ll make a link from $PROJECT_HOME\android\Resources to $PROJECT_HOME\Resources – this way the files are where they need to be, but Eclipse can see them.
To do this, open up a Terminal and run the following command from within the $PROJECT_HOME\android folder:
ln -s ../Resources ./Resources.
If you go back to Eclipse, right click the project, and select Refresh, you should see the new folder appear.
Now it’s time to copy the files to the Resources folder. However, note that for cross platform portability reasons, you should avoid using subdirectory hierarchies inside the Resources folder. Although subdirectories work fine under iOS, they do not work well on Android. For example, if you had the Sprites.pvr.ccz file inside a SpriteSheet subdirectory, on Android the CCSpriteBatchNode::batchNodeWithFile Cocos2DX method would fail and return a null pointer.
So copy all of the individual files from the space game resources ZIP file into the Resources folder. Remember not to create any subdirectories, just copy the files over. There’s also an existing fonts subfolder inside Resources, move all the files inside fonts into Resources as well. Additionally, there’s a Classes subfolder inside the ZIP file that you should not add to the Resources – just delete that folder, or keep it for later reference when you get to the point where those particular files are created in the tutorial. When you’re done your Resources folder should look like this:

Next, let’s bring in these same files in to the iOS project as well. Open your Xcode project, and create a new group called SharedResources. Select the new group, and in the File Inspector click the button next to Path and browse to your Resources directory under your Android project.

Then right click the SharedResources group, select Add Files, and add all of the files from the Android folder. w00t – you’re fully set up!
Adding A Space Ship
Let’s try this out and see if it works! Open up Classes\HelloWorldScene.h and add the following to the beginning of the HelloWorld class (above the existing public: line):
private: cocos2d::CCSpriteBatchNode * _batchNode; cocos2d::CCSprite * _ship; |
This creates two private instance variables – one for the sprite batch node, and one for the space ship sprite.
Next switch to HelloWorldScene.cpp and inside the init() method, delete everything from the comment “2. add a menu item” to the end of the method. Then add the following:
_batchNode = CCSpriteBatchNode::batchNodeWithFile("Sprites.pvr.ccz"); this->addChild(_batchNode); CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("Sprites.plist"); _ship = CCSprite::spriteWithSpriteFrameName("SpaceFlier_sm_1.png"); CCSize winSize = CCDirector::sharedDirector()->getWinSize(); _ship->setPosition(ccp(winSize.width * 0.1, winSize.height * 0.5)); _batchNode->addChild(_ship, 1); return true; |
Note that this code looks very similar to what you’re used to with Cocos2D in Objective-C. The API is pretty much the same, it’s just that there’s some differences in C++ language syntax.
Compile and run in your Android simulator, and you should see your ship appear on the screen:

And the best part is it works on iOS as well!

Adding Parallax Scrolling
Next, let’s add our space background and make it scroll in a cool way with parallax scrolling.
First, it’s annoying to have to prefix everything with the cocos2d namespace, so add this line to HelloWorldScene.h, right before the class declaration:
USING_NS_CC ; |
Then add some new variables to the private section of the HelloWorld class (note we don’t need to prefix them anymore):
CCParallaxNode *_backgroundNode; CCSprite *_spacedust1; CCSprite *_spacedust2; CCSprite *_planetsunrise; CCSprite *_galaxy; CCSprite *_spacialanomaly; CCSprite *_spacialanomaly2; |
Next, add the following to the HelloWorldScene.cpp‘s init method, right before the return statement:
// 1) Create the CCParallaxNode _backgroundNode = CCParallaxNode::node() ; //1 this->addChild(_backgroundNode,-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 CCPoint dustSpeed = ccp(0.1, 0.1); CCPoint bgSpeed = ccp(0.05, 0.05); // 4) Add children to CCParallaxNode _backgroundNode->addChild(_spacedust1, 0 , dustSpeed , ccp(0,winSize.height/2) ); // 2 _backgroundNode->addChild(_spacedust2, 0 , dustSpeed , ccp( _spacedust1->getContentSize().width,winSize.height/2)); _backgroundNode->addChild(_galaxy,-1, bgSpeed , ccp(0,winSize.height * 0.7)); _backgroundNode->addChild(_planetsunrise,-1 , bgSpeed,ccp(600,winSize.height * 0)); _backgroundNode->addChild(_spacialanomaly,-1, bgSpeed,ccp(900,winSize.height * 0.3)); _backgroundNode->addChild(_spacialanomaly2,-1, bgSpeed,ccp(1500,winSize.height * 0.9)); |
Again, this is quite similar to the code in our Cocos2D space shooter tutorial, there are only minor syntax changes. You might enjoy comparing the two tutorials to see the differences in syntax.
Compile and run in your Android simulator, and you should see the start of a space scene:

And again, it works on the iPhone too!

Now let’s make the background scroll. First, predeclare our update method in HelloWorldScene.h – you can add the following code to either the private or public section but since the update method is used internally, it’s probably more appropriate to add it as a private method:
// scheduled Update void update(cocos2d::ccTime dt); |
Then add the implementation at the end of HelloWorldScene.cpp:
void HelloWorld::update(ccTime dt) { CCPoint backgroundScrollVert = ccp(-1000,0) ; _backgroundNode->setPosition(ccpAdd(_backgroundNode->getPosition(),ccpMult(backgroundScrollVert,dt))) ; } |
Finally, call the scheduleUpdate method at the end of the init method (but before the final return statement):
this->scheduleUpdate(); |
Compile and run, and you will see the background scroll!
Continuous Scrolling
However, you will notice that after the background scrolls offscreen, it doesn’t repeat, so we need to fix that.
When we implemented this in the Cocos2D space shooter tutorial, we extended the CCParallaxNode class via an Objective-C category. Unfortunately, categories don’t exist in C++, so we’ll have to resort to an alternative method: inheritance.
We’re going to define a CCParallaxNode extras class that extends the standard CCParallaxNode. This is not as elegant as an Objective-C category, but sometimes we need to make some sacrifices for the sake of portability.
In Xcode, right click the Classes group and select New File. Select the iOS\C and C++\C++ File template, click Next, name it CCParallaxNodeExtras.cpp, browse to the $PROJECT_HOME\Classes directory to save it, and click Create. Then repeat these steps but choose the iOS\C and C++\Header File template and name it CCParallaxNodeExtras.h.
Then replace CCParallaxNodeExtras.h with the following:
#ifndef Cocos2DxFirstIosSample_CCParallaxNodeExtras_h #define Cocos2DxFirstIosSample_CCParallaxNodeExtras_h #include "cocos2d.h" USING_NS_CC; class CCParallaxNodeExtras : public CCParallaxNode { public : // Need to provide our own constructor CCParallaxNodeExtras() ; // just to avoid ugly later cast and also for safety static CCParallaxNodeExtras * node() ; // Facility method (we expect to have it soon in COCOS2DX) void incrementOffset(CCPoint offset,CCNode* node) ; } ; #endif |
Here we extend CCParallaxNode to add a new method (incrementOffset) that we will use to update the position of a child of the parallax node. We will be using this when part of the background goes offscreen to the left – we’re going to move it offscreen to the right for continuous wrapping.
Next replace CCParallaxNodeExtras.cpp with the following:
#include "CCParallaxNodeExtras.h" // Hack to access CCPointObject (which is not a public class) class CCPointObject : CCObject { CC_SYNTHESIZE(CCPoint, m_tRatio, Ratio) CC_SYNTHESIZE(CCPoint, m_tOffset, Offset) CC_SYNTHESIZE(CCNode *,m_pChild, Child) // weak ref }; // Need to provide our own constructor CCParallaxNodeExtras::CCParallaxNodeExtras() { CCParallaxNode::CCParallaxNode() ; // call parent constructor } CCParallaxNodeExtras * CCParallaxNodeExtras::node() { return new CCParallaxNodeExtras() ; } void CCParallaxNodeExtras::incrementOffset(CCPoint offset,CCNode* node){ for( unsigned int i=0;i < m_pParallaxArray->num;i++) { CCPointObject *point = (CCPointObject *)m_pParallaxArray->arr[i]; CCNode * curNode = point->getChild() ; if( curNode->isEqual(node) ) { point->setOffset( ccpAdd(point->getOffset(), offset) ); break; } } } |
Note that CCPointObject is unfortunately not a public class in Cocos2D, so we have to resort to a bit of a hack to use it (redefine it in our own class with the same signature). Although this works, the drawback is that if the CCPointObject structure ever changes, you’ll have to update it here as well or the program will crash.
The meat of the code is the incrementOffset method, and it’s implemented the same way the Cocos2D space shooter tutorial did it, just with different syntax.
Next, switch to HelloWorldScene.h and add this to the top of the file beneath the existing #include statement:
#include "CCParallaxNodeExtras.h" |
Then change the _backgroundNode definition in the private section from a CCParallaxNode to a CCParallaxNodeExtras as follows:
CCParallaxNodeExtras *_backgroundNode; |
Now switch over to HelloWorldScene.cpp and replace the first line in section #1 (the one where _backgroundNode is created) with the following:
_backgroundNode = CCParallaxNodeExtras::node() ; |
Finally, add the following to the end of the update method:
CCArray *spaceDusts = CCArray::arrayWithCapacity(2) ; spaceDusts->addObject(_spacedust1) ; spaceDusts->addObject(_spacedust2) ; for ( int ii = 0 ; ii <spaceDusts->count() ; ii++ ) { CCSprite * spaceDust = (CCSprite *)(spaceDusts->objectAtIndex(ii)) ; float xPosition = _backgroundNode->convertToWorldSpace(spaceDust->getPosition()).x ; float size = spaceDust->getContentSize().width ; if ( xPosition < -size ) { _backgroundNode->incrementOffset(ccp(spaceDust->getContentSize().width*2,0),spaceDust) ; } } CCArray *backGrounds = CCArray::arrayWithCapacity(4) ; backGrounds->addObject(_galaxy) ; backGrounds->addObject(_planetsunrise) ; backGrounds->addObject(_spacialanomaly) ; backGrounds->addObject(_spacialanomaly2) ; for ( int ii = 0 ; ii <backGrounds->count() ; ii++ ) { CCSprite * background = (CCSprite *)(backGrounds->objectAtIndex(ii)) ; float xPosition = _backgroundNode->convertToWorldSpace(background->getPosition()).x ; float size = background->getContentSize().width ; if ( xPosition < -size ) { _backgroundNode->incrementOffset(ccp(2000,0),background) ; } } |
You can see that the alternative to NSArray here is CCArray, which is based on the STL C++ template library. We use the arrayWithCapacity constructor which will release the object automatically for us. Note that there is also a more sophisticated CCMutableArray that can be used for collection storage.
One final change – since you added a new file, you need to add it to the Android project’s Makefile to get it to build correctly. So open Classes\Android.mk in Eclipse, and modify the LOCAL_SRC_FILES line to read as follows:
LOCAL_SRC_FILES := AppDelegate.cpp \
HelloWorldScene.cpp \
CCParallaxNodeExtras.cpp
Compile and run, and now the background will scroll endlessly!
Adding Stars
Adding the stars is pretty straightforward. Just add this code to the init method in HelloWorldScene.cpp, right before the return statement:
HelloWorld::addChild(CCParticleSystemQuad::particleWithFile("Stars1.plist")) ; HelloWorld::addChild(CCParticleSystemQuad::particleWithFile("Stars2.plist")) ; HelloWorld::addChild(CCParticleSystemQuad::particleWithFile("Stars3.plist")) ; |
Compile and run, and pretty cool – it’s starting to look like a space game!


Moving the Ship with Accelerometer
In the original Cocos2D space shooter tutorial we used the iOS accelerometer API to detect accelerometer input. Obviously the iOS accelerometer API isn’t cross platform, so what do we do?
Luckily, Cocos2D-X has provided an abstraction layer around accelerometer input that we can use. Let’s see how it works.
First, make add a new private variable to HelloWorldScene.h:
float _shipPointsPerSecY; |
Then, add a new public method definition:
virtual void didAccelerate(CCAcceleration* pAccelerationValue); |
Then add the following code to the bottom of init in HelloWorldScene.cpp, before the return:
this->setIsAccelerometerEnabled(true); |
Next, add the following new method to the bottom of the file:
void HelloWorld::didAccelerate(CCAcceleration* pAccelerationValue) { #define KFILTERINGFACTOR 0.1 #define KRESTACCELX -0.6 #define KSHIPMAXPOINTSPERSEC (winSize.height*0.5) #define KMAXDIFFX 0.2 double rollingX ; // Cocos2DX inverts X and Y accelerometer depending on device orientation // in landscape mode right x=-y and y=x !!! (Strange and confusing choice) pAccelerationValue->x = pAccelerationValue->y ; rollingX = (pAccelerationValue->x * KFILTERINGFACTOR) + (rollingX * (1.0 - KFILTERINGFACTOR)); float accelX = pAccelerationValue->x - rollingX ; CCSize winSize = CCDirector::sharedDirector()->getWinSize(); float accelDiff = accelX - KRESTACCELX; float accelFraction = accelDiff / KMAXDIFFX; _shipPointsPerSecY = KSHIPMAXPOINTSPERSEC * accelFraction; } |
Finally, add the following to the bottom of update:
CCSize winSize = CCDirector::sharedDirector()->getWinSize(); float maxY = winSize.height - _ship->getContentSize().height/2; float minY = _ship->getContentSize().height/2; float diff = (_shipPointsPerSecY * dt) ; float newY = _ship->getPosition().y + diff; newY = MIN(MAX(newY, minY), maxY); _ship->setPosition(ccp(_ship->getPosition().x, newY)); |
The didAccelerate callback provides a CCAcceleration instance which contains x,y and z information the acceleration data. In our case, we’re only interested in the x acceleration data so that we can move the spaceship when the phone rotates around its x axis.
Note: Cocos2D-X switches the accelerometer x and y values depending on whether the device is in portrait or landscape mode. In Landscape right (our case here), the received x is -y and received y is x. In Landscape left, the received x is y and the received y is -x. Confusing eh? :]
Compile and run on your iPhone and Android devices, and you should now be able to move the ship by tilting your device! Of course, at this point, you’ll not be able to test the game on the simulator either for Android or iOS and would need to test on device.
Adding Asteroids
Time to add some asteroids to the game! Start by adding some new private variables to HelloWorldScene.h:
CCMutableArray<CCSprite *> * _asteroids ; int _nextAsteroid ; float _nextAsteroidSpawn ; |
Then, add some new public method definitions:
float randomValueBetween( float low , float high ); void setInvisible(CCNode * node); float getTimeTick(); |
Next switch to HelloWorldScene.cpp, and add the implementations for the helper methods you defined above to the end of the file:
float HelloWorld::randomValueBetween( float low , float high ) { return (((float) arc4random() / 0xFFFFFFFFu) * (high - low)) + low; } float HelloWorld::getTimeTick() { timeval time; gettimeofday(&time, NULL); unsigned long millisecs = (time.tv_sec * 1000) + (time.tv_usec / 1000); return (float) millisecs; } |
randomValueBetween is a helper method to get a random number within a float range, and getTimeTick is a portable way to get the time in milliseconds (as a cross-platform alternative to CACurrentMediaTime, but with milliseconds instead of seconds).
Next, add this code to create an array of asteroids at the bottom of the init method right before the return statement:
#define KNUMASTEROIDS 15 _asteroids = new CCMutableArray<CCSprite *>(); for(int i = 0; i < KNUMASTEROIDS; ++i) { CCSprite *asteroid = CCSprite::spriteWithSpriteFrameName("asteroid.png"); asteroid->setIsVisible(false) ; _batchNode->addChild(asteroid); _asteroids->addObject(asteroid); } |
Here we use Cocos2D-X’s CCMutableArray class to store an array of CCSprites. Note that we created the array manually with the new operator (rather than using arrayWithCapacity) to avoid the autorelease mechanism.
Finally, add this code to the bottom of update:
float curTimeMillis = getTimeTick(); if (curTimeMillis > _nextAsteroidSpawn) { float randMillisecs = randomValueBetween(0.20,1.0) * 1000; _nextAsteroidSpawn = randMillisecs + curTimeMillis; float randY = randomValueBetween(0.0,winSize.height); float randDuration = randomValueBetween(2.0,10.0); CCSprite *asteroid = _asteroids->getObjectAtIndex(_nextAsteroid); _nextAsteroid++; if (_nextAsteroid >= _asteroids->count()) _nextAsteroid = 0; asteroid->stopAllActions(); asteroid->setPosition( ccp(winSize.width+asteroid->getContentSize().width/2, randY)); asteroid->setIsVisible(true) ; asteroid->runAction ( CCSequence::actions ( CCMoveBy::actionWithDuration(randDuration,ccp(-winSize.width-asteroid->getContentSize().width,0)) , CCCallFuncN::actionWithTarget(this,callfuncN_selector(HelloWorld::setInvisible)) , NULL // DO NOT FORGET TO TERMINATE WITH NULL (unexpected in C++) ) ) ; } |
Again, this is vary similar to the original Cocos2D code. Note that you can even mimic calling Objective-C selectors through the callfuncN_selector call.
Note: Since the CCSequence::actions method is defined to have a variable number of arguments, you might be tempted to omit the final NULL argument, since it’s useless in C++. But for compatibility reasons, the Cocos2D-X developers decided to keep compliancy with Cocos2D and require the NULL terminator, and if you don’t provide it in your code, your app will crash.
The final step is to add the setInvisible callback method to the end of the file:
void HelloWorld::setInvisible(CCNode * node) { node->setIsVisible(false) ; } |
Compile and run, and asteroids-ahoy! Here it is running on an iPhone 3GS:

And here running on an Android Samsung Galaxy S:

Shooting Lasers
It’s time to make this ship shoot! Add the following private variables to HelloWorldScene.h:
CCMutableArray<CCSprite *> * _shipLasers ; int _nextShipLaser ; |
Now add a new public method definition:
virtual void ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event); |
Then, add the following to the bottom of init in HelloWorldScene.cpp before the return:
#define KNUMLASERS 5 _shipLasers = new CCMutableArray<CCSprite *>(); for(int i = 0; i < KNUMLASERS; ++i) { CCSprite *shipLaser = CCSprite::spriteWithSpriteFrameName("laserbeam_blue.png"); shipLaser->setIsVisible(false) ; _batchNode->addChild(shipLaser); _shipLasers->addObject(shipLaser); } this->setIsTouchEnabled(true) ; |
Finally, implement ccTouchesBegan at the end of the file:
void HelloWorld::ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event) { CCSize winSize = CCDirector::sharedDirector()->getWinSize() ; CCSprite *shipLaser = _shipLasers->getObjectAtIndex(_nextShipLaser++); if ( _nextShipLaser >= _shipLasers->count() ) _nextShipLaser = 0; shipLaser->setPosition( ccpAdd( _ship->getPosition(), ccp(shipLaser->getContentSize().width/2, 0))); shipLaser->setIsVisible(true) ; shipLaser->stopAllActions() ; shipLaser->runAction( CCSequence::actions ( CCMoveBy::actionWithDuration(0.5,ccp(winSize.width, 0)), CCCallFuncN::actionWithTarget(this,callfuncN_selector(HelloWorld::setInvisible)) , NULL // DO NOT FORGET TO TERMINATE WITH NULL ) ) ; } |
Compile and run, and now you can shoot lasers! Here it is running on an iPhone 3GS :

And on an Android Samsung Galaxy S:

Basic Collision Detection
Next, we want to add some code to detect collisions between lasers and asteroids, and explode the asteroids that were hit when that occurs!
Again, the code to do this is quite similar to the Cocos2D space shooter tutorial, but with some new syntax. Notice that we introduce a C++ iterator to walk through the CCMutableArray.
First, add a new private variable to HelloWorldScene.h:
int _lives ; |
Then, add the following code to the bottom of update in HelloWorldScene.cpp:
// Asteroids CCMutableArray<CCSprite *>::CCMutableArrayIterator itAster , itLaser ; for ( itAster = _asteroids->begin() ; itAster != _asteroids->end() ; itAster++ ) { CCSprite *asteroid = (CCSprite *)*itAster; if ( ! asteroid->getIsVisible() ) continue ; for ( itLaser = _shipLasers->begin() ; itLaser != _shipLasers->end() ; itLaser++ ) { CCSprite *shipLaser = (CCSprite *)*itLaser; if ( ! shipLaser->getIsVisible() ) continue ; if ( CCRect::CCRectIntersectsRect(shipLaser->boundingBox(), asteroid->boundingBox()) ) { shipLaser->setIsVisible(false) ; asteroid->setIsVisible(false) ; continue ; } } if ( CCRect::CCRectIntersectsRect(_ship->boundingBox(), asteroid->boundingBox()) ) { asteroid->setIsVisible(false) ; _ship->runAction( CCBlink::actionWithDuration(1.0, 9)) ; _lives-- ; } } |
Compile and run, and now you should be able to blow up asteroids!
Of course, you’ll notice that while the asteroids disappear when the lasers hit them, there are no explosions. That’s because we haven’t added in the particle system for the explosion. Since we’ve already covered particle system addition for the stars, it should be a simple enough task to follow the original tutorial and add the explosions in. Consider it an extra credit assignment :]
Win/Lose Detection
Converting the win/lose detection code to Cocos2D-X is straightforward as well.
Switch back to HelloWorldScene.h and add an enum before the class declaration:
typedef enum { KENDREASONWIN, KENDREASONLOSE } EndReason; |
Now add a couple of private variables to the HelloWorld class:
double _gameOverTime ; bool _gameOver ; |
Next, add the definitions for two new private methods:
void endScene( EndReason endReason ) ; void restartTapped() ; |
Then, switch to HelloWorldScene.cpp and add the following code to init, before the return:
_lives = 3 ; double curTime = getTimeTick() ; _gameOverTime = curTime + 30000 ; |
Add the following code to the end of update:
if ( _lives <= 0 ) { _ship->stopAllActions() ; _ship->setIsVisible(false) ; this->endScene(KENDREASONLOSE) ; } else if ( curTimeMillis >= _gameOverTime ) { this->endScene(KENDREASONWIN) ; } |
Finally, add our new method implementations to the end of the file:
void HelloWorld::restartTapped() { CCDirector::sharedDirector()->replaceScene ( CCTransitionZoomFlipX::transitionWithDuration(0.5, this->scene())); // reschedule this->scheduleUpdate() ; } void HelloWorld::endScene( EndReason endReason ) { if ( _gameOver ) return ; _gameOver = true ; CCSize winSize = CCDirector::sharedDirector()->getWinSize() ; char message[10] = "You Win" ; if ( endReason == KENDREASONLOSE ) strcpy(message,"You Lose") ; CCLabelBMFont * label ; label = CCLabelBMFont::labelWithString(message ,"Arial.fnt" ); label->setScale(0.1) ; label->setPosition( ccp( winSize.width/2 , winSize.height*0.6)) ; this->addChild(label) ; CCLabelBMFont * restartLabel ; restartLabel = CCLabelBMFont::labelWithString("Restart" ,"Arial.fnt" ); CCMenuItemLabel *restartItem = CCMenuItemLabel::itemWithLabel(restartLabel, this, menu_selector(HelloWorld::restartTapped) ); restartItem->setScale(0.1) ; restartItem->setPosition( ccp( winSize.width/2 , winSize.height*0.4)) ; CCMenu *menu = CCMenu::menuWithItems(restartItem, NULL); menu->setPosition(CCPointZero); this->addChild(menu) ; // clear label and menu restartItem->runAction( CCScaleTo::actionWithDuration(0.5, 1.0)) ; label ->runAction( CCScaleTo::actionWithDuration(0.5, 1.0)) ; // Terminate update callback this->unscheduleUpdate() ; } |
Note that one difference between the original version of the code and the Cocos2D-X version is that we have to schedule/unschedule the update callback when the game ends and begins.
Compile and run, and if your ship gets hit too many times, you’ll die!
Gratuitous Music and Sound Effects
Android does not support the CAF sound file format, so the first thing we need to do is convert the original sound files to an alternate format that works on Android as well.
Open up a Terminal and issue the following commands (and don’t forget to replace $PROJECT_HOME in the first command with the actual path to the Android project – otherwise the command won’t work):
cd $PROJECT_HOME/Resources afconvert -f WAVE -d UI8 SpaceGame.caf SpaceGame.wav afconvert -f WAVE -d UI8 explosion_large.caf explosion_large.wav afconvert -f WAVE -d UI8 laser_ship.caf laser_ship.wav |
As you’ve probably guessed, the above commands will convert the files from CAF format to WAV format.
Once you’re done converting the files, go to your Xcode project and add the converted files to your Resources folder (from the Android directory) – similar to how you added in the images and other resources earlier.
The hard part’s done – now to play these sounds! Add the following code to the top of HelloWorldScene.cpp:
#include "SimpleAudioEngine.h" using namespace CocosDenshion ; |
Here we include the SimpleAudioEngine header file and say that we want to use symbols in its namespace.
The rest is a simple port of the original code to Cocos2D-X. Start by adding this code to the end of init, before the return:
SimpleAudioEngine::sharedEngine()->playBackgroundMusic("SpaceGame.wav",true) ; SimpleAudioEngine::sharedEngine()->preloadEffect("explosion_large.wav") ; SimpleAudioEngine::sharedEngine()->preloadEffect("laser_ship.wav") ; |
Next, add the following code to the Asteroids section in update where the CGRectIntersectsRect test is made to determine if a laser collided with an asteroid. (You could also add a sound effect when a ship collides with an asteroid, which is tested for in the next CGRectIntersectsRect test in the same section but since the ship doesn’t explode, you might want to make that a different sound :]):
SimpleAudioEngine::sharedEngine()->playEffect("explosion_large.wav") ; |
Finally, add the following to the beginning of ccTouchesBegan:
SimpleAudioEngine::sharedEngine()->playEffect("laser_ship.wav") ; |
Compile and run, and enjoy the sound effects! Here is the final project running on an iPhone 3GS:

And on an Android Samsung Galaxy S:

Platform-Specific Bugs and Tweaks
The Cocos2D-X developers do a great job, but nobody’s perfect, especially when it comes to ensuring cross-platform compatibility ;]
If you play around with the app, you’ll see that it works flawlessly on iOS. However, on Android you might find some of the following issues:
- When closing /resuming the game, sprites set by CCParticleSystemQuad are not correctly repainted and you get white squared areas instead. This appears to be an issue with Cocos2D-X on Android.
- Parallax scrolling is slow with visible refreshing sometimes.
- Cocos2D-X version .11 has a crash on menu selection (luckily this has been fixed in .12).
Since these issues deal with Cocos2D-X itself, they are beyond the scope of this tutorial (and will likely be fixed in future version of Cocos2D-X).
Also, you might notice that the stars only show up on a limited portion of the screen. This is because when the particle systems were created with Particle Designer, they were set up assuming the iPhone’s screen dimensions.
To make them work properly on Android devices, just open up the particle systems and edit them according to the Android’s screen dimensions. These settings should do the trick for most devices: source pos Y 720, variance 720, and sourcePos X 1088. However, depending on your device’s screen size, you might need to adjust those settings a bit.
Developing for Multiple Devices and Resolutions
When you develop for iOS, you have it easy – you just have two screen aspect ratios: iPhone and iPad, and normal/retina resolution for each.
However, it gets more complicated for the Android because there are numerous devices from multiple vendors, with varying screen sizes.
If you have a device with a different screen size than the iPhone, the game might not look quite right currently. But we want our game to look OK no matter what screen size the user is using!
The general idea is, you can scale the artwork based on the screen size according to the following formula:
factor = min(targetWidth / DesignWidth, targetHeight / DesignHeight) |
You also should try to place artwork, when laying out scenes, based on multiples of the screen size, etc. rather than hardcoded coordinates.
Unfortunately this tutorial is long enough already and is only meant to be an intro to Cocos2D-X, so this will be left as an exercise for you guys! :]
Where To Go From Here?
Congratulations, you now have hands-on experience with Cocos2D-X and have created a simple cross-platform game!
Here is the example project from the above tutorial.
Note that I have cleaned up the project directory structure a bit so that the same root directory can be shared across iOS and Android projects. If you’re interested in more details on how to set this up, I’d be glad to discuss more in the forums!
If you have any questions on this tutorial or Cocos2D-X in general, please join the forum discussion below!

This is a blog post by iOS Tutorial Team member Jean-Yves Mengant, an experienced Android / IOS developer and designer.
English
简体中文 






