Cocos2d is a richly featured smartphone game engine. Cocos2d-x is a C++ version that makes Cocos2d a cross platform framework. It's released under the MIT License and has an active open-source community. Cocos2d-x supports 2D graphics animation based on sprites. It includes two physics engines, a particle system, an audio API and can handle user interaction. You don't need to know OpenGL because its features are wrapped into easily accessible cocos2d-x methods. There are many tutorials on the web including Ray Wenderlich tutorials for the iPhone http://www.raywenderlich.com/tutorials and tutorials translated to C++ on the cocos2d-x website http://www.cocos2d-x.org/projects/cocos2d-x/wiki/Tutorials. You can also find some sample games like http://developer.bada.com/library/BalloonRide or http://www.cocos2d-x.org/projects/cocos2d-x/wiki/Download#Sample-Games-by-the-community.
Installing cocos2d-x
By now, you should be able to build examples included in the framework and create a new cocos2d-x project. It's recommended to use the provided script to create a new project. The script file for bada is called "create-bada-project.vbs". You will find it in the framework's directory. To avoid path problems, all the projects should have English names and they should be stored in the framework's directory.
The example project is called TestCocos2dx and it' s a very useful source of information. It contains over 40 sub projects that present the cocos2d-x features. Graphic effects used in the examples are pretty cool and you can use them freely as cocos2d-x is a royalty-free project.

Figure 1: Cocos2d-x samples
The basic concepts
A cocos2d-x game is composed of Scenes that contain Layers which handle Sprites. Scenes are managed by a Director. Every class in cocos2d-x is a subclass of the CCObject and most of them are subclasses of the CCNode which makes them easy to manage. In fact, if you want to use cocos2d-x methods with your classes you need to derive them at least from the CCNode.
As found in the iPhone guide http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:basic_concepts, the Scene is an independent piece of the application workflow. For example, you can divide your game into a Menu scene, a Game scene and a High scores scene. You can change scenes using the CCDirector which is a singleton object. You can also use it to check which scene is currently running, or to pause, resume and end scenes.
Each scene is composed of one or more layers which handle drawing and behavior of the game. For example you can have a game scene which is composed of a HUD layer, a main layer and a background layer. Each layer handles a specific part of the game logic, e.g. the HUD layer displays the current score and the character' s life. Layers can handle the player's interaction, manage game time, update game objects and exchange information with other parts of the application.
Game characters and objects are represented by sprites. Sprites can be animated and moved around the screen. Most of the time sprites will be stored on a sprite sheet like the one below.

Figure 2: Space Shooter sprite sheet
Each image on the sprite sheet is described in an xml file similar to the MainScene.plist file found in the Res folder of the sample project. It's a good practice to store many sprites on one sprite sheet, e.g. all sprites for the Space Level. This will significantly improve performance as only one file will be used per OpenGL ES draw call.
To move sprites you can use cocos2d-x actions like CCMoveTo or CCRotateBy. Actions can be grouped in sequences, repeated, delayed or scheduled. You can read more about it here http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:index#actions_transformations_and_effects.
Each sprite and layer is a node that can be updated. You can update objects with every frame using the scheduleUpdate() method or you can use the schedule() method and set the update frequency. Schedule callback will be called only if the node is currently active, e.g. the layer is part of the currently running scene. You can't use the schedule with objects that aren't nodes.
Cocos2d-x has its own methods handling sensor events and it's recommended to use them instead of the plain bada equivalents. The framework will hold sensor updates when the game pauses or the screen goes black avoiding unwanted sprite movement or clicks.
You can also choose between two physics engines, Box2d and chipmunk, that makes creating realistic collisions and rigid body transformations easy. These engines have their own worlds that need to be synchronized with cocos2d-x world time and space, but fortunately it's fairly easy to do and there are examples.
Space shooter – the first steps
The goal of the Space Shooter episode series is to show developers how to create a 2D game for bada platform using cocos2d-x framework. The game is a sidescroller where the player controls a spaceship that fights off hordes of invading aliens. The first episode will have the spaceship hanging in space with aliens flying through the screen. The player controls the ship using a touch sensor. The game will have a menu scene and a game scene. If the ship hits an alien, the game will return to the menu scene. To run the game you need to extract the downloaded application to the previously set cocos2d-x framework directory and import the application.
Space Shooter: Episode I looks like this :

Figure 3: Space Shooter scenes
The game starts with the menu scene which has only one item – the start button. The scene object is also a layer because it's a small class and there's no reason to separate the scene and layer. The MenuScene class declaration looks like this :
1 class MenuScene :
2 public cocos2d::CCLayer
3 {
4 public:
5 MenuScene();
6 bool init();
7 static cocos2d::CCScene* scene();
8 void menuStartCallback(cocos2d::CCObject* pSender);
9
10 // implement the "static node()" method manually
11 LAYER_NODE_FUNC(MenuScene);
12 };
LAYER_NODE_FUNC [11] macro implements a static node() method that will create the node and call init() [6] method of the layer – the MenuScene in this case. To create the scene you need to call the scene() [7] method. It's called in the applicationDidFinishLaunching() method of the AppDelegate:
1 bool AppDelegate::applicationDidFinishLaunching() {
2 …
3 // create a scene. it's an autorelease object
4 CCScene *pScene = MenuScene::scene();
5
6 // run
7 pDirector->runWithScene(pScene);
The menu scene has a CCMenu object which is in fact a layer provided by the framework for convenience. You can use this class to create menus and different buttons, e.g. controls. The menu is created in the init() method :
1 bool
2 MenuScene::init()
3 {
4 // Init super first.
5 if ( !CCLayer::init() )
6 {
7 return false;
8 }
9
10 // Create start button.
11 CCMenuItemFont* pStartButton = CCMenuItemFont::itemFromString(
12 "Start",
13 this,
14 menu_selector(MenuScene::menuStartCallback));
15
16 pStartButton->setFontSizeObj(50);
17
18 // Ask director the window size.
19 CCSize winSize = CCDirector::sharedDirector()->getWinSize();
20 pStartButton->setPosition(ccp(winSize.width / 2, winSize.height / 2));
21
22 // Create menu, it's an autorelease object
23 CCMenu* pMenu = CCMenu::menuWithItems(pStartButton, NULL);
24 pMenu->setPosition(CCPointZero);
25 this->addChild(pMenu, 1);
26
27 return true;
28 }
The Start button is created using the CCMenuItemFont::itemFromString() [11] method that takes the MenuScene::menuStartCallback() [14] callback method as a parameter which will later change the scene to the MainScene. After setting the font and position, the pMenu [23] object is added as a child node of the MenuScene [25]. It will be displayed when the MenuScene becomes active.
The menuStartCallback() method looks as follows :
1 void
2 MenuScene::menuStartCallback(cocos2d::CCObject* pSender)
3 {
4 CCScene *pScene = MainScene::scene();
5 CCDirector::sharedDirector()->replaceScene(pScene);
6 }
After creating the MainScene using the static scene() [4] method, the scene is changed using the CCDirector::sharedDirector() [5].
The MainScene is composed of two layers – the MainLayer and the HUD layer, both created in the MainScene::scene() method:
1 CCScene*
2 MainScene::scene()
3 {
4 // 'pScene' is an autorelease object
5 CCScene* pScene = CCScene::node();
6
7 // 'hudLayer' is an autorelease object
8 HudLayer* hudLayer = HudLayer::node();
9
10 // add the hudLayer as a child to the scene
11 pScene->addChild(hudLayer, 1);
12
13 // 'pMainLayer' is an autorelease object
14 MainLayer* pMainLayer = MainLayer::node();
15
16 // pass the HudLayer pointer to the MainLayer so they can communicate
17 pMainLayer->setHudLayer(hudLayer);
18 pScene->addChild(pMainLayer);
19
20 return pScene;
21 }
Let's take a look at the HUD layer first. For now, it's only responsible for displaying the score, but it will later display player's health and have buttons for upgrading guns and autofire. It has only two methods :
1 bool
2 HudLayer::init()
3 {
4 if (!CCLayer::init())
5 {
6 return false;
7 }
8
9 // Ask director the window size.
10 CCSize winSize = CCDirector::sharedDirector()->getWinSize();
11
12 // Create the score label.
13 __pScoreLabel = CCLabelTTF::labelWithString("Score: 0", "Arial", 30);
14
15 // Position the label on the right side of the screen.
16 __pScoreLabel->setPosition(ccp(winSize.width - 100, winSize.height - 20));
17
18 // Add the label as a child to this layer.
19 this->addChild(__pScoreLabel, 1);
20
21 return true;
22 }
23
24 void
25 HudLayer::updateScore(int score)
26 {
27 const int labelLength = 100;
28 char scoreLabelText[labelLength];
29 snprintf(scoreLabelText, labelLength, "Score: %d", score);
20 __pScoreLabel->setString(scoreLabelText);
31 }
The init() method sets the score label using CCLabelTTF::labelWithString() [13] method. The updateScore() method is used by the MainLayer to display the current score. Notice that Cocos2d-x is using C style string so every bada String will have to be converted to const char* array.
The MainLayer contains most of the game's logic so it's a more complicated class. The init() method looks like this :
1 bool
2 MainLayer::init()
3 {
4 if( !CCLayer::init())
5 {
6 return false;
7 }
8
9 // Ask director the window size.
10 __winSize = CCDirector::sharedDirector()->getWinSize();
11
12 // Load and save sprite frames described in the MainScene.plist file.
13 CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("MainScene.plist");
14
15 // CCSpriteBatchNode will act as the sprite sheet.
16 __pSpriteSheet = CCSpriteBatchNode::batchNodeWithFile("MainScene.png");
17 this->addChild(__pSpriteSheet, -1);
18
19 // Create the background sprite.
20 CCSprite* pBackground = CCSprite::spriteWithSpriteFrameName("background.png");
21
22 // Change the anchor for convenience.
23 pBackground->setAnchorPoint(ccp(0, 0));
24 pBackground->setPosition(ccp(0, 0));
25 __pSpriteSheet->addChild(pBackground, -2);
26
27 __pEnemies = new CCMutableArray();
28
29 // Initialize the random number generator
30 Math::Srand((unsigned)time(NULL));
31
32 // Schedule game updates
33 scheduleUpdate();
34
35 // Schedule enemy generator callbacks.
36 schedule(schedule_selector(MainLayer::generate), (ccTime)3.0f);
37
38 return true;
39 }
Line [13] loads the MainScene.plist file describing sprites on the MainScene.png spritesheet which is loaded in the next line [16]. The __pSpriteSheet object will be used as a parent for all the sprites in the spritesheet. The pBackground object [20] is the first created sprite. While adding child nodes, you can set the zOrder parameter which, for example, allows putting the background image below any other sprite. Sprites are positioned according to their anchor, which is by default in the middle of the sprite. For convenience the pBackground anchor was changed to (0, 0) [23].
This game will have randomly generated enemies that will be held in the __pEnemies array [27]. Line [30] initializes the random number generator. Line [33] turns on updates for the MainLayer node, which means that an update() callback method will be called every frame. This method will be used to update the score, remove enemies that have left the screen and detect the collisions. Line [36] schedules the generate() method that will be called every 3 seconds. This method creates the new enemies.
After executing the init() method, the onEnter() method is called :
1 void
2 MainLayer::onEnter()
3 {
4 CCLayer::onEnter();
5 __pShip = new Ship();
6 __pShip->init("ship.png", __pSpriteSheet);
7 __pShip->setPosition(ccp(__winSize.width / 2, __winSize.height / 2));
8 __pShip->setMove(__pShip->getPosition());
9
10 this->setIsTouchEnabled(true);
11 }
t's a good place to create the game objects [5] and turn on sensor listeners [10].
Touch events are handled in the ccTouchesBegan() and the ccTouchesMoved() methods that look the same :
1 void
2 MainLayer::ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent)
3 {
4 // Get any touch and convert the touch position to in-game position.
5 CCTouch* touch = (CCTouch*)pTouches->anyObject();
6 CCPoint position = touch->locationInView(touch->view());
7 position = CCDirector::sharedDirector()->convertToGL(position);
8
9 __pShip->setMove(position);
10 }
This method can handle multi-touch events, that's why it's called the ccTouchesBegan(). There is also the ccTouchBegan() method that handles single touch events but you need to register a touch dispatcher first. You can read more about it here http://www.cocos2d-iphone.org/wiki/doku.php/tips:touchdelegates and here http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:lesson_2._your_first_game#responding_to_touch. The touch location is converted to in-game position [7] and passed to the setMove() method of the __pShip object [9]. This method sets a variable that is later used to position the ship in response to the touch events.
Game objects are updated in the update() method :
1 void
2 MainLayer::update(ccTime dt)
3 {
4 //Update the score with every frame.
5 __score += 1;
6 if(__score % 10 == 0)
7 {
8 // Displaying is expensive so the score is displayed every 10 frames.
9 __pHudLayer->updateScore(__score);
10 }
11
12 // It is changed to true if enemy hits the ship.
13 bool theEnd = false;
14
15 // This array will hold enemy objects that are off the screen or in collision with the ship.
16 CCMutableArray* pObjectsToRemove = new CCMutableArray();
17 CCMutableArray::CCMutableArrayIterator it;
18 if(__pEnemies && __pEnemies->count() > 0)
19 {
20 for(it = __pEnemies->begin(); it != __pEnemies->end(); it++)
21 {
22 Enemy* pEnemy = *it;
23 if(pEnemy)
24 {
25 CCRect enemyRect = pEnemy->getRect();
26 CCRect shipRect = __pShip->getRect();
27 bool collision = CCRect::CCRectIntersectsRect(shipRect, enemyRect);
28 if(((CCRect::CCRectGetMinX(enemyRect) < 0) && (CCRect::CCRectGetMaxX(enemyRect) < 0))
29 || (collision))
30 {
31 // Enemy object is off the screen or colliding with the ship.
32 if(collision)
33 {
34 // Enemy object is colliding with the ship.
35 theEnd = true;
36 }
37 pObjectsToRemove->addObject(pEnemy);
38 }
39 }
40 }
41 }
42
43 if(pObjectsToRemove->count() > 0)
44 {
45 CCMutableArray::CCMutableArrayIterator it;
46 for(it = pObjectsToRemove->begin(); it != pObjectsToRemove->end(); it++)
47 {
48 Enemy* pObjectToRemove = *it;
49 pObjectToRemove->destroy();
50 }
51 }
52
53 __pEnemies->removeObjectsInArray(pObjectsToRemove);
54
55 // Clean the array.
56 pObjectsToRemove->removeAllObjects(true);
57 pObjectsToRemove->release();
58
59 if(theEnd)
60 {
61 // Get back to the MenuScene.
62 CCScene *pScene = MenuScene::scene();
63 CCDirector::sharedDirector()->replaceScene(pScene);
64 }
65 }
First, this method updates the score using the HUD layer [9]. The other part of this method is responsible for removing enemies that have left the screen and for detecting collisions. The "for loop" in line [20] iterates through enemies. If the rectangle around the enemy intersects with the ship's rectangle a collision is detected [27]. If the enemy is off the screen or it's colliding with the ship [28], it is added to the pObjectsToRemove array [37]. It's better to remove objects using the provided methods than by yourself while iterating, as shown in lines [53] and [56]. If the ship is hit by an enemy the game ends and the scene is changed to the MenuScene [63].
Game objects like the ship and enemies are derived from the AnimatedObject class which subclasses the CCSprite. The AnimatedObject class contains common methods like sprite initialization and cleanup; in the future it will also support animation frames and collisions.
1 Bool
2 AnimatedObject::init(const char* frameName, CCSpriteBatchNode* pSpriteSheet)
3 {
4 __pSpriteSheet = pSpriteSheet;
5 if(!CCSprite::initWithSpriteFrameName(frameName))
6 {
7 return false;
8 }
9
10 float spriteWidth = this->getContentSize().width;
11 this->setPosition(ccp(-spriteWidth, 0.0f));
12 __pSpriteSheet->addChild(this);
13
14 return true;
15 }
The frameName [2] parameter of the init() method is equal to the key value in MainScene.plist file and is used to initialize the CCSprite. The pSpriteSheet node will be parent to the new sprite [12] and in SpaceShooter it is equal for all sprites belonging to the current scene. Sprites are created outside the visible part of the scene [11].
The Enemy class currently has only one method :
1 void
2 Enemy::start(float speed)
3 {
4 CCSize enemySize = this->boundingBox().size;
5 CCSize winSize = CCDirector::sharedDirector()->getWinSize();
6
7 int startingPosition = Math::Rand();
8 float x = winSize.width + enemySize.width / 2.0f;
9 float y = (float)(startingPosition % (int)(winSize.height - enemySize.height)) + enemySize.height / 2.0f;
10
11 this->setPosition(ccp(x, y));
12
13 ccTime duration = (ccTime)(winSize.width / speed);
14 x = -this->boundingBox().size.width - 5;
15 CCFiniteTimeAction* actionMove = CCMoveTo::actionWithDuration(duration, ccp(x, y));
16 this->runAction(actionMove);
17 }
This method sets a random starting position for the enemy [11] and starts the enemy movement [15]. It is moved using the CCMoveTo action which takes duration of the movement and a point to which the sprite should move as the parameters.
The ship’s movement is handled differently than the enemies’. In the init() method it schedules the move() callback [10] that will move the ship based on the current __move parameter value. This parameter is updated when user touches the screen.
1 bool
2 Ship::init(const char* frameName, cocos2d::CCSpriteBatchNode* pSpriteSheet)
3 {
4 if(!AnimatedObject::init(frameName, pSpriteSheet))
5 {
6 return false;
7 }
8
9 __speed = 10000.0f;
10 schedule(schedule_selector(Ship::move), (ccTime)0.03f);
11
12 return true;
13 }
The move() method looks like this :
1 void
2 Ship::move()
3 {
4 if(__pMoveAction)
5 {
6 this->stopAction(__pMoveAction);
7 }
8 CCPoint currentPosition = this->getPosition();
9 CCPoint moveDifference = ccpSub(currentPosition, __move);
10 float distanceToMove = ccpLength(moveDifference);
11 float moveDuration = distanceToMove / __speed;
12
13 __pMoveAction = CCMoveTo::actionWithDuration((ccTime)moveDuration, __move);
14 this->runAction(__pMoveAction);
15 }
First, the previous move action has to be stopped [6] and then the CCMoveTo action is used [13], similarly to the Enemy::start() method.
You can find the SpaceShooter: Episode I code in the attachment. In Episode II, accelerometer will exchange touch events as the ship control mechanism. The ship and enemies will gain animation; also the ship will be able to fire bullets.
Revision History
|
Description |
Date |
|
Initial version |
2012-04-20 |