Table of Contents
The StuntPlane application is an example of how to write a simple game using only the features provided by the X-Forge Core. For 2D games and very simple 3D games this might actually be easier than using the full blown X-Forge Game Engine.
The purpose of the game is quite simple. The player is piloting a biplane and must fly through hoops of different shapes while collecting as many bonus items as possible. The plane can be moved around in the vertical plane and rolled in 45-degree steps along it's direction of movement. The plane is always moving forward with a constant speed. On each level the player must not crash into or miss more than 3 hoops, or the game will be over.
StuntPlane.cpp contains the main application class. The StuntPlane class inherits from the XFcApp class and thus has the responsibility to initialize and shut down the application, as well as controlling the main application flow and taking care of other external events.
Regardless of which renderer is active, the onTick() method of the class that inherits from XFcApp is called just before rendering starts. We can thus use this function to implement a global state machine. When a state change is needed, for example when the player has missed four hoops and the game is over, the setState() method in StuntPlane is called, in this particular case with the parameter SP_STATE_GAMEOVER. When onTick() is called next it will take the appropriate actions needed to switch the game into "game over" state. This procedure is used for all global state changes.
The StuntPlane class creates two Screens, the MenuScreen and the GameScreen. A Screen is basically just a wrapper for the XFcRenderer and XFcController interfaces. It's defined in Screen.h.
The MenuScreen takes care of rendering and input for both the main menu and the options menu, as well as initial splash screens and the highscore display. What to render is determined by the current application state, and is selected using a switch statement in the render() method. The control is delegated in the same way. The same menu graphics and font are used for all resolutions. The game logo is linearily scaled to fit the current display dimensions. The menu text is placed to fill up the remaining space. This is ok for example purposes, but if this were a real game we would most likely want to either use different graphics for different resolutions or use the XFcGLSurfaceToolkit to rescale the graphics more accurately.
The GameScreen takes care of rendering the ingame graphics. It's also used to render loading screens, level complete messages and game over messages, which are overlays on top of the slightly modified ingame graphics. For example, the loading screen renders the ingame graphics as usual (except for the status indicators), brightens the image a bit and then draws the loading text on top of that. This enables the player to get an idea of what the next level looks like while waiting for takeoff.
A level is basically a rectangular volume in space containing some hoops, some bonus items and a number of clouds. Theese are all 2D bitmaps. The width and height of a level is hardcoded to 320 length units (-160,160). The depth is specified in the level file and is usually about 2000 length units.
The level layout is loaded and parsed from plain text files. The format is quite simple. An example of a minimal but commented level file might look like this:
# level1.txt # Level info: # background image, length, startx, starty, loading screen text LEVEL=backdrop1.pcx, 1200, 0, 0, I can fly! # Wind info: # strength(100 = strong), initial direction(0-360 deg), rotation speed, gust factor (100 = strong) WIND=20, 180, 10, 10 # Cloud info: # sprite image, number of clouds visible at the same time. CLOUDS=cloud.pcx, 10 # Hoop info: # sprite image, x, y, z, hx, hy, hr, allowed rotation (o=any) HOOP=hoop1.pcx, 20, 0, 120, 0, 0, 20.0, o # Bonus item info: # sprite image, x, y, z, bonus points BONUS=redballoon128.pcx, 0, 32, 80, 50
The parsing is done using three XFuTokenizer objects. One for splitting the file into a set of lines, another for splitting the command and parameter list, and a third one for splitting the parameter list into single parameters.
The hoops and the bonus items are placed in the level as specified in the level file while the locations of the clouds are randomly generated. The background image specified in the level file is panned as the plane turns back and forth to make the movement more apparent.
The plane is a 3D mesh created using 3DSMax and exported to an XFF file. The loader only reads the information that is really needed by the game and ignores the rest. In this case we only need a mesh node, a vertex buffer, a triangle info buffer, and a face list. The plane is rendered using Gouraud shaded, untextured triangles. The XFFPlayer core example provides a more complete example of how to read and display the contents of XFF files.
The plane also has a rotating propeller. The propeller is not described in the XFF file but instead animated and rendered using hardcoded vertex information in the source code.
3D sprites (Billboards) are used to draw hoops, bonus items and clouds. Theese are basically 2D bitmaps given a size and 3d dimensonal space coordinates. They are then automatically scaled and rendered using this information by a call to the XFcGL method drawSprite3dBillboard(). The collision detection between the plane and sprites is very simple, using just some quick bounding sphere/bounding cylinder calculations.
As stated earlier, the plane can be controlled in the vertical (xy) plane while it's forward speed in the z direction is constant. Although it appears as the plane is moving forward it actually always stays at the origin. The objects are instead moved towards the plane, in the negative z direction.
The game logic is updated by calling updateGameLogic() in the GameScreen class. This method advances the current time of the game physics by a fixed amount. updateGameLogic() is called repeatedly until the game time has caugth up with the actual time as returned by XFcCore::getTick().
As long as the player keeps a key pressed down for movement in some direction a constant acceleration is added to the current speed of the plane towards that direction, given that the speed is not larger than a constant, predefined maximum speed. As a very simplified emulation of air resistance the current horizontal and vertical speed of the plane is multiplied by a constant slightly less than 1 on every game logic iteration. Thus, the speed will decrease automatically when the player doesn't keep the key pressed down. In addition, wind is simulated by adding a small amount of speed in some direction during each game logic iteration.