So far, we have only used the AndEngine class, PhysicsFactory
, to create rectangle (box) bodies. It is the simplest and most straightforward way to create bodies. The whole creation of a body remains hidden.
A body consists of one or more fixtures. Let's create a slightly more complicated body by manually creating three fixtures and putting them together to form a body. We will first create fixtures for the head, torso, and body, just to illustrate how to create multiple fixtures, but we are going to see how filtering works on the example of these fixtures as well.
Here's a screenshot showing how the final body should look in the game:
First, we need to create an empty body. The body is in fact just a virtual holder for fixtures. When we create an empty body and add it to the game, nothing will show and no collisions will happen. We are going to change the createPlayer()
method of the PlayerFactory
class. Here's the new code:
public Player createPlayer(float x, float y) { Player player = new Player(x, y, ResourceManager.getInstance().playerTextureRegion, vbom); player.setZIndex(2); BodyDef bodyDef = new BodyDef(); bodyDef.type = BodyType.DynamicBody; bodyDef.position.x = x / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT; bodyDef.position.y = y / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT; Body playerBody = physicsWorld.createBody(bodyDef); playerBody.setLinearDamping(1f); playerBody.setFixedRotation(true); playerBody.setUserData(player); physicsWorld.registerPhysicsConnector(new PhysicsConnector(player, playerBody)); player.setBody(playerBody); return player; }
We are defining a BodyDef
object that has a position. In the physics world, the units are meters, not pixels. Therefore, we use the pixel-to-meter conversion. We specified that our BodyDef
object defines a dynamic body. Other than this, we keep the code unchanged. Next, we are going to add the fixtures. Each fixture can have different properties. Let's make use of that.
What we want to achieve is almost perfect collisions with the enemy flies. Moreover, right now, the whole body collides with a platform, making it jump even when the head touches it, and this is something we don't really want. Only the legs should propel the player upwards. What we can do is to turn the head into a circular shape sensor.
Sensors do not generate the pre-solve and post-solve events in the contact listener. This makes sense because a sensor can't influence its environment; it's immaterial. However, it will still generate the begin and end contact events that we use to detect the contact with enemies.
Here's a method that will create a circular fixture where the player's head should be. It belongs to the PlayerFactory
class:
private void createHead(Body body) { FixtureDef headFixtureDef = PhysicsFactory.createFixtureDef(1f, 0f, 1f, true); CircleShape circle = new CircleShape(); circle.setRadius(32 / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT); circle.setPosition(new Vector2(0, 12 / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT)); headFixtureDef.shape = circle; body.createFixture(headFixtureDef); }
First, we create a fixture definition in the same way we created it before. Then, we create a circle shape, set its radius in meters, and set its position to the place where the character's head is, again in meters. Then, we assign this shape to the fixture definition and call the correct method in the Body
class to create the fixture itself.
Before we add it to the final physics body, let's create the other fixtures as well.
Circle shapes are easy; they only have a radius and position. To create every other shape, a polygon shape must be created. Polygons are tricky. They must be defined by vertices. Only convex polygon shapes are allowed; therefore, other shapes must be assembled from multiple fixtures. The order of vertices is defined by a simple rule. The "outside" of the shape is always on the right of each side. This means we have to define our vertices in a counterclockwise order, as shown in the following diagram:
Creating the vertices manually is a bit tedious. Let's see how it looks for our trapezoid-shaped torso:
private void createTorso(Body body) { FixtureDef torsoFixtureDef = PhysicsFactory.createFixtureDef(1f, 0f, 0.5f, true); PolygonShape middleBox = new PolygonShape(); final float halfWidth = 30 / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT; final float halfHeight = 8 / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT; final float yShift = - 30 / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT; Vector2[] vertices = new Vector2[4]; vertices[0] = new Vector2(halfWidth, halfHeight + yShift); vertices[1] = new Vector2(-halfWidth, halfHeight + yShift); vertices[2] = new Vector2(-halfWidth * 0.75f, -halfHeight + yShift); vertices[3] = new Vector2(halfWidth * 0.75f, -halfHeight + yShift); middleBox.set(vertices); torsoFixtureDef.shape = middleBox; body.createFixture(torsoFixtureDef); }
The code is not complicated, just long. First, we create the fixture definition and again we want the torso to actually be a sensor. We create a PolygonShape
object and set four vertices to it. The positions are defined using variables called halfWidth
and halfHeight
, so we can easily change all vertices at once. The following figure explains what they mean:
The following diagram shows the final points that are plotted:
Finally, we create the legs. We will use the same method using the polygonal shape as we used when creating the torso. However, this time we will create a rectangle, and we can use a convenience method, setAsBox()
, to define the vertices for us.
Also, we don't want the legs to be a sensor, but rather a regular material part of the body. We are also going to increase the density of the legs to make the final body about the same weight as the original big box. This can be done as follows:
private void createLegs(Body body) { FixtureDef legsFixtureDef = PhysicsFactory.createFixtureDef(4f, 0.2f, 1f, false); PolygonShape legsBox = new PolygonShape(); legsBox.setAsBox(20 / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT, 4 / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT, new Vector2(0, -44 / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT), 0); legsFixtureDef.shape = legsBox; body.createFixture(legsFixtureDef); }
Finally, we assemble the body parts together. This is done in the createPlayer()
method in the PlayerFactory
class, the same place where we created the empty body. We simply call the three methods that we just defined, as follows:
public Player createPlayer(float x, float y) { Player player = new Player(x, y, ResourceManager.getInstance().playerTextureRegion, vbom); player.setZIndex(2); BodyDef bodyDef = new BodyDef(); bodyDef.type = BodyType.DynamicBody; bodyDef.position.x = x / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT; bodyDef.position.y = y / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT; Body playerBody = physicsWorld.createBody(bodyDef); createTorso(playerBody); createHead(playerBody); createLegs(playerBody); playerBody.setLinearDamping(1f); playerBody.setFixedRotation(true); playerBody.setUserData(player); physicsWorld.registerPhysicsConnector(new PhysicsConnector(player, playerBody)); player.setBody(playerBody); return player; }
We can run the game now. It will work almost the same as before, just with more precise collisions and the jumping will only happen when the player actually touches the platform top with legs.
3.147.60.63