Cocos2d-x is a fast, powerful, and easy-to-use open source 2D game engine.
It’s is very similar to Apple’s Sprite Kit, but has one key advantage – Cocos2d-x is cross platform.
This means with one set of code, you can make games for iOS, Android, Windows Phone, Mac OS X, Windows Desktop and Linux. This is huge for indie game devs!
In this tutorial, you will learn how to create a simple 2D game in Cocos2d-x with C++. And yes — there will be ninjas! :]
Getting Started
Download the latest version of Cocos2d-x at www.cocos2d-x.org/download; this tutorial used version 3.5.
Place the downloaded file wheres you’d like to store your Cocos2d-x installation, such as in your home directory, then unzip it.
Open Terminal and cd into the folder you just extracted. For example, if you placed the project in your home directory, run the following command:
cd ~/cocos2d-x-3.5/ |
Now run the following command:
python setup.py |
This sets up the necessary shell environment variables. When it prompts you to configure the Android-specific variables NDK_ROOT
, ANDROID_SDK_ROOT
and ANT_ROOT
, just press Enter three times to finish the configuration.
python
on the command line and it will display the version (then hit Ctrl-D to exit). If you have an older version of Python, install the latest version of Python at python.org.As you can see in the following screenshot, the script instructs you to execute another command to complete the setup:
Enter the command as the script’s output instructed. A great timesaving tip: you can use a tilde (~) in place of /Users/your_user_name, so to save keystrokes you could type the following:
source ~/.zshrc (or source ~/.bash_profile) |
The command you entered simply re-processes your shell configuration and gives it access to the new variables. Now you can call the cocos command in Terminal from any directory.
Run the following command to create a C++ game template named SimpleGame:
cocos new -l cpp -d ~/Cocos2d-x-Tutorial SimpleGame |
This creates a directory named Cocos2d-x-Tutorial in your home directory, and inside that, a sub-directory named SimpleGame that contains your project’s files.
Note: To learn about the available cocos subcommands, type cocos --help or cocos -h. You can also learn about any subcommand’s options by appending “--help” or “-h”, such as cocos new -h to see the options for the new command.
Double-click ~/Cocos2d-x-Tutorial/SimpleGame/proj.ios_mac/SimpleGame.xcodeproj in Finder to open the project in Xcode.
Once you’re Inside Xcode, ensure that SimpleGame Mac is the active scheme, as shown below:
While Cocos2d-x is capable of building games for many platforms, in this tutorial you’ll focus on making an OS X app. Porting this project to other platforms is a trivial (yes, trivial!) matter discussed briefly at the end of this tutorial.
Build and run your app to see the template project in all its glory:
Resolution Setup
By default, Cocos2d-x games are named “MyGame” and have a resolution of 960×640, but those details are easy to change.
Open AppDelegate.cpp and find the following line inside AppDelegate::applicationDidFinishLaunching
:
glview = GLViewImpl::create("My Game"); |
Replace that line with the following code:
glview = GLViewImpl::createWithRect("SimpleGame", Rect(0,0, 480, 320), 1.0); |
This changes the app’s name to “SimpleGame” and sets its resolution to 480×320 to match the background art included with the template.
Build and run your app again to see your new, smaller game:
Notice the third argument you passed to createWithRect
— 1.0
. This parameter scales the frame, which is usually \used for testing resolutions larger than your monitor. For example, to test a 1920×1080 resolution on a monitor smaller than 1920×1080, you could pass 0.5
to scale the window to 960×540.
While this call to createWithRect
changes the game’s frame on desktops, it doesn’t work this way on iOS devices; instead, the game’s resolution matches the screen size. Here is how it looks on an iPhone 6:
So how do you handle multiple resolutions? In this tutorial, you’ll create a single set of game resources based on a 960×640 resolution, then simply scale the assets up or down as necessary at runtime.
To implement this, add the following code inside AppDelegate::applicationDidFinishLaunching
, just above the line that calls setDisplayStats
on director
:
// 1 Size designSize = Size(480,320); Size resourceSize = Size(960,640); // 2 director->setContentScaleFactor(resourceSize.height / designSize.height); director->getOpenGLView()->setDesignResolutionSize( designSize.width, designSize.height, ResolutionPolicy::FIXED_HEIGHT); |
Here’s what the code above does:
- Here you define
designSize
– the size you’re using when creating your game logic – andresourceSize
– the size on which all your art assets are based. - These lines tell your game’s
Director
to scale the assets as necessary based on the design and resource sizes you provided.
For a detailed explanation of how Cocos2d-x handles resolutions, please refer to the Cocos2d-x wiki entry on Multi-resolution adaptation.
Adding a Sprite
Next, download the resources file for this project and unzip it to a convenient location.
Select all the files in the SimpleGameResources folder you just extracted and drag them into the Resources group in your Xcode project. In the dialog that appears, be sure to check Copy items if needed, SimpleGame iOS and SimpleGame Mac before clicking Finish:
Next open HelloWorldScene.h add the following line just after the line that includes cocos2d.h:
using namespace cocos2d; |
This specifies you’ll be using the cocos2d
namespace; this lets you do things like write Sprite*
instead of cocos2d::Sprite*
. It’s not absolutely necessary, but it certainly makes development more pleasant. :]
Now you’ll need a private member variable to point to your player sprite. Add the following code to the HelloWorld
declaration:
private: Sprite* _player; |
Next, open HelloWorldScene.cpp and replace the contents of the HelloWorld::init method with the following:
// 1 if ( !Layer::init() ) { return false; } // 2 auto origin = Director::getInstance()->getVisibleOrigin(); auto winSize = Director::getInstance()->getVisibleSize(); // 3 auto background = DrawNode::create(); background->drawSolidRect(origin, winSize, Color4F(0.6,0.6,0.6,1.0)); this->addChild(background); // 4 _player = Sprite::create("player.png"); _player->setPosition(Vec2(winSize.width * 0.1, winSize.height * 0.5)); this->addChild(_player); return true; |
Here’s the play-by-play of this method:
- First you call the super class’s
init
method. Only if this succeeds do you proceed withHelloWorldScene
‘s setup. - You then get the window’s bounds using the game’s
Director
singleton. - You then create a
DrawNode
to draw a gray rectangle that fills the screen and add it to the scene. This serves as your game’s background. - Finally, you create the player sprite by passing in the image’s name. You position it 10% from the left edge of the screen, centered vertically, and add it to the scene.
Build and run your app; voila, ladies and gentlemen, the ninja has entered the building! :]
Moving Monsters
Your ninja needs a purpose in life, so you’ll add some monsters to your scene for your ninja to fight. To make things more interesting, you want the monsters to move around – otherwise, it wouldn’t prove to be much of a challenge! You’ll create the monsters slightly off-screen to the right, and set up an action for them telling them to move to the left.
First, open HelloWorldScene.h and add the following method declaration:
void addMonster(float dt); |
Then add the following method implementation inside HelloWorldScene.cpp:
void HelloWorld::addMonster(float dt) { auto monster = Sprite::create("monster.png"); // 1 auto monsterContentSize = monster->getContentSize(); auto selfContentSize = this->getContentSize(); int minY = monsterContentSize.height/2; int maxY = selfContentSize.height - monsterContentSize.height/2; int rangeY = maxY - minY; int randomY = (rand() % rangeY) + minY; monster->setPosition(Vec2(selfContentSize.width + monsterContentSize.width/2, randomY)); this->addChild(monster); // 2 int minDuration = 2.0; int maxDuration = 4.0; int rangeDuration = maxDuration - minDuration; int randomDuration = (rand() % rangeDuration) + minDuration; // 3 auto actionMove = MoveTo::create(randomDuration, Vec2(-monsterContentSize.width/2, randomY)); auto actionRemove = RemoveSelf::create(); monster->runAction(Sequence::create(actionMove,actionRemove, nullptr)); } |
It’s relatively straightforward, but here’s what the above code does:
- The first part of this method is similar to what you did earlier with the player: it creates a monster sprite and places it just offscreen to the right. It sets its y-position to a random value to keep things interesting.
- Next, the method calculates a random duration between two and four seconds for the action it’s about to add to the monster. Each monster will move the same distance across the screen, so varying the duration results in monsters with random speeds.
- Finally, the method creates an action that moves the monster across the screen from right to left and instructs the monster to run it. This is explained in more detail below.
Cocos2d-x provides lots of extremely handy built-in actions that help you easily change the state of sprites over time, including move actions, rotate actions, fade actions, animation actions, and more. Here you use three actions on the monster:
MoveTo
: Moves an object from one point to another over a specific amount of time.RemoveSelf
: Removes a node from its parent, effectively “deleting it” from the scene. In this case, you use this action to remove the monster from the scene when it is no longer visible. This is important because otherwise you’d have an endless supply of monsters and would eventually consume all your device’s resources.Sequence
: Lets you perform a series of other actions in order, one at a time. This means you can have the monster move across the scene and remove itself from the screen when it reaches its destination.
There’s one last thing to do before you let you ninja go to town — you need to actually call the method to create monsters! Just to make things fun, you’ll create monsters that spawn continuously.
Simply add the following code to the end of HelloWorld::init
, just before the return
statement:
srand((unsigned int)time(nullptr)); this->schedule(schedule_selector(HelloWorld::addMonster), 1.5); |
srand((unsigned int)time(nullptr));
seeds the random number generator. If you didn’t do this, your random numbers would be the same every time you ran the app. That wouldn’t feel very random, would it? :]
You then pass HelloWorld::addMonster
into the schedule
method, which will call addMonster()
every 1.5 seconds.
Here Cocos2d-x takes advantage of C++’s pointers to member functions. If you don’t understand how this works, please refer to ioscpp for more information.
That’s it! Build and run your project; you should now see monsters happily (or angrily, as the case may be!) moving across the screen:
Shooting Projectiles
Your brave little ninja needs a way to protect himself. There are many ways to implement firepower in a game, but in this project you’ll have the user click or tap the screen to fire a projectile in the direction of the click or tap. Pew-pew! :]
To keep things simple, you’ll implement this with a MoveTo
action — but this means you’ll need to do a little math.
The MoveTo
action requires a destination for the projectile, but you can’t use the input location directly because that point only represents the direction to shoot relative to the player. You want to keep the bullet moving through that point until the bullet arrives at the final destination off-screen.
Here’s a picture that illustrates the matter:
The x and y offsets from the origin point to the touched location create a small triangle; you just need to make a big triangle with the same ratio – and you know you want one of the endpoints to be off-screen.
Performing these calculations is a snap with Cocos2d-x’s included vector math routines. But before you can calculate where to move, you need to enable input event handling to figure out where the user touched!
Add the following code to the end of HelloWorld::init
, just above the return
statement:
auto eventListener = EventListenerTouchOneByOne::create(); eventListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this); this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(eventListener, _player); |
Cocos2d-x versions 3 and up use EventDispatcher
to dispatch various events, such as touch, accelerometer and keyboard events.
Note: Throughout this discussion, the term “touch” refers to taps on a touch device as well as clicks on a desktop. Cocos2d-x uses the same methods for handling both types of events.
In order to receive events from the EventDispatcher
, you need to register an EventListener
. There are two types of touch event listeners:
EventListenerTouchOneByOne
: This type calls your callback method once for each touch event.EventListenerTouchAllAtOnce
: This type calls your callback method once with all of the touch events.
Touch event listeners support four callbacks, but you only need to bind methods for the events you care about.
onTouchBegan
: Called when your finger first touches the screen. If you are usingEventListenerTouchOneByOne
, you must returntrue
in order to receive any of the other three touch events.onTouchMoved
: Called when your finger, already touching the screen, moves without lifting off the screen.onTouchEnded
: Called when your finger lifts off the screen.onTouchCancelled
: Called in certain circumstances that stop event handling, like when you are touching the screen and then something like a phone call interrupts the app.
In this game, you really only care about when touches occur. Declare your callback to receive touch notifications in HelloWorldScene.h
like so:
bool onTouchBegan(Touch *touch, Event *unused_event); |
Then implement your callback in HelloWorldScene.cpp:
bool HelloWorld::onTouchBegan(Touch *touch, Event *unused_event) { // 1 - Just an example for how to get the _player object //auto node = unused_event->getCurrentTarget(); // 2 Vec2 touchLocation = touch->getLocation(); Vec2 offset = touchLocation - _player->getPosition(); // 3 if (offset.x < 0) { return true; } // 4 auto projectile = Sprite::create("projectile.png"); projectile->setPosition(_player->getPosition()); this->addChild(projectile); // 5 offset.normalize(); auto shootAmount = offset * 1000; // 6 auto realDest = shootAmount + projectile->getPosition(); // 7 auto actionMove = MoveTo::create(2.0f, realDest); auto actionRemove = RemoveSelf::create(); projectile->runAction(Sequence::create(actionMove,actionRemove, nullptr)); return true; } |
There’s a lot going on in the method above, so take a moment to review it step by step.
- The first line is commented out, but it’s there to show you how you can access the
_player
object that you passed as the second parameter toaddEventListenerWithSceneGraphPriority(eventListener, _player)
. - Here you get the coordinate of the touch within the scene’s coordinate system and then calculate the offset of this point from the player’s current position. This is one example of vector math in Cocos2d-x.
- If
offset
‘sx
value is negative, it means the player is trying to shoot backwards. This is is not allowed in this game (real ninjas never look back!), so simply return without firing a projectile. - Create a projectile at the player’s position and add it to the scene.
- You then call
normalize()
to convert the offset into a unit vector, which is a vector of length 1. Multiplying that by 1000 gives you a vector of length 1000 that points in the direction of the user’s tap. Why 1000? That length should be enough to extend past the edge of your screen at this resolution :] - Adding the vector to the projectile’s position gives you the target position.
- Finally, you create an action to move the projectile to the target position over two seconds and then remove itself from the scene.
Build and run your app; touch the screen to make your ninja fire away at the oncoming hordes!
Collision Detection and Physics
You now have shurikens flying everywhere — but what your ninja really wants to do is to lay some smack down. So you’ll need some code to detect when your projectiles intersect their targets.
One nice thing about Cocos2d-x is it comes with a physics engine built right in! Not only are physics engines great for simulating realistic movement, but they are also great for detecting collisions. You’ll use Cocos2d-x’s physics engine to determine when monsters and projectiles collide.
Start by adding the following code to HelloWorldScene.cpp, just above the implementation of HelloWorld::createScene
:
enum class PhysicsCategory { None = 0, Monster = (1 << 0), // 1 Projectile = (1 << 1), // 2 All = PhysicsCategory::Monster | PhysicsCategory::Projectile // 3 }; |
These bit masks define the physics categories you’ll need in a bit – no pun intended! :] Here you’ve created two types, Monster
and Projectile
, along with two special values to specify no type or all types. You’ll use these categories to assign types to your objects, allowing you to specify which types of objects are allowed to collide with each other.
Note: You may be wondering what this fancy syntax is. The category on Cocos2d-x is simply a single 32-bit integer; this syntax sets specific bits in the integer to represent different categories, giving you 32 possible categories max. Here you set the first bit to indicate a monster, the next bit over to represent a projectile, and so on.
Next, replace the first line of HelloWorld::createScene
with the following code:
auto scene = Scene::createWithPhysics(); scene->getPhysicsWorld()->setGravity(Vec2(0,0)); scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL); |
This creates a Scene
with physics enabled. Cocos2d-x uses a PhysicsWorld
to control its physics simulation. Here you set the world’s gravity to zero in both directions, which essentially disables gravity, and you enable debug drawing to see your physics bodies. It’s helpful to enable debug drawing while you’re prototyping physics interactions so you can ensure things are working properly.
Inside HelloWorld::addMonster
, add the following code right after the first line that creates the monster sprite:
// 1 auto monsterSize = monster->getContentSize(); auto physicsBody = PhysicsBody::createBox(Size(monsterSize.width , monsterSize.height), PhysicsMaterial(0.1f, 1.0f, 0.0f)); // 2 physicsBody->setDynamic(true); // 3 physicsBody->setCategoryBitmask((int)PhysicsCategory::Monster); physicsBody->setCollisionBitmask((int)PhysicsCategory::None); physicsBody->setContactTestBitmask((int)PhysicsCategory::Projectile); monster->setPhysicsBody(physicsBody); |
Here’s what the above code does:
- Creates a
PhysicsBody
for the sprite. Physics bodies represent the object in Cocos2d-x’s physics simulation, and you can define them using any shape. In this case, you use a rectangle of the same size as the sprite as a decent approximation for the monster. You could use a more accurate shape, but simpler shapes are good enough for most games and more performant. - Sets the sprite to be dynamic. This means that the physics engine will not apply forces to the monster. Instead, you’ll control it directly through the
MoveTo
actions you created earlier. -
Here, you set the category, collision and contact test bit masks:
- Category: defines the object’s type –
Monster
. - Collision: defines what types of objects should physically affect this object during collisions – in this case,
None
. Because this object is also dynamic, this field has no effect but is included here for the sake of completeness. - Contact Test: defines the object types with which collisions should generate notifications –
Projectile
. You’ll register for and handle these notifications just a bit later in the tutorial. - Finally, you assign the physics body to the monster.
- Category: defines the object’s type –
Next, add the following code to HelloWorld::onTouchBegan, right after the line that sets projectile
‘s position:
auto projectileSize = projectile->getContentSize(); auto physicsBody = PhysicsBody::createCircle(projectileSize.width/2 ); physicsBody->setDynamic(true); physicsBody->setCategoryBitmask((int)PhysicsCategory::Projectile); physicsBody->setCollisionBitmask((int)PhysicsCategory::None); physicsBody->setContactTestBitmask((int)PhysicsCategory::Monster); projectile->setPhysicsBody(physicsBody); |
This is very similar to the physics setup you performed for the monsters, except it uses a circle instead of a rectangle to define the physics body. Note that it’s not absolutely necessary to set the contact test bit mask because the monsters are already checking for collisions with projectiles, but it helps make your code’s intention more clear.
Build and run your project now; you’ll see red shapes superimposed over your physics bodies, as shown below:
Your projectiles are set up to hit monsters, so you need to remove both bodies when they collide.
Remember that physics world from earlier? Well, you can set a contact delegate on it to be notified when two physics bodies collide. There you’ll write some code to examine the categories of the objects, and if they’re the monster and projectile, you’ll make them go boom!
First, add the following method declaration to HelloWorldScene.h:
bool onContactBegan(PhysicsContact &contact); |
This is the method you’ll register to receive the contact events.
Next, implement the following method in HelloWorldScene.cpp:
bool HelloWorld::onContactBegan(PhysicsContact &contact) { auto nodeA = contact.getShapeA()->getBody()->getNode(); auto nodeB = contact.getShapeB()->getBody()->getNode(); nodeA->removeFromParent(); nodeB->removeFromParent(); return true; } |
The PhysicsContact
passed to this method contains information about the collision. In this game, you know that the only objects colliding will be monsters and projectiles. Therefore, you get the nodes involved in the collision and remove them from the scene.
Finally, you need to register to receive contact notifications. Add the following lines to the end of HelloWorld::init
, just before the return
statement:
auto contactListener = EventListenerPhysicsContact::create(); contactListener->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegan, this); this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(contactListener, this); |
This creates a contact listener, registers HelloWorld::onContactBegan
to receive events and adds the listener to the EventDispatcher
. Now, whenever two physics bodies collide and their category bit masks match their contact test bit masks, the EventDispatcher
will call onContactBegan
.
Build and run your app; now when your projectiles intersect targets they should disappear:
Most ninjas are silent, but not this one! Time to add some sound to your game.
Finishing Touches
You’re pretty close to having a workable (but extremely simple) game now. You just need to add some sound effects and music (since what kind of game doesn’t have sound!) and some simple game logic.
Cocos2d-x comes with a simple audio engine called CocosDenshion which you’ll use to play sounds.
Note: Cocos2d-x also includes a second audio engine designed to replace the simple audio engine module. However, it is still experimental and it is not available for all the supported platforms, so you won’t use it here.
The project already contains some cool background music and an awesome “pew-pew” sound effect that you imported earlier. You just need to play them!
To do this, add the following code to the top of HelloWorldScene.cpp, after the other #include
statement:
#include "SimpleAudioEngine.h" using namespace CocosDenshion; |
This imports the SimpleAudioEngine
module and specifies that you’ll be using the CocosDenshion
namespace in this file.
Next, add the following defines to HelloWorldScene.cpp, just above the PhysicsCategory
enum:
#define BACKGROUND_MUSIC_SFX "background-music-aac.mp3" #define PEW_PEW_SFX "pew-pew-lei.mp3" |
Here you define two string constants: BACKGROUND_MUSIC_SFX
and PEW_PEW_SFX
. This simply keeps your filenames in a single place, which makes it easier to change them later. Organizing your code like this (or even better, using a completely separate file) makes it easier to support platform-specific changes like using .mp3 files on iPhone and .wav files on Windows Phone.
Now add the following line to the end of HelloWorld::init
, just before the return
statement:
SimpleAudioEngine::getInstance()->playBackgroundMusic(BACKGROUND_MUSIC_SFX, true); |
This starts the background music playing as soon as your scene is set up.
As for the sound effect, add the following line to HelloWorld::onTouchBegan
, just above the return
statement:
SimpleAudioEngine::getInstance()->playEffect(PEW_PEW_SFX); |
This plays a nice “pew-pew” sound effect whenever the ninja attacks. Pretty handy, eh? You can play a sound effect with only a single line of code.
Build and run, and enjoy your groovy tunes!
Where to Go From Here?
Here is the finished example game from the above tutorial.
I hope you enjoyed learning about Cocos2d-x and feel inspired to make your own game! To learn more about Cocos2d-x, check out the Cocos2d-x website for great learning resources.
If you have any questions or comments about this tutorial, please join the discussion below!
Cocos2d-x Tutorial for Beginners is a post from: Ray Wenderlich
The post Cocos2d-x Tutorial for Beginners appeared first on Ray Wenderlich.