Programming for Mobiles - Practical 4
- JBox2D Java 2D Physics engine: [below]
- Drawing 2D graphics on Android: [below]
- Initialising JBox2D: [below]
- The game loop: [below]
- Drawing the world bodies [below]
- Collecting and interpreting a touch event [below]
- Check out the demos in the JBox2D distribution to see how to create a MouseJoint
JBox2D is a Java 2D Physics simulation library. You can download it from here. The best way to include this in your Android project is to download the zip file from the website and use Import existing projects into workspace. This will create a new Eclipse project for JBox2D. Remove extraneous source code from this to strip it down to just the Physics engine code. Then edit the build path of your Android application and add JBox2D as a project dependency
2D Graphics on Android
2D graphics are drawn using Surface. First, create a SurfaceView in your interface XML definition file. You now need to get the SurfaceHolder from the SurfaceView. The SurfaceHolder provides a callback mechanism (addCallback()) to which you can register a listener to receive notifications when the surface is created or destroyed. Once the surface has been created you should use SurfaceHolder.lockCanvas() to get a Canvas instance onto which you can draw. You should call SurfaceHolder.unlockCanvasAndPost() when you've finished drawing.
Note that Android (ERE27) throttles the rate at which you can lock a canvas to at most every 100ms.
You need to choose a co-ordinate system for use in your Physics simulation. Using a 1-to-1 mapping with pixels will not work well (see the note in the JBox2D distribution and think about why). I decided to use a system which is ten units wide across the longest edge of the screen.
mWorld = new World(coordinateSystem.getWorldBounds(), new Vec2(0.0f, -10.0f), true);
The JBox2D World object contains all the information about the physics simulation. You should set the bounds to be slightly bigger than your visible display (objects which move out of the bounds will disappear). The second argument determines gravity for the simulation and the third turns on an optimisation in which objects which come to rest 'sleep' and are no longer simulated.
To create a static object:
PolygonDef sd = new PolygonDef(); sd.setAsBox(1, 1); BodyDef bd = new BodyDef(); bd.position.set(5.f, 5.f); Body ground = mWorld.createBody(bd); ground.createShape(sd);
To create a dynamic object which moves (objects with non-zero mass are simulated):
PolygonDef sd = new PolygonDef(); sd.setAsBox(1, 1); sd.density = 5.0f; sd.restitution = 0.5f; sd.friction = 0.1f; BodyDef bd = new BodyDef(); bd.position.set(5.f, 5.f); Body ground = mWorld.createBody(bd); ground.createShape(sd); ground.setMassFromShapes();
The Android toolkit redraws widgets only when the interface changes. This will not suffice for your purposes since you want to redraw your simulation at some desired framerate. One strategy is to fork a thread which loops forever. Pick some desired framerate. Each iteration you should advance the world physics simulation using (World.step), draw the current world state to the Surface and then sleep (Thread.sleep()) for the desired interframe interval. A better approach however is to notice that some frames will take longer to compute and render than others and to instead measure how much time has elapsed since the last frame (try System.nanoTime()) and then sleep for the remainder of the interval.
Drawing the simulation bodies
Use World.getBodyList() to get the first Body (and Body.getNext() to traverse the list). Each Body has a number of Shapes associated with it: use Body.getShapeList() to get the shapes. A Shape can either be an instance of PolygonShape or CircleShape. For a PolygonShape you should get the vertices (PolygonShape.getVertices) and use them to create a Path which you can draw to the Canvas. You will need to use Body.getWorldPoint() to turn the local vertex position on the shape into a globabl position in the World frame of reference (which you should then convert to pixel position). CircleShapes can be drawn in an analogous fashion.
Collecting and interpreting a touch event
Register an OnTouchListener with the SurfaceView object to receive touch events. When you receive an ACTION_DOWN event you need to work out which world bodies the user might be touching. To do this you should convert the pixel position of the touch in to World coordinates and then use the World.query() method to discover all the touched Bodies.
Handling multiple touches is more complicated (and requires Android API level 5 or greater). See http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html.