(For more resources related to this topic, see here.)
Introduction to the Box2D physics extension
Physics-based games are one of the most popular types of games available for mobile devices. AndEngine allows the creation of physics-based games with the Box2D extension. With this extension, we can construct any type of physically realistic 2D environment from small, simple simulations to complex games. In this recipe, we will create an activity that demonstrates a simple setup for utilizing the Box2D physics engine extension. Furthermore, we will use this activity for the remaining recipes in this article.
Getting ready...
First, create a new activity class named PhysicsApplication that extends BaseGameActivity and implements IAccelerationListener and IOnSceneTouchListener.
How to do it...
Follow these steps to build our PhysicsApplication activity class:
Create the following variables in the class:
public static int cameraWidth = 800;
public static int cameraHeight = 480;
public Scene mScene;
public FixedStepPhysicsWorld mPhysicsWorld;
public Body groundWallBody;
public Body roofWallBody;
public Body leftWallBody;
public Body rightWallBody;
We need to set up the foundation of our activity. To start doing so, place these four, common overridden methods in the class to set up the engine, resources, and the main scene:
@Override
public Engine onCreateEngine(final EngineOptions
pEngineOptions) {
return new FixedStepEngine(pEngineOptions, 60);
}
@Override
public EngineOptions onCreateEngineOptions() {
EngineOptions engineOptions = new EngineOptions(true,
ScreenOrientation.LANDSCAPE_SENSOR, new
FillResolutionPolicy(), new Camera(0,0, cameraWidth, cameraHeight));
engineOptions.getRenderOptions().setDithering(true);
engineOptions.getRenderOptions().
getConfigChooserOptions()
.setRequestedMultiSampling(true);
engineOptions.setWakeLockOptions(
WakeLockOptions.SCREEN_ON);
return engineOptions;
}
@Override
public void onCreateResources(OnCreateResourcesCallback
pOnCreateResourcesCallback) {
pOnCreateResourcesCallback.
onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback
pOnCreateSceneCallback) {
mScene = new Scene();
mScene.setBackground(new Background(0.9f,0.9f,0.9f));
pOnCreateSceneCallback.onCreateSceneFinished(mScene);
}
Continue setting up the activity by adding the following overridden method, which will be used to populate our scene:
@Override
public void onPopulateScene(Scene pScene,
OnPopulateSceneCallback pOnPopulateSceneCallback) {
}
Next, we will fill the previous method with the following code to create our PhysicsWorld object and Scene object:
mPhysicsWorld = new FixedStepPhysicsWorld(60, new
Vector2(0f,-SensorManager.GRAVITY_EARTH*2),
false, 8, 3);
mScene.registerUpdateHandler(mPhysicsWorld);
final FixtureDef WALL_FIXTURE_DEF =
PhysicsFactory.createFixtureDef(0, 0.1f,
0.5f);
final Rectangle ground =
new Rectangle(cameraWidth / 2f, 6f,
cameraWidth - 4f, 8f,
this.getVertexBufferObjectManager());
final Rectangle roof =
new Rectangle(cameraWidth / 2f, cameraHeight –
6f, cameraWidth - 4f, 8f,
this.getVertexBufferObjectManager());
final Rectangle left =
new Rectangle(6f, cameraHeight / 2f, 8f,
cameraHeight - 4f,
this.getVertexBufferObjectManager());
final Rectangle right =
new Rectangle(cameraWidth - 6f,
cameraHeight / 2f, 8f,
cameraHeight - 4f,
this.getVertexBufferObjectManager());
ground.setColor(0f, 0f, 0f);
roof.setColor(0f, 0f, 0f);
left.setColor(0f, 0f, 0f);
right.setColor(0f, 0f, 0f);
groundWallBody =
PhysicsFactory.createBoxBody(
this.mPhysicsWorld, ground,
BodyType.StaticBody, WALL_FIXTURE_DEF);
roofWallBody =
PhysicsFactory.createBoxBody(
this.mPhysicsWorld, roof,
BodyType.StaticBody, WALL_FIXTURE_DEF);
leftWallBody =
PhysicsFactory.createBoxBody(
this.mPhysicsWorld, left,
BodyType.StaticBody, WALL_FIXTURE_DEF);
rightWallBody =
PhysicsFactory.createBoxBody(
this.mPhysicsWorld, right,
BodyType.StaticBody, WALL_FIXTURE_DEF);
this.mScene.attachChild(ground);
this.mScene.attachChild(roof);
this.mScene.attachChild(left);
this.mScene.attachChild(right);
// Further recipes in this chapter will require us
to place code here.
mScene.setOnSceneTouchListener(this);
pOnPopulateSceneCallback.onPopulateSceneFinished();
The following overridden activities handle the scene touch events, the accelerometer input, and the two engine life cycle events—onResumeGame and onPauseGame. Place them at the end of the class to finish this recipe:
@Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent
pSceneTouchEvent) {
// Further recipes in this chapter will require us
to place code here.
return true;
}
@Override
public void onAccelerationAccuracyChanged(
AccelerationData pAccelerationData) {}
@Override
public void onAccelerationChanged(
AccelerationData pAccelerationData) {
final Vector2 gravity = Vector2Pool.obtain(
pAccelerationData.getX(),
pAccelerationData.getY());
this.mPhysicsWorld.setGravity(gravity);
Vector2Pool.recycle(gravity);
}
@Override
public void onResumeGame() {
super.onResumeGame();
this.enableAccelerationSensor(this);
}
@Override
public void onPauseGame() {
super.onPauseGame();
this.disableAccelerationSensor();
}
How it works...
The first thing that we do is define a camera width and height. Then, we define a Scene object and a FixedStepPhysicsWorld object in which the physics simulations will take place. The last set of variables defines what will act as the borders for our physics-based scenes.
In the second step, we override the onCreateEngine() method to return a FixedStepEngine object that will process 60 updates per second. The reason that we do this, while also using a FixedStepPhysicsWorld object, is to create a simulation that will be consistent across all devices, regardless of how efficiently a device can process the physics simulation. We then create the EngineOptions object with standard preferences, create the onCreateResources() method with only a simple callback, and set the main scene with a light-gray background.
In the onPopulateScene() method, we create our FixedStepPhysicsWorld object that has double the gravity of the Earth, passed as an (x,y) coordinate Vector2 object, and will update 60 times per second. The gravity can be set to other values to make our simulations more realistic or 0 to create a zero gravity simulation. A gravity setting of 0 is useful for space simulations or for games that use a top-down camera view instead of a profile. The false Boolean parameter sets the AllowSleep property of the PhysicsWorld object, which tells PhysicsWorld to not let any bodies deactivate themselves after coming to a stop. The last two parameters of the FixedStepPhysicsWorld object tell the physics engine how many times to calculate velocity and position movements. Higher iterations will create simulations that are more accurate, but can cause lag or jitteriness because of the extra load on the processor. After creating the FixedStepPhysicsWorld object, we register it with the main scene as an update handler. The physics world will not run a simulation without being registered.
The variable WALL_FIXTURE_DEF is a fixture definition. Fixture definitions hold the shape and material properties of entities that will be created within the physics world as fixtures. The shape of a fixture can be either circular or polygonal. The material of a fixture is defined by its density, elasticity, and friction, all of which are required when creating a fixture definition. Following the creation of the WALL_FIXTURE_DEF variable, we create four rectangles that will represent the locations of the wall bodies. A body in the Box2D physics world is made of fixtures. While only one fixture is necessary to create a body, multiple fixtures can create complex bodies with varying properties.
Further along in the onPopulateScene() method, we create the box bodies that will act as our walls in the physics world. The rectangles that were previously created are passed to the bodies to define their position and shape. We then define the bodies as static, which means that they will not react to any forces in the physics simulation. Lastly, we pass the wall fixture definition to the bodies to complete their creation.
After creating the bodies, we attach the rectangles to the main scene and set the scene's touch listener to our activity, which will be accessed by the onSceneTouchEvent() method. The final line of the onPopulateScene() method tells the engine that the scene is ready to be shown.
The overridden onSceneTouchEvent() method will handle all touch interactions for our scene. The onAccelerationAccuracyChanged() and onAccelerationChanged() methods are inherited from the IAccelerationListener interface and allow us to change the gravity of our physics world when the device is tilted, rotated, or panned. We override onResumeGame() and onPauseGame() to keep the accelerometer from using unnecessary battery power when our game activity is not in the foreground.
There's more...
In the overridden onAccelerationChanged() method, we make two calls to the Vector2Pool class. The Vector2Pool class simply gives us a way of re-using our Vector2 objects that might otherwise require garbage collection by the system. On newer devices, the Android Garbage Collector has been streamlined to reduce noticeable hiccups, but older devices might still experience lag depending on how much memory the variables being garbage collected occupy.
Visit http://www.box2d.org/manual.htmlto see the Box2D User Manual. The AndEngine Box2D extension is based on a Java port of the official Box2D C++ physics engine, so some variations in procedure exist, but the general concepts still apply.
See also
Understanding different body types in this article.
Understanding different body types
The Box2D physics world gives us the means to create different body types that allow us to control the physics simulation. We can generate dynamic bodies that react to forces and other bodies, static bodies that do not move, and kinematic bodies that move but are not affected by forces or other bodies. Choosing which type each body will be is vital to producing an accurate physics simulation. In this recipe, we will see how three bodies react to each other during collision, depending on their body types.
Getting ready...
Follow the recipe in the Introduction to the Box2D physics extension section given at the beginning of this article to create a new activity that will facilitate the creation of our bodies with varying body types.
How to do it...
Complete the following steps to see how specifying a body type for bodies affects them:
First, insert the following fixture definition into the onPopulateScene() method:
FixtureDef BoxBodyFixtureDef =
PhysicsFactory.createFixtureDef(20f, 0f, 0.5f);
Next, place the following code that creates three rectangles and their corresponding bodies after the fixture definition from the previous step:
Rectangle staticRectangle = new Rectangle(cameraWidth /
2f,75f,400f,40f,this.getVertexBufferObjectManager());
staticRectangle.setColor(0.8f, 0f, 0f);
mScene.attachChild(staticRectangle);
PhysicsFactory.createBoxBody(mPhysicsWorld, staticRectangle,
BodyType.StaticBody, BoxBodyFixtureDef);
Rectangle dynamicRectangle = new Rectangle(400f, 120f, 40f, 40f,
this.getVertexBufferObjectManager());
dynamicRectangle.setColor(0f, 0.8f, 0f);
mScene.attachChild(dynamicRectangle);
Body dynamicBody = PhysicsFactory.createBoxBody(mPhysicsWorld,
dynamicRectangle, BodyType.DynamicBody, BoxBodyFixtureDef);
mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(
dynamicRectangle, dynamicBody);
Rectangle kinematicRectangle = new Rectangle(600f, 100f,
40f, 40f, this.getVertexBufferObjectManager());
kinematicRectangle.setColor(0.8f, 0.8f, 0f);
mScene.attachChild(kinematicRectangle);
Body kinematicBody = PhysicsFactory.createBoxBody(mPhysicsWorld,
kinematicRectangle, BodyType.KinematicBody, BoxBodyFixtureDef);
mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(
kinematicRectangle, kinematicBody);
Lastly, add the following code after the definitions from the previous step to set the linear and angular velocities for our kinematic body:
kinematicBody.setLinearVelocity(-2f, 0f);
kinematicBody.setAngularVelocity((float) (-Math.PI));
How it works...
In the first step, we create the BoxBodyFixtureDef fixture definition that we will use when creating our bodies in the second step. For more information on fixture definitions, see the Introduction to the Box2D physics extension recipe in this article.
In step two, we first define the staticRectangle rectangle by calling the Rectangle constructor. We place staticRectangle at the position of cameraWidth / 2f, 75f, which is near the lower-center of the scene, and we set the rectangle to have a width of 400f and a height of 40f, which makes the rectangle into a long, flat bar. Then, we set the staticRectangle rectangle's color to be red by calling staticRectangle. setColor(0.8f, 0f, 0f). Lastly, for the staticRectangle rectangle, we attach it to the scene by calling the mScene.attachChild() method with staticRectangle as the parameter. Next, we create a body in the physics world that matches our staticRectangle. To do this, we call the PhysicsFactory.createBoxBody() method with the parameters of mPhysicsWorld, which is our physics world, staticRectangle to tell the box to be created with the same position and size as the staticRectangle rectangle, BodyType. StaticBody to define the body as static, and our BoxBodyFixtureDef fixture definition.
Our next rectangle, dynamicRectangle, is created at the location of 400f and 120f, which is the middle of the scene slightly above the staticRectangle rectangle. Our dynamicRectangle rectangle's width and height are set to 40f to make it a small square. Then, we set its color to green by calling dynamicRectangle.setColor(0f, 0.8f, 0f) and attach it to our scene using mScene.attachChild(dynamicRectangle). Next, we create the dynamicBody variable using the PhysicsFactory.createBoxBody() method in the same way that we did for our staticRectangle rectangle. Notice that we set the dynamicBody variable to have BodyType of DynamicBody. This sets the body to be dynamic. Now, we register PhysicsConnector with the physics world to link dynamicRectangle and dynamicBody. A PhysicsConnecter class links an entity within our scene to a body in the physics world, representing the body's realtime position and rotation in our scene.
Our last rectangle, kinematicRectangle, is created at the location of 600f and 100f, which places it on top of our staticRectangle rectangle toward the right-hand side of the scene. It is set to have a height and width of 40f, which makes it a small square like our dynamicRectangle rectangle. We then set the kinematicRectangle rectangle's color to yellow and attach it to our scene. Similar to the previous two bodies that we created, we call the PhysicsFactory.createBoxBody() method to create our kinematicBody variable. Take note that we create our kinematicBody variable with a BodyType type of KinematicBody. This sets it to be kinematic and thus moved only by the setting of its velocities. Lastly, we register a PhysicsConnector class between our kinematicRectangle rectangle and our kinematicBody body type.
In the last step, we set our kinematicBody body's linear velocity by calling the setLinearVelocity() method with a vector of -2f on the x axis, which makes it move to the left. Finally, we set our kinematicBody body's angular velocity to negative pi by calling kinematicBody.setAngularVelocity((float) (-Math.PI)). For more information on setting a body's velocities, see the Using forces, velocities, and torque recipe in this article.
There's more...
Static bodies cannot move from applied or set forces, but can be relocated using the setTransform() method. However, we should avoid using the setTransform() method while a simulation is running, because it makes the simulation unstable and can cause some strange behaviors. Instead, if we want to change the position of a static body, we can do so whenever creating the simulation or, if we need to change the position at runtime, simply check that the new position will not cause the static body to overlap existing dynamic bodies or kinematic bodies.
Kinematic bodies cannot have forces applied, but we can set their velocities via the setLinearVelocity() and setAngularVelocity() methods.
See also
Introduction to the Box2D physics extension in this article.
Using forces, velocities, and torque in this article.
Creating category-filtered bodies
Depending on the type of physics simulation that we want to achieve, controlling which bodies are capable of colliding can be very beneficial. In Box2D, we can assign a category, and category-filter to fixtures to control which fixtures can interact. This recipe will cover the defining of two category-filtered fixtures that will be applied to bodies created by touching the scene to demonstrate category-filtering.
Getting ready...
Create an activity by following the steps in the Introduction to the Box2D physics extension section given at the beginning of the article. This activity will facilitate the creation of the category-filtered bodies used in this section.
How to do it...
Follow these steps to build our category-filtering demonstration activity:
Define the following class-level variables within the activity:
private int mBodyCount = 0;
public static final short CATEGORYBIT_DEFAULT = 1;
public static final short CATEGORYBIT_RED_BOX = 2;
public static final short CATEGORYBIT_GREEN_BOX = 4;
public static final short MASKBITS_RED_BOX =
CATEGORYBIT_DEFAULT + CATEGORYBIT_RED_BOX;
public static final short MASKBITS_GREEN_BOX =
CATEGORYBIT_DEFAULT + CATEGORYBIT_GREEN_BOX;
public static final FixtureDef RED_BOX_FIXTURE_DEF =
PhysicsFactory.createFixtureDef(1, 0.5f, 0.5f, false,
CATEGORYBIT_RED_BOX, MASKBITS_RED_BOX, (short)0);
public static final FixtureDef GREEN_BOX_FIXTURE_DEF =
PhysicsFactory.createFixtureDef(1, 0.5f, 0.5f, false,
CATEGORYBIT_GREEN_BOX, MASKBITS_GREEN_BOX, (short)0);
Next, create this method within the class that generates new category-filtered bodies at a given location:
private void addBody(final float pX, final float pY) {
this.mBodyCount++;
final Rectangle rectangle = new Rectangle(pX, pY, 50f, 50f,
this.getVertexBufferObjectManager());
rectangle.setAlpha(0.5f);
final Body body;
if(this.mBodyCount % 2 == 0) {
rectangle.setColor(1f, 0f, 0f);
body = PhysicsFactory.createBoxBody(this.mPhysicsWorld,
rectangle, BodyType.DynamicBody, RED_FIXTURE_DEF);
} else {
rectangle.setColor(0f, 1f, 0f);
body = PhysicsFactory.createBoxBody(this.mPhysicsWorld,
rectangle, BodyType.DynamicBody, GREEN_FIXTURE_DEF);
}
this.mScene.attachChild(rectangle);
this.mPhysicsWorld.registerPhysicsConnector(new
PhysicsConnector(
rectangle, body, true, true));
}
Lastly, fill the body of the onSceneTouchEvent() method with the following code that calls the addBody() method by passing the touched location:
if(this.mPhysicsWorld != null)
if(pSceneTouchEvent.isActionDown())
this.addBody(pSceneTouchEvent.getX(),
pSceneTouchEvent.getY());
How it works...
In the first step, we create an integer, mBodyCount, which counts how many bodies we have added to the physics world. The mBodyCount integer is used in the second step to determine which color, and thus which category, should be assigned to the new body.
We also create the CATEGORYBIT_DEFAULT, CATEGORYBIT_RED_BOX, and CATEGORYBIT_ GREEN_BOX category bits by defining them with unique power-of-two short integers and the MASKBITS_RED_BOX and MASKBITS_GREEN_BOX mask bits by adding their associated category bits together. The category bits are used to assign a category to a fixture, while the mask bits combine the different category bits to determine which categories a fixture can collide with. We then pass the category bits and mask bits to the fixture definitions to create fixtures that have category collision rules.
The second step is a simple method that creates a rectangle and its corresponding body. The method takes the X and Y location parameters that we want to use to create a new body and passes them to a Rectangle object's constructor, to which we also pass a height and width of 50f and the activity's VertexBufferObjectManager. Then, we set the rectangle to be 50 percent transparent using the rectangle.setAlpha() method. After that, we define a body and modulate the mBodyCount variable by 2 to determine the color and fixture of every other created body. After determining the color and fixture, we assign them by setting the rectangle's color and creating a body by passing our mPhysicsWorld physics world, the rectangle, a dynamic body type, and the previously-determined fixture to use. Finally, we attach the rectangle to our scene and register a PhysicsConnector class to connect the rectangle to our body.
The third step calls the addBody() method from step two only if the physics world has been created and only if the scene's TouchEvent is ActionDown. The parameters that are passed, pSceneTouchEvent.getX() and pSceneTouchEvent.getY(), represent the location on the scene that received a touch input, which is also the location where we want to create a new category-filtered body.
There's more...
The default category of all fixtures has a value of one. When creating mask bits for specific fixtures, remember that any combination that includes the default category will cause the fixture to collide with all other fixtures that are not masked to avoid collision with the fixture.
See also
Introduction to the Box2D physics extension in this article.
Understanding different body types in this article.
Read more