Simulating a real physical world in a game can be a daunting task. Usually a physics engine can help make this easier. Cocos2d-x provides a physics engine that is based on the popular Chipmunk physics engine.
A PhysicsBody
holds the physical properties of an object these include mass,
position, rotation, velocity, damping, etc. PhysicsBody
objects are the
backbone for shapes. A PhysicsBody
does not have a shape until you attach a
shape to it.
Materials describe material attributes:
- density:It is used to compute the mass properties of the parent body.
- friction:It is used to make objects slide along each other realistically.
- restitution:It is used to make objects bounce. The restitution value is usually set to be between 0 and 1.
Shapes describe collision geometry. By attaching shapes to bodies, you define a
body’s shape. You can attach as many shapes to a single body as you need in order
to define a complex shape. Each shape relates to a PhysicsMaterial
object and
contains the following attributes: type, area, moment, tag, mass and
offset.
PhysicsShape
: Shapes implement thePhysicsShape
base class.PhysicsShapeCircle
: Circles are solid. You cannot make a hollow circle using the circle shape.PhysicsShapePolygon
: Polygon shapes are solid convex polygons.PhysicsShapeBox
: A Box shape is one kind of convex polygon.PhysicsShapeEdgeSegment
: A segment shape.PhysicsShapeEdgePolygon
: Hollow polygon shapes.A edge-polygon shape consists of multiple segment shapes.PhysicsShapeEdgeBox
:Hollow box shapes.A edge-box shape consists of four segment shapes.PhysicsShapeEdgeChain
: The chain shape provides an efficient way to connect many edges together.
Contacts and joints describe how bodies are attached to each other.
A World
container is what your physics bodies are added to and where they are
simulated. You add bodies, shapes and constraints to a world and then update
the world as a whole. The World
controls how all of these items interact together.
Much of the interaction with the physics API will be with a PhysicsWorld
object.
Now that we have had a bit of overview, let's jump right into the details.
Cocos2d-x providea a PhysicsWorld
as the basic unit of simulation. PhysicsWorld
integrates deeply with Scene
. You can create a Scene
that is a PhysicsWorld
using:
auto scene = Scene::createWithPhysics();
Every PhysicsWorld
has properties associated with it:
- gravity: Global gravity applied to the world. Defaults to Vec2(0.0f, -98.0f).
- speed: Set the speed of physics world, speed is the rate at which the simulation executes. Defaults to 1.0.
- updateRate: set the update rate of physics world, update rate is the value of EngineUpdateTimes/PhysicsWorldUpdateTimes.
- substeps: set the number of substeps in an update of the physics world.
You can disable auto step of physics world by PhysicsWorld::setAutoStep(false)
and step physics world manually by PhysicsWorld::step(time)
.
/** Get physics shapes that contains the point. */
Vector<PhysicsShape*> getShapes(const Vec2& point) const;
/** return physics shape that contains the point. */
PhysicsShape* getShape(const Vec2& point) const;
/** Get all the bodys that in the physics world. */
const Vector<PhysicsBody*>& getAllBodies() const;
/** Get body by tag */
PhysicsBody* getBody(int tag) const;
/** set the debug draw mask */
void setDebugDrawMask(int mask);
PhysicsWorld
objects support three kinds of spatial queries.
Point queries are useful for things like mouse picking and simple sensors. They allow you to check if there are shapes within a certain distance of a point, find the closest point on a shape to a given point or find the closest shape to a point.
You can cast a ray at a shape to get the point of first intersection. For example:
void PhysicsDemoRayTest::tick(float dt)
{
Vec2 d(300 * cosf(_angle), 300 * sinf(_angle));
Vec2 point2 = s_centre + d;
if (_drawNode)
{
removeChild(_drawNode);
}
_drawNode = DrawNode::create();
#define MAX_MULTI_RAYCAST_NUM 5
Vec2 points[MAX_MULTI_RAYCAST_NUM];
int num = 0;
PhysicsRayCastCallbackFunc func = [&points, &num](PhysicsWorld& world,
const PhysicsRayCastInfo& info, void* data)->bool
{
if (num < MAX_MULTI_RAYCAST_NUM)
{
points[num++] = info.contact;
}
return true;
};
s_currScene->getPhysicsWorld()->rayCast(func, s_centre, point2, nullptr);
_drawNode->drawSegment(s_centre, point2, 1, Color4F::RED);
for (int i = 0; i < num; ++i)
{
_drawNode->drawDot(points[i], 3, Color4F(1.0f, 1.0f, 1.0f, 1.0f));
}
addChild(_drawNode);
_angle += 1.5f * (float)M_PI / 180.0f;
}
Rect queries provide a fast way to check roughly which shapes are in an area. It is pretty easy to implement:
PhysicsQueryRectCallbackFunc func = [](PhysicsWorld& world, PhysicsShape& shape,
void* userData)->bool
{
//Return true from the callback to continue rect queries
return true;
}
scene->getPhysicsWorld()->queryRect(func, Rect(0,0,200,200), nullptr);
PhysicsBodies
have position and velocity. You can apply forces, movement,
damping and impulses to PhysicsBody
objects. PhysicsBody
can be static or
dynamic. A static body does not move under simulation and behaves as if it has
infinite mass. A dynamic body is fully simulated. They can be moved manually by
the user, but normally they move according to forces. A dynamic body can collide
with all body types. Cocos2d-x provide Node::setPhysicsBody()
to associate a
PhysicsBody
to a Node
object.
Lets create a static PhysicsBody
that contains a box shape:
auto physicsBody = PhysicsBody::createBox(Size(65.0f, 81.0f),
PhysicsMaterial(0.1f, 1.0f, 0.0f));
physicsBody->setDynamic(false);
//create a sprite
auto sprite = Sprite::create("whiteSprite.png");
sprite->setPosition(s_centre);
addChild(sprite);
//apply physicsBody to the sprite
sprite->setPhysicsBody(physicsBody);
//add five dynamic body
for (int i = 0; i < 5; ++i)
{
physicsBody = PhysicsBody::createBox(Size(65.0f, 81.0f),
PhysicsMaterial(0.1f, 1.0f, 0.0f));
//set the body isn't affected by the physics world's gravitational force
physicsBody->setGravityEnable(false);
//set initial velocity of physicsBody
physicsBody->setVelocity(Vec2(cocos2d::random(-500,500),
cocos2d::random(-500,500)));
physicsBody->setTag(DRAG_BODYS_TAG);
sprite = Sprite::create("blueSprite.png");
sprite->setPosition(Vec2(s_centre.x + cocos2d::random(-300,300),
s_centre.y + cocos2d::random(-300,300)));
sprite->setPhysicsBody(physicsBody);
addChild(sprite);
}
The result is a stationary PhysicsBody
with 5 additional PhysicsBody
objects
colliding around it.
Collisions are what happens when PhysicBody
objects come in contact with each
other.
Collision filtering allows you to prevent collision between shapes. Cocos2d-x supports collision filtering using category and groups bitmasks.
Cocos2d-x supports 32 collision categories. For each shape you can specify which category it belongs to. You also specify what other categories this shape can collide with. This is done with masking bits. For example:
auto sprite1 = addSpriteAtPosition(Vec2(s_centre.x - 150,s_centre.y));
sprite1->getPhysicsBody()->setCategoryBitmask(0x02); // 0010
sprite1->getPhysicsBody()->setCollisionBitmask(0x01); // 0001
sprite1 = addSpriteAtPosition(Vec2(s_centre.x - 150,s_centre.y + 100));
sprite1->getPhysicsBody()->setCategoryBitmask(0x02); // 0010
sprite1->getPhysicsBody()->setCollisionBitmask(0x01); // 0001
auto sprite2 = addSpriteAtPosition(Vec2(s_centre.x + 150,s_centre.y),1);
sprite2->getPhysicsBody()->setCategoryBitmask(0x01); // 0001
sprite2->getPhysicsBody()->setCollisionBitmask(0x02); // 0010
auto sprite3 = addSpriteAtPosition(Vec2(s_centre.x + 150,s_centre.y + 100),2);
sprite3->getPhysicsBody()->setCategoryBitmask(0x03); // 0011
sprite3->getPhysicsBody()->setCollisionBitmask(0x03); // 0011
You can text for collisions by checking and comparing category and collision bitmasks like:
if ((shapeA->getCategoryBitmask() & shapeB->getCollisionBitmask()) == 0
|| (shapeB->getCategoryBitmask() & shapeA->getCollisionBitmask()) == 0)
{
// shapes can't collide
ret = false;
}
Collision groups let you specify an integral group index. You can have all shapes with the same group index always collide (positive index) or never collide (negative index and zero index). Collisions between shapes of different group indices are filtered according the category and mask bits. In other words, group filtering has higher precedence than category filtering.
Each joint type has a definition that derives from PhysicsJoint
. All joints are
connected between two different bodies. One body may static.You can prevent the
attached bodies from colliding with each other by joint->setCollisionEnable(false)
.
Many joint definitions require that you provide some geometric data. Often a joint
will be defined by anchor points.The rest of the joint definition data depends
on the joint type.
- PhysicsJointFixed: A fixed joint fuses the two bodies together at a reference point. Fixed joints are useful for creating complex shapes that can be broken apart later.
- PhysicsJointLimit: A limit joint imposes a maximum distance between the two bodies, as if they were connected by a rope.
- PhysicsJointPin: A pin joint allows the two bodies to independently rotate around the anchor point as if pinned together.
- PhysicsJointDistance: Set the fixed distance with two bodies
- PhysicsJointSpring: Connecting two physics bodies together with a spring
- PhysicsJointGroove: Attach body a to a line, and attach body b to a dot
- PhysicsJointRotarySpring: Likes a spring joint, but works with rotary
- PhysicsJointRotaryLimit: Likes a limit joint, but works with rotary
- PhysicsJointRatchet: Works like a socket wrench
- PhysicsJointGear: Keeps the angular velocity ratio of a pair of bodies constant
- PhysicsJointMotor: Keeps the relative angular velocity of a pair of bodies constant
Contacts are objects created by physics mode to manage collision between two shapes. The contact class is created and destroyed by physics mode. Contact objects are not created by the user. Here is some terminology associated with contacts.
- contact point: A contact point is a point where two shapes touch.
- contact normal: A contact normal is a unit vector that points from one shape
- to another.
You can get the shapes from a contact. From those you can get the bodies.
bool PhysicsContactTest::onContactBegin(PhysicsContact& contact)
{
PhysicsBody* bodyA = contact.getShapeA()->getBody();
PhysicsBody* bodyB = contact.getShapeB()->getBody();
return true;
}
You can get access to contacts by implement a contact listener. The contact listener supports several events: begin, pre-solve, post-solve and separate.
- begin: Two shapes just started touching for the first time this step. Return true from the callback to process the collision normally or false to cause physics engine to ignore the collision entirely. If you return false, the preSolve() and postSolve() callbacks will never be run, but you will still receive a separate event when the shapes stop overlapping.
- pre-solve: Two shapes are touching during this step. Return false from the callback to make physics engine ignore the collision this step or true to process it normally. Additionally, you may override collision values using setRestitution(), setFriction() or setSurfaceVelocity() to provide custom restitution,friction, or surface velocity values.
- post-solve: Two shapes are touching and their collision response has been processed.
- separate: Two shapes have just stopped touching for the first time this step.
You also can use EventListenerPhysicsContactWithBodies, EventListenerPhysicsContactWithShapes, EventListenerPhysicsContactWithGroup to listen the event you interested with bodys, shapes or group. But you also need to set the physics contact related bitmask value, because the contact event won't be received by default, even you create the relative EventListener.
For example:
#define STATIC_SPRITE_TAG 10
bool PhysicsDemoCollisionProcessing::init()
{
if (PhysicsDemo::init()) {
//create a static PhysicsBody
auto sprite = addSpriteAtPosition(s_centre,1);
sprite->setTag(STATIC_SPRITE_TAG);
sprite->getPhysicsBody()->setContactTestBitmask(0xFFFFFFFF);
sprite->getPhysicsBody()->setDynamic(false);
//adds contact event listener
auto contactListener = EventListenerPhysicsContact::create();
contactListener->onContactBegin = CC_CALLBACK_1(PhysicsDemoCollisionProcessing::onContactBegin,
this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener,
this);
schedule(CC_SCHEDULE_SELECTOR(PhysicsDemoCollisionProcessing::tick), 0.3f);
return true;
}
return false;
}
void PhysicsDemoCollisionProcessing::tick(float dt)
{
auto sprite1 = addSpriteAtPosition(Vec2(s_centre.x + cocos2d::random(-300,300),
s_centre.y + cocos2d::random(-300,300)));
auto physicsBody = sprite1->getPhysicsBody();
physicsBody->setVelocity(Vec2(cocos2d::random(-500,500),cocos2d::random(-500,500)));
physicsBody->setContactTestBitmask(0xFFFFFFFF);
}
bool PhysicsDemoCollisionProcessing::onContactBegin(PhysicsContact& contact)
{
auto nodeA = contact.getShapeA()->getBody()->getNode();
auto nodeB = contact.getShapeB()->getBody()->getNode();
if (nodeA && nodeB)
{
if (nodeA->getTag() == STATIC_SPRITE_TAG)
{
nodeB->removeFromParentAndCleanup(true);
}
else if (nodeB->getTag() == STATIC_SPRITE_TAG)
{
nodeA->removeFromParentAndCleanup(true);
}
}
//bodies can collide
return true;
}