Cocos2d-x Tutorial for Beginners

In this Cocos2d-x tutorial, learn how to create a basic cross-platform game for iOS, Android, and more using C++! By Guanghui Qu.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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:

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.
  1. 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.
  2. 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.

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:

debug_ninjas

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:

ninja_shooting

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!

Guanghui Qu

Contributors

Guanghui Qu

Author

Over 300 content creators. Join our team.