In this tutorial you are going to learn:
In this tutorial you are going to learn:
How physics enabled games work in cocos2d-x
How to create physics collision shapes for cocos2d-x
How to load the shapes in your game
How to set up a simple scene with dropping objects
I assume that you already have basic knowledge about cocos2d-x. If not please check out our tutorial Using sprite sheet animations in cocos2d-x — it explains the basics setting up a simple game scene.
The complete source code for this tutorial is available on github.
This screenshot shows you what the demo project from the tutorial looks like:
There's a bunch of different objects that you can drop on the scene by tapping / clicking in the game scene.
The objects fall down and nicely collide with other objects.
The nice thing about cocos2d-x is that it already comes with everything you need to create cool games. This includes a physics engine called Chipmunk.
So everything you need to get the engine running is to initialize it during scene creation:
auto scene = Scene::createWithPhysics();
The next step is to set the gravity vector (if you want to enable gravity):
scene->getPhysicsWorld()->setGravity(Vec2(0, -900));
You can also enable debug drawing during — which is quite helpful to see if your collision shapes are created as you expect them to be or if you expect some strange behaviour:
// optional: enable debug draw
scene->getPhysicsWorld()->setDebugDrawMask(0xffff);
With this setup your are ready to add sprites to your scene:
auto sprite = Sprite::create("banana.png");
sprite->setPhysicsBody(body);
sprite->setPosition(pos);
addChild(sprite);
The important line of code is the sprite->setPhysicsBody(body)
—
it converts your normal sprite into a shape controlled by the simulation.
But where to get the PhysicsBody body
from?
cocos2d-x contains several constructors for PhysicsBody
objects:
PhysicsBody::createCircle
— which creates a circle shape with a given radius
PhysicsBody::createBox
— which creates a rectangle shape with a given size
PhysicsBody::createPolygon
— which creates collision polygon
Creating a circle or box is simple — but creating the collision shapes for a complex object is not. Especially since the polygon has to meet 2 conditions:
The vertices of the polygon have to be in clockwise winding.
The polygon must be convex.
This means that every convex polygon is ok. But concave polygons have to be split into multiple convex polygons:
Yes — you are right — this is a tedious job. I personally got fed up with it directly after creating my first object...
We've developed an application for you to simplify the whole work: It's called PhysicsEditor. The editor looks like this:
And you can download it from here — It's available for Windows, MacOS and Linux:
PhysicsEditor costs some bucks but believe me: You'll love it. Instead of creating the shapes manually it'll really help you speed up your development. It comes with a 7 days trial — long enough to get through this tutorial.
After installing PhysicsEditor you'll first have to choose the framework you want to develop with — which is of course cocos2d-x . Choose it from the Exporter settings in the top right panel.
Now drag & drop your sprites on the left panel.
Let's create some shapes manually — before doing it all automatically. E.g. let's start with the crate.png from the demo project.
Click on the Polygon shape in the toolbar to create a triangle.
Basic editing:
You can also add multiple fixtures to a single body to build more complex objects. Use the Circle tool to add circles.
The small blue circle with the cross is the pivot / anchor point of your sprite. It's applied to the sprite and can be used for aligning and positioning the sprite in your game scene.
This is already a big improvement over creating the shapes by looking up coordinates in a graphics tool. But it gets even better....
The Shape tracer reduces your work even more:
The most important setting here is the Tolerance . It allows you to control the shape creation.
It's now an act of balance: Find the best tolerance value that gives you a good shape coverage while keeping the vertex count low.
Not enough vertices, bad shape coverage.
Good shape coverage, but way too many vertices.
Good shape coverage, good amount of vertices.
There are 2 levels of physics parameters: Body and fixture level. The body level parameters effect the whole body and all fixtures inside. The fixture level parameters only effect the fixture itself.
These parameters belong to only 1 part of the body.
This allows you to create fixtures for different purposes:
Object that collide with others — e.g. the fruit shapes from the demo project.
Object that collide and report collisions — e.g. a bullet hitting an enemy.
Sensors that don't collide but report contact with a certain type of other object. You can use this to trigger events if the hero passes a certain spot in a game scene. Or you can make the hero look up if something is dropping going to drop onto his head.
These parameters are all stored as part of the data file PhysicsEditor creates for you. The parameters are automatically applied as soon as you create an object.
If you finished editing the sprites press the Publish button in the top toolbar. This exports an xml file that you can load in your game. Make sure that the data file is written to your Resources folder and that you include it in your project.
PhysicsEditor requires a loader to read the shapes at runtime. The loader code is open source and available from our loader repository. The example project already contains the loader for cocos2d-x.
The loader class is called PhysicsShapeCache
and is implemented as a singleton to make
it easy to access the data from everywhere. You can access the loader instance using PhysicsShapeCache::getInstance()
.
Use addShapesWithFile("filename")
to load a shapes definition file.
You can also load multiple files at once. Just make sure that the sprites have different names.
To unload the shapes use removeShapesWithFile("filename")
.
To get the PhysicsBody
structure of a shape use createBodyWithName("name of the sprite")
.
The convenience method setBodyOnSprite("name of the sprite", sprite)
directly sets the body on the given sprite.
The createScene
method creates the physics world scene, and the
gravity vector.
Scene* HelloWorld::createScene()
{
// create the scene with physics enabled
auto scene = Scene::createWithPhysics();
// set gravity
scene->getPhysicsWorld()->setGravity(Vec2(0, -900));
// optional: set debug draw
// scene->getPhysicsWorld()->setDebugDrawMask(0xffff);
auto layer = HelloWorld::create();
scene->addChild(layer);
return scene;
}
This code block loads the physics shapes from the Shapes.plist created with PhysicsEditor.
It also adds a background image which has no physics properties, the ground sprite (static
physics object) and drops a banana. The spawnSprite("name")
loads a sprite and
sets the physics shape.
Finally there's a touch listener setup to add new shapes when the screen is touched or the mouse is clicked in the scene.
bool HelloWorld::init()
{
if ( !Layer::init() )
{
return false;
}
auto pos = Vec2(Director::getInstance()->getVisibleSize()) / 2 +
Director::getInstance()->getVisibleOrigin();
// Load shapes
shapeCache = PhysicsShapeCache::getInstance();
shapeCache->addShapesWithFile("Shapes.plist");
// Load background image
Sprite *background = Sprite::create("background.png");
background->setPosition(pos);
addChild(background);
// Add ground sprite and drop a banana
spawnSprite("ground.png", pos);
spawnSprite("banana.png", pos);
// Add touch listener
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchesBegan, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
return true;
}
This code block creates a sprite and sets its physics body. It also adds the sprite to the scene.
void HelloWorld::spawnSprite(const std::string &name, Vec2 pos)
{
// create a sprite with the given image name
auto sprite = Sprite::create(name);
// attach physics body
shapeCache->setBodyOnSprite(name, sprite);
// set position and add it to the scene
sprite->setPosition(pos);
addChild(sprite);
}
The onTouchesBegan()
method spawns random objects at the
position where the touch was detected.
bool HelloWorld::onTouchesBegan(Touch *touch, Event *event)
{
auto touchLoc = touch->getLocation();
static int i = 0;
static std::string sprites[] = { "banana.png", "cherries.png", "crate.png", "orange.png" };
spawnSprite(sprites[i], touchLoc);
i = (i + 1) % (sizeof(sprites)/sizeof(sprites[0]));
return false;
}
Well — that's it for the start. You've now learned how easy it is to add physics shapes to your game.