Cocos2d-x Developer Guide 10: Physics Engine

Suppose our game development is going very smoothly. Sprite objects and game mechanics have been added to the game. At this time we began to feel: it is a game for people to play. But in fact, it is not that simple, because next we will find that our game also needs to simulate the real world. What should we do? Especially those real physical environments. Because this will involve collision detection, gravity, elasticity, friction and other physical principles, these are called physics engines in development. In this chapter, we will learn about the physics engine and how to use it. Let us explore when, where, and why we use physics engines.

       The physics engine is terrible, do I really need it? Please tell me that's not the case!

       Don't run away, the physics engine is not actually a monster hiding under your bed ready to scare you to death. Perhaps your needs are simple, so simple that you don't need to use a physics engine. Maybe the node object, update() function, Rect object, containsPoint() function or intersectsRect() function are all you need. E.g:

void update(float dt)

{

auto p = touch->getLocation();

auto rect = this->getBoundingBox();

if(rect.containsPoint(p))

{

// do something, intersection

}

}

       The above system can meet simple needs, but it cannot be expanded. If you have 100 sprites, and all these sprites need to be constantly updated to detect overlaps with other objects, what should you do? With the above system, this can be achieved, but it will seriously consume CPU Usage rate and affect the frame rate. There is no way to continue playing your game. The PhysicsEngine helps us solve these problems, and it is extensible, and it will not cause excessive pressure on the CPU. This may seem a bit strange, but let's look at a simple example. After that, let's look at a simple example, and strive to combine concepts, terminology and practice.

// create a static PhysicsBody

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(Vec2(400, 400));

// sprite will use physicsBody

sprite->setPhysicsBody(physicsBody);

//add contact event listener

auto contactListener = EventListenerPhysicsContact::create();

contactListener-> onContactBegin = CC_CALLBACK_1 (onContactBegin, this);

_eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);

        Although the above example is very simple, you may still find it to be a bit scary? It doesn't matter, let's analyze it more carefully, and we will find that it is not that complicated. The creation steps in the code are: create a PhysicsBody object. Create Sprite sprites. Sprite objects use the properties of the PhysicsBody object. A listener is created in response to the onContactBegin event.

       Once we analyze it step by step, these concepts become easy to understand. Understanding the following terms and concepts will help you better understand all the details of the physics engine:

Physics engine terms and concepts

rigid body

The PhysicsBody object contains the physical properties of an object. These attributes include: mass, position, spin, speed, and decay. The PhysicsBody object is the core of the shape. After you associate the shape with the PhysicsBody, the PhysicsBody object can have a shape.

Material

Material describes the following properties of the material:

Density: It is used to calculate the mass properties of the matrix.

Friction: It is used for relative movement between objects.

Restoration coefficient: It is used to make objects bounce. The coefficient of restitution is generally set between 0 and 1. 0 means no rebound, 1 means complete rebound.

shape

       The shape describes the geometric properties of the collision. Binding the shape to a rigid body defines the shape of a rigid body. If necessary, you can associate countless shapes with a rigid body. This is a way to define complex shapes. Each shape is related to a PhysicsMaterial object and has the following attributes: type (type), area (area), mass (mass), moment (torque), offset (offset/center of gravity) and tag (tag) . Maybe you are still new to some of them:

type: describes a series of shapes, such as circles, rectangles, polygons, etc.

area: used to calculate the mass of a rigid body. Density and volume determine the mass of a rigid body.

mass: The amount of matter contained in a rigid body can be measured in two ways: the acceleration of the object under a given force, or the force of the object in a gravitational field.

moment: Determines the torque required to obtain a specific angular acceleration.

offset: The amount of offset relative to the center of gravity of the rigid body in the current coordinates of the rigid body.

tag: Used to make it easier for developers to determine the shape. Do you probably remember? You can assign a label to all nodes for identification and easier access.

We describe the different shapes like this:

PhysicsShape: shapes implement the base class of PhysicsShape.

PhysicsShapeCircle: The circle is solid. You can't use circle shape to realize hollow circle.

PhysicsShapePolygon: Polygon shape refers to a solid and convex polygon.

PhsicsShapeBox: Box shape is a kind of convex polygon.

PhysicsShapeEdgeSegment: A shape of a line segment.

PhysicsShapePolygon: Hollow polygon. An edge of a polygon composed of multiple line segments.

PhysicsShapeEdgeBox: Hollow rectangular shape. A rectangular edge composed of four line segments.

PhysicsShapeEdgeChain chain shape (chain shape) can effectively connect many edges.

Connection/joint

The contacts and joint objects describe the way rigid bodies are related to each other.

World

       Physical rigid bodies are added to a container called World, which is also the place where they are simulated. Add objects such as bodies, shapes, constraints to the physical world, and update the entire physical world as a whole. The physical world determines how all these components interact together. Among them, many of the interactions implemented with the physics API are related to the PhysicsWorld object.

      There are many things to remember here. Please keep these terms by your side, so that you can refer to them when you need them.

Physical world and physical rigid body

Physical world

       The PhysicsWorld object is a core component in physics simulation. The physical world (PhysicsWorld) is closely integrated with the scene (Scene). Let's look at an example that we will all be involved in. Does the house you live in have a kitchen? When you think about this question, it’s like thinking about your physical world! Now, your world has some PhysicsBody objects, just like food, knives, and electrical appliances. Things are the same! In this world, these rigid bodies interact. They touch each other and respond to each other's contact. For example: cut the food with a knife and put it in the appliance. Did the knife cut the food? It might. Maybe not yet. Maybe this knife is simply not suitable for this.

      You can create a Scene object containing PhysicsWorld in the following way:

1auto scene = Scene::createWithPhysics();

Every PhysicsWorld (PhysicsWorld) has attributes related to it:

-Gravity: global gravity, applied to the entire physical world. The default value is Vec2(0.0f,-98.0f). -Speed: Set the speed of the physical world. Here, speed refers to a ratio of this simulated world movement. The default value is 1.0. -Refresh rate: Set the refresh rate of the physical world, where the refresh rate refers to the ratio of EngineUpdateTimes/PhysicsWorldUpdateTimes. -Substeps (substeps): Set the number of substeps for each refresh in the physical world.

The process of refreshing the physical world is also called stepping. According to the default settings, the physical world will continuously refresh automatically. This is called "auto stepping" and it happens automatically. You can disable the auto step of a physics world by setting PhysicsWorld::setAutoStep(false), and then manually refresh PhysicsWorld by setting PhysicsWorld::step(time). Substeps use more precise time increments than a single frame to refresh the physical world. Using it, we can achieve more detailed control of the stepping process, including smoother movements.

Physical rigid body

      PhyicsBody objects have two attributes: position and velocity. You can apply forces, movement, damping and impulses on PhysicsBody. The physical rigid body can be static or dynamic. A static rigid body does not move in the simulated world, it looks like it has infinite mass. The dynamic rigid body is a fully simulated simulation. It can be moved manually by the user, but it is more common that they are moved by force. Dynamic rigid bodies can collide with all rigid body types. Cocos2d-x provides Node::setPhysicsbody() to associate a physical rigid body with a node object.

       Let's create a static physical rigid body object and 5 dynamic physical rigid body objects, and set them as rectangles:

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 bodies

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);

}

       As a result, five dynamic physical rigid body objects continuously collide around a static physical rigid body object.

 

 

collision

      Have you ever experienced a car accident? Have you collided with something? Just like a car, physical rigid objects can touch each other. When they touched, there was a collision. When a collision occurs, it can be completely ignored, or it can cause a series of events.

Collision screening

      Collision filtering allows you to enable or prevent collisions between shapes. The physics engine supports the use of type and group bit mask to filter collisions.

There are 32 collision types supported in Cocos2d-x. For each shape, you can specify its type. You can also specify which types can collide with this shape. These are done through masks. E.g:

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

The occurrence of a collision can be determined through detection, type comparison, and collision mask:

if ((shapeA->getCategoryBitmask() & shapeB->getCollisionBitmask()) == 0 || (shapeB->getCategoryBitmask() & shapeA->getCollisionBitmask()) == 0)

{

// shapes can't collide

ret = false;

}

 

 

       The collision group allows you to specify the index of a comprehensive group. You can make all shapes with the same set of indexes always collide (positive index) or never collide (negative index or zero index). The collisions between different shapes of the group index can be filtered according to the type and mask. In other words, group filtering has a higher priority than type filtering.

Connection/joint

       Remember the terminology from before? A joint is a way of connecting contact points together. Yes, you can compare it to the joints of your body. Each joint has a definition obtained from the PhysicsJoint object. Between two different rigid bodies, all the joints are connected together. The rigid body can be static. You can use joint->setCollisionEnable(false) to avoid the collision of related rigid bodies. The definition of many joints requires you to provide some geometric data. In many cases, joints are defined by anchor points. The rest of the joint definition data depends on the type of joint.

PhysicsJointFixed: Fix the joint at a specific point and combine the two rigid bodies together. If you want to create some complex shapes that will break in the future, fixing the joints is very useful.

PhysicsJointLimit: A limit joint that uses the maximum distance between two rigid bodies, just like two rigid bodies are connected by a rope.

PhysicsJointPin: Needle joint allows two rigid bodies to independently rotate around the anchor point, as if they were nailed together.

PhysicsJointDistance: Set a fixed distance between two rigid bodies.

PhysicsJointSpring: Use a spring to connect two physical rigid bodies

PhysicsJointGroove: Connect one rigid body to the line and the other to the point.

PhysicsJointRotarySpring: Similar to the spring joint, but with the addition of spin

PhysicsJointRotaryLimit: similar to limiting joints, but with increased spin

PhysicsJointRatchet: similar to the work of a socket wrench

PhysicsJointGear: Keep the angular velocity ratio of a pair of rigid bodies constant.

PhysicsJointMotor: Keep the relative angular velocity of a pair of rigid bodies constant.

 

 

Impact checking

Contacts are objects created by the physics engine to manage the collision between two shapes. The Contact object is created automatically, not by the user. There are several terms associated with it.

contact point: contact point is the point where two shapes touch.

contact normal: contact normal refers to the unit vector from one shape to another.

You can get the PhysicsShape from a collision. From it, you can get rigid bodies.

bool onContactBegin(PhysicsContact& contact)

{

auto bodyA = contact.getShapeA()->getBody();

auto bodyB = contact.getShapeB()->getBody();

return true;

}

      You can access the collision by using the contact listener. The contact listener supports multiple events: begin, pre-solve, post-solve, and separate.

begin: In this step, the two shapes have just begun to touch. Returning true from the callback function can make the collision happen normally. If it returns false, the physics engine will ignore the collision entirely. If it returns false, the preSolve() and postSolve() callback functions will be disabled, but when the two shapes stop overlapping, you can still receive a separate event.

pre-solve: In this step, the two shapes touch each other. If the false value is returned in the callback function, the physics engine will ignore the collision; if it returns true, the collision will proceed normally. In addition, you can use the setRestitution() and setSurfaceVelocity() functions to ignore the collision value, so that you can provide custom restitution coefficient, friction coefficient and surface velocity values.

post-solve: Two shapes touch, and the collision between them has been processed.

separate: In this step, the two shapes just stopped touching.

You can also use EventListenerPhysicsContactWithBodies, EventListenerPhysicsContactWithShapes, EventListenerPhysicsContactWithGroup to monitor some events of the rigid bodies, shapes and groups you are interested in. In addition, you also need to set a mask related to physical contact, because even if you create a related EventListener, collision events will not be received by default.

E.g:

bool init()

{

//create a static PhysicsBody

auto sprite = addSpriteAtPosition(s_centre,1);

sprite->setTag(10);

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 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 onContactBegin(PhysicsContact& contact)

{

auto nodeA = contact.getShapeA()->getBody()->getNode();

auto nodeB = contact.getShapeB()->getBody()->getNode();

if (nodeA && nodeB)

{

if (nodeA->getTag() == 10)

{

nodeB->removeFromParentAndCleanup(true);

}

else if (nodeB->getTag() == 10)

{

nodeA->removeFromParentAndCleanup(true);

}

}

//bodies can collide

return true;

}

 

 

Inquire

      Have you ever stood in one place and looked around? You can see things that are close to you and things that are far away. You can judge how far they are from you. The physics engine provides similar spatial query functions. The query currently supported by the PhysicsWorld object includes point query, ray query and rectangle query.

Point query

When you encounter something, such as your table, you can think of this as an example of a point query. This allows you to check if there is a shape within a certain distance around a point. Point query is very useful for mouse picking and simple sensors. You can also find the closest point to a certain point on a shape, or find the closest shape to a certain point.

Ray query

When you look around, certain objects in your line of sight will definitely attract your attention. When like this, you basically execute a ray query. You keep scanning until something interesting makes you stop. You can use a ray query on a shape to get the first intersection. E.g:

void tick(float dt)

{

Vec2 d (300 * cosf (_angle), 300 * sinf (_angle));

Vec2 point2 = s_centre + d;

if (_drawNode)

{

removeChild(_drawNode);

}

_drawNode = DrawNode::create();

Vec2 points[5];

int num = 0;

auto func = [&points, &num](PhysicsWorld& world,

const PhysicsRayCastInfo& info, void* data)->bool

{

if (num < 5)

{

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;

}

 

 

Rectangle query

Rectangle queries provide a quick way to roughly check the shapes that exist in the area. It is very easy to implement:

auto 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);

A few examples of using a Rect query while doing a logo smash:

Here are a few examples of using rectangle queries when making impact logos:

 

 

 

 

Disable physics engine

       It is a good idea to use the built-in physics engine. It is stable and powerful. However, sometimes you will want to use some other physics engine. At this time, you only need to disable CC_USE_PHYSICS in base/ccConfig.h.

Guess you like

Origin blog.csdn.net/qq_21743659/article/details/108660296