Mastering AndEngine Game Development
上QQ阅读APP看书,第一时间看更新

Integrating Box2D

Adding the Box2D physics engine to AndEngine is easy. Simply obtain a copy of the AndEnginePhysicsBox2DExtension project (found at https://github.com/nicolasgramlich/AndEnginePhysicsBox2DExtension) and link it into your own project in the same way as you added the AndEngine project to your project before. Add the libandenginephysicsbox2dextension.so library found in the /libs/armeabi/ folder of the extension project to the same folder of your own project if it's reported as missing during the running of your project. Normally, this copying is done automatically, but you may have to do it manually.

The first real challenge is making the Box2D physics engine work with the 3D models for which we integrated code in the previous chapter. To this end, we have to modify the following files:

  • MainActivity.java
  • Actor3D.java

MainActivity.java

Most of the changes have to be made in the MainActivity.java file. To the global class variables, we add the following:

  private static final FixtureDef FIXTURE_DEF = PhysicsFactory.createFixtureDef(1, 0.5f, 0.5f);
  private PhysicsWorld mPhysicsWorld;

The FixtureDef type defines the properties of a physics object. In this case, they are density, elasticity, and friction. These are set to reasonable values. We then modify the onCreateScene callback function:

  public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback)
      throws IOException {
    mScene = new Scene();
    
    mPhysicsWorld = new PhysicsWorld(new Vector2(0, (SensorManager.GRAVITY_EARTH * -1)), false);
    mScene.registerUpdateHandler(mPhysicsWorld);
    
    pOnCreateSceneCallback.onCreateSceneFinished(mScene);
        }

Here, we add the physics-world-related initialization. We create a new physics world with a gravity vector set to earth-level gravity (9.8g), though we have to invert it because of the Y-axis reversal of the branch of AndEngine we're using. Otherwise, we would have objects falling upwards, as the following diagram details:

MainActivity.java

Finally, we tell the new physics world that it's not allowed to sleep. This tells the simulation whether or not to simulate inactive bodies, which can improve performance but may lead to simulation inaccuracies. The resulting physics world is then registered with our scene so that the physics world can update the objects in the scene.

Next, we just have to create a physics body for our single model. This is done at the end of the onPopulateScene function, before we add the Scene3D instance to Scene:

  Body body;
  body = PhysicsFactory.createBoxBody(mPhysicsWorld, 0, 0, 10.0f, 10.0f, BodyType.DynamicBody, FIXTURE_DEF);
  mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(mActor3D, body, true, true));

This uses the factory class of the Box2D extension to create a new box body object, which is one of the standard rigid body types of Box2D. This object will be instantiated inside the physics world that we specify as the first parameter. We also set the location (center at the origin) and the body type and use the fixture we created earlier.

Finally, we create a new physics connector. What this does is establish a link between the physics object and the model (mActor3D). The PhysicsConnector class is configured by the last two boolean parameters to send updates for position and rotation changes.

With that, we are done with the changes to the MainActivity class. We just need to update the Actor3D class now to make everything work.

Actor3D.java

The main change we have to make in the Actor3D class is to make it implement the IEntity interface so that it can be used with the physics connector and receive updates. As it would be very messy to have this class directly implement the IEntity interface due to the many dozens of functions specified by this interface, we fortunately have an easier option:

public class Actor3D extends Entity {

By extending from AndEngine's own Entity class, we indirectly implement the IEntity interface, and we only have to override two functions in it. These are used to set the position and rotation respectively:

  @Override
  public void setPosition(final float pX, final float pY) {
    actorToWorldMatrix[12] = pX;
    actorToWorldMatrix[13] = pY;
  }

This function is pretty basic. The Box2D physics world sends position updates for each object that has this type of update enabled and for which a physics connector exists. AndEngine translates this position and calls this function in the relevant entity. Here, we directly update our actor's position in the world matrix using the new coordinates:

  @Override
  public void setRotation(final float pRotation) {
    double rads = Math.toRadians(pRotation);
    actorToWorldMatrix[0] *= Math.cos(rads);
    actorToWorldMatrix[1] *= Math.sin(rads);
    actorToWorldMatrix[4] *= Math.sin(rads);
    actorToWorldMatrix[4] *= -1;
    actorToWorldMatrix[5] *= Math.cos(rads);
  }

The value given to this function is in degrees, which means that we have to convert it to radians for it to be useful for our matrix. The resulting value (in radians) is then used in a standard z-axis-oriented rotation without an intermediate transformation matrix. As the transformation is relatively straightforward, we can apply it directly.

With that, we have made all the changes required to enable our 3D models to make use of the Box2D extension provided with AndEngine. When we run the resulting application, we can see that our sample model object starts off in the center of the screen as usual, and then falls down, off the screen. This follows the parameters that we set for the physics world (the gravity vector) and the model (density).