
Creating the Eclipse Project
Getting this sample up and running is pretty straightforward once you know how. Start by creating a new Android project as normal. We want to add the AndEngine JAR file to this project so it's available to our code. Right-click the new project in Eclipse Navigator, and choose Properties. Click Java Build Path in the left-hand pane of the project properties dialog, and then select the Libraries tab.
Click the Add External JARs... browse to your AndEngine Box 2D extension JAR file and select it. Be sure to add the Box 2D extension not AndEngine core - the extension JAR also contains the core engine and the duplicate AndEngine classes will throw 'Already Added' Exceptions that prevent the project from being built.
Now open up your project's Activity source file, and replace the default code with Nicolas's Racer code from the samples project (but keep your package name). You'll also need to rename the file to RacerGameActivity.java to match the class name.
The following image files are needed by our project:
- vehicles.png
- racetrack_straight.png
- racetrack_curve.png
- onscreen_control_base.png
- onscreen_control_knob.png
- box.png
There's one last step before we can run our project in the emulator - download the compiled .so file for the Box 2D extension to a directory called libs/armeabi off our project root. Once it's there, the project should compile and run in the emulator just fine.
Analysis of the AndEngine Racer Code
I now want to have a detailed look at Nicolas' code, to break it down in order to understand what's going on where. There seems to be such a small amount of code to create this game and all its features that I'm very eager to know how it was accomplished. I want to look at features such as the acceleration of the car, cornering, collision detection both with the edges of the track and also the objects (crates) on the track, and physics behaviours such as how the boxes move when they are struck.Pointing Eclipse to the JAR Source
To help us in our quest to understand what's going on under the bonnet, we can link the AndEngine JAR file to its source project, assuming that you compiled the JAR yourself. To do it, right-click the project in the Navigator, and choose Properties. On the project properties dialog, select Java Build Path in the left-hand pane, and open the Libraries tab. Expand the entry for the andenginebox2d.jar (or however yours is named), and select Source attachment. Now click the Edit.. button, then Workspace and select the Eclipse project which houses the Box 2D extension.
Now that Eclipse knows where to find the source, we can easily jump to the code for a particular class by pressing F3 when the cursor is at the class name, and also JavaDoc comments from the source will be used for the pop-up information the IDE produces when hovering over class or method names. This latter won't be too useful unfortunately, as Nicolas has used very few JavaDoc blocks in his code. Hopefully we'll also be able to step through the AndEngine code with the Eclipse debugger. All good juicy stuff!
RacerGameActivity Type Hierarchy
We can examine the entire class inheritance tree by pressing F4 with the RacerGameActivity.java file open in the editor. This shows us that the RacerGameActivity class extends BaseGameActivity, an AndEngine UI class which itself extends BaseActivity, another AndEngine UI class. BaseActivity extends the Android platform Activity class. BaseGameActivity also implements IGameInterface, another AndEngine UI component. BaseGameActivity does not however implement any of this interface's methods, so that must be done by RacerGameActivity.The onLoadEngine() Method
Sure enough, when the app runs, the first method to execute is onLoadEngine(), one of the IGameInterface methods that must be implemented. It is invoked by the BaseGameActivity's onCreate() method. Inside onLoadEngine(), RacerGameActivity first instantiates an AndEngine Camera object and stores it in the mCamera class variable. The Camera constructor parameters are the camera's left edge X position, its top Y position, and the camera's width and height respectively. The camera represents a viewport onto the gameworld. The gameworld could be larger than the viewport, in which case the camera will only show a section of it. As AndEngine is a 2D engine, there's no Z coordinate to worry about.The next line of onLoadEngine() instantiates an AndEngine Engine object. passing in a few options as an EngineOptions instance, as well as the camera created in the previous line. The engine is then returned by onLoadEngine() to the calling code in BaseGameActivity's onCreate() method, which stores the engine as the mEngine class variable.
The onLoadResources() Method
The next RacerGameActivity method to fire is onLoadResources(), also from IGameInterface via BaseGameActivity. As its name suggests, this method sets up resources for the game by loading various textures into class variables.The onLoadScene() Method
This is the next RacerGameActivity method to execute, and originates in the IGameInterface interface we inherited from BaseGameActivity. It begins by instantiating an AndEngine Scene object, which represents the visible on-screen playing area, and also seems to handle touchscreen inputs. Next a background colour is set for the scene.Then a Box 2D Physics world is created using the constructor with the signature below:
FixedStepPhysicsWorld(final int pStepsPerSecond, final Vector2 pGravity, final boolean pAllowSleep, final int pVelocityIterations, final int pPositionIterations)
Referring to this signature, we can see the Physics world is created having 30 steps every second, zero gravity, sleep is prevented, and 8 velocity iterations and 1 position iteration - whatever that means.The remaining initialisation tasks have been split into private methods of RacerGameActivity, and these are then called in turn. Finally, the Physics world is linked to the Scene as an update handler presumably so that the scene will get its updates from the Physics world, which makes sense.
The initRaceTrack() method
As its name suggests, this private method sets up the race track around which the player drives the car. The race track is built in much the same way as a Scalextric track, but using textures rather than physical track sections. This way the track can be built from just two types of pieces - straight type and corner type. For the simple oval track in this game, there are two horizontal straight sections which are built as a TextureRegion instance that is textured with the straight texture and set at a width of three times that of the straight texture.The top and bottom straight sections are attached to the Scene by calling the attachChild() method of the mScene private variable. passing in a Sprite created from the horizontal texture and positioned at the appropriate location.
The left and right vertical straight sections are added in much the same way, although before being added to the Scene, the Sprite created for each is rotated by 90 degrees.
The initRaceTrackBorders() method
This method creates the red border on each edge of the straight sections. The borders are created as Shape objects of 2 pixels width. Each shape is added to the Physics world by passing it into the PhysicsFactory.createBoxBody() static method along with the mPhysicsWorld private PhysicsWorld instance and a couple of other parameters that define how the body should act in the physics world.All the shapes are then added to the scene and this method is done. So far we seem to have added the straight sections of the track and created the wall on each of their edges. It's not yet clear how or where the corners are dealt with!
The initCar() method
No surprises here - this method adds the player car to the physics world and to the scene. The car is a Sprite instance - or rather it is an instance of the TiledSprite subclass of Sprite. This subclass is used because the car texture is supplied in a tiled format i.e. multiple different textures in a single image file. The specific texture tile we want for our car is set by the setCurrentTileIndex() method of the TiledSprite.As with the track edges, physics properties are given to our car by passing it into PhysicsFactory.createBoxBody(). As this is a dynamic object however, we need to link the box body returned by this method to the car sprite by creating a PhysicsConnector instance from the sprite and the box body, and passing that into the registerPhysicsConnector() method of the private mPhysicsWorld variable.
Lastly we attach the Sprite to the Scene.