AS-0.1102 - project document

Crumbled Earth

Authors:

Sakari Bergen
Heikki Laurila
Joonas Ryynänen

Last updated:

3.1.2007

Contents

Instructions for compiling and use

Since the program uses ClanLib for almost everything, it should work on all the platforms and operating systems which are supported by ClanLib. That should include Windows, Linux and Mac OS X. The program has been tested to work only on the x86 platform with Windows and on x86 and amd64 with Linux.

The program won't work on older machines as the graphics engine uses OpenGL and needs quite a lot of processing power from the processor. A graphics card supporting OpenGL is required, otherwise graphical glitches will be visible and the program might crash. At least a 1.5GHz processor is required to achieve adequate frame rates and any moderately good graphics card should do.

Building and running on Linux

The following packages are needed for building and running the program on Linux:

ClanLib and a c++ compiler are the only real dependencies for building and running the program, but the provided building script might not work correctly if the above packages are not installed.

The default building method on Linux is via scons. The command scons executed in the src-directory should be enough on most systems. However if ClanLib is not in the default library directory of pkg-config, the PKG_CONFIG enviornment variable might need to be set. An example of this can be found in the SConstruct-script under the option TKK, which is inteded for compiling on the TKK Linux computers. Since scons is not in the default binary directory at TKK, a special building script for TKK is provided, which also enables the TKK-flag in scons. The script can be executed with the command ./build.

There is also a DEBUG flag in the scons script, that can be enabled for debugging (i.e. scons DEBUG=1). This disables several optimizations and turns on all compiler warnings. Some optimizations used are processor specific.

Short instructions: use scons on most all computers and ./build on TKK computers (both executed in the directory 'src').

The program starts with the command ./tank. Again if ClanLib is not in the default ld library path the LD_LIBRARY_PATH enviornment variable has to be set. The command ./run will take care of this on TKK computers and this script can be used as an example for other setups. The default resolution for the game is 1024x768 pixels and the minumum resolution 800x600 pixels. The default resolution can be overriden from the commandline with the syntax ./tank x_res y_res (eg. ./tank 1280 1024).

Building on Windows

You should be able to build the program with ClanLib, Scons, pkg-config and some c++ compiler on Windows, but we have tested only Microsoft Visual Studio. When building with Microsoft Visual Studio, no extra measures have to be taken other than having ClanLib correctly installed.

Instructions for use

When the game is started the main menu will be shown. This menu includes options for the random terrain generator, a terrain regeneration button and a start game button. The currently displayed menu can be changed by clicking on the buttons on the left pane. Main menu The terrain properties can be adjusted with the controls in this screen.

Player and control settings can be made from the "Players & keys" menu: Players menu Players' key settings can be adjusted by selecting the desired command from the respective players list and pressing the "Set key" button. The next key that is pressed will be assigned to this command. Default settings can be set by pressing the "Set defaults" button. The players names can be written into the input boxes labeled "Name".

Sound settings can be made from the "Sound" menu: Sound menu

Gameplay settings can be made from the "Gameplay" menu: Gameplay menu

Physics settings: Tank settings:

Presets selection: You can choose between manual settings and presets from the box in the bottom of the screen. Settings are loaded with the "Load settings" button and the manual setting are saved automatically.

When the "Start!" button is pressed in the main menu the game starts: Main The left tank is controlled by player 1 and the right tank by player 2. Statistics for each player are shown in the top corners:

The tanks are controlled with the keys set in the "Players & keys" menu. Shooting happens by holding down the shoot key until the desired shooting power is reached. Once the shoot-key is lifted, or maximum shooting power is met, the tank will shoot. The turret angle does not depend on the ground angle underneath the tank, but stays at the same angle until the turret is adjusted, or when tank pivots enough that the turret must rotate with it. After a shot has been fired, the cannon will start loading before next shot can be fired. Loading time also resets when different weapon is selected.

There are 6 different weapons in the game:

The game can be paused at any time during game play by pressing esc, while paused, player statistics are shown on screen. A round ends when either player's health drops to zero or below. It can happen in 5 ways, on explosion, when drops in the ocean, when gets hit by a rock, when crashes due to falling or recoil or when gets buried inside ground under falling rocks. The game can also be exited during gameplay by clicking the window close button. After round ends, round statistics are shown. They include player's health, damage inflicted, shots fired and other statistics from round plus the total number of rounds won by each player.

Program architechture

The program consists of six main classes. Interaction between these classes is depicted in the diagram below: Architecture

When the program starts, the graphics engine, sound engine, physics engine, input class and GUI are initialized. The physics engine is started in its own thread and the sound engine and input are threaded by ClanLib. Program flow is mainly controlled by the GUI. If the gui exits with a return value indicating a new game, the input class is activated, the physics engine is started and the graphics engine is run when a status window is not shown. Showing the status window and running the graphics engine are mutually exclusive, so running the graphics engine is controlled by the status window (part of the GUI). Once the game ends, the input is disabled and the GUI is started again. This cycle repeats until the GUI returns a value indicating that the program should terminate.

Data structures and algorithms

Smart pointers

The newest version of the program uses boost::shared_ptr for storing pointers to objects in the physics engine. The physics engine itself keeps shared_ptrs of objects and also passes them to other game components as shared_ptrs, which again store the shared_ptrs. With this setup the program should never crash due to a non-existent object being polled by any game component. The sound engine also uses smart pointers for storing its objects.

Although all storage is done with smart pointers, many functions still use raw pointers because that was the way the program was originally written. This is not a very elegant solution, but works fine.

Physics engine

The physics engine consists of two main classes, the main class PhysicsWorld storing the objects in physics world, and Object class handling objects' movement and other actions. All objects in physics world inherit from the Object class. The physics engine runs in its own thread and it passes pointers of its objects to graphics and sound engines. The pointers act as an interface between the 3 engines. User input is passed to physics world by GameInput class, and the world's terrain is given to physics world from terrain generator / gui as a parameter in addTerrain function.

Upon explosion, or when object hits water or rock hits ground or other special effect occurs, the physics world notifies sound and graphics engines of the effect and they display effects / play sounds accordingly. These kinds of effects have only parameters for type, coordinates and value for defining eg. explosion size or sound volume, and they are passed to graphics and sound engines by calling a function in them, thus they don't need to be deleted as each effect lasts a predefined time.

Physics world is also responsible for keeping track of when game should end or be paused. It ends when either of the tanks' health reaches 0, or when exit() function is called. Game will be paused and a stats window will be displayed when togglePause() is called.
Physics world thread is also responsible for shutting down the graphics engine, thus starting gui in that thread, when game is paused or ends. When game ends or is exited, physics world thread stops running, but physics world isn't destructed, and when new game starts, all stats and values are reseted.


Running the physics world and its features:

Objects

The Object class is a base class for all physics engine objects and it is used for storing and handling the objects' movement and collisions between it and other objects. All physics engine objects inherit from this class. The Object class also acts as an interface between the physics, graphics and sound engines. The physics engine sends pointers to graphics and sound engines, which then poll objects' coordinates, angles, speeds, and other object specific variables.

There are 4 different classes that inherit from Object class:

Terrain modificators

Terrain smoother smoothes given amount of terrain near a given point. It calculates starting and ending points from terrain outline, and then calculates a vector from start to end, indicating average height. It then moves each terrain point between start and end closer to the average height vector by given portion (with shovel weapon 10% closer, and on explosions 50%) thus creating a more smooth terrain.

Terrain remover removes an area from terrain set by given outline, if terrain is left hanging in the air, it collapses it, thus creating stones from it. Also from half of the terrain removed, it creates stones. All the stones have small random initial velocities and angular speeds. After all this is done, it uses terrain smoother to make the holes' edges smoother for easier movability of tanks.

Terrain raiser adds given amount of terrain at given point. It transforms the mass of the ground to be added to a sine graph and lifts terrain points by that amount.

Terrain collapsig occurs where there are cliffs / negative angles in terrain. It first randomly selects an edge off a cliff, then calculates its width by moving away from the edge along terrain outline, until reaches the main rock. It then selects a defined portion of it (by default 25%) and randomly cuts it off from terrain using the same function removeFromTerrain(), that explosions use.

Sound engine

The sound engine uses mainly ClanLib datastructures. Sound resource and session management is all done via ClanLibs classes and functions. Individual sound efffects can be played by passing an identifier (enum value) and the position of the sound to the sound engine. The sound engine automatically positions the sounds in the soundscape.

Objects in the game are represented by the (Snd::)Object class from which the classes Tank and Projectile are derived. The derived classes receive a pointer to the respective object managed by the physics engine and poll it for its location and status. This information is handled and appropriate sound are produced. Each derived class uses a static resource manager shared by all Objects. Each derived class also has static shared instances of each sound buffer it uses.

Upon registering a Object to the sound engine, a new Object of the apropriate types is created and pointers to Objects are added to a list (std::list), from which they are removed (and the respective objects deleted) upon unregistering. Polling the objects is done with a ClanLib timer class, which calls an update function regularly. The update function again calls the update function of each Object. Since the program uses multiple threads, adding, deleting and updating objects had to be placed under a mutex (also from ClanLib).

Rock sounds were handled specially because phase problems occurred when playing the same sound many times in a row (it sounded strange). The RockHandler class returns a sound buffer each time a function is called. It has five different samples in it and it takes care that the same sample is not played too fast after the previous play. Vectors (std::vector) are used for accessing the different samples by index and ClanLib timers are used to keep track of time.

Explosions are now handled the same way as rocks by the ExplosionHandler class. This makes the soundscape more intresting and clusterbombs sound better.

Game Input

The game input mechanism consists of four classes. The main input class which reads the keyboard, a control strruct ehich keeps information about key and commands that are set for a player, a debouncer class and a player status class. Input The input from the keyboard is hooked to hadler functions with ClanLib signals. These functions check if the pressed/released key belongs to a player. If it does, it will be either debounced or passed on to the player status class. Debouncing has to be done with shooting keys only. The player status class checks whether the command should be passed on to the physics engine or not, and does so when necessary.

The debouncer class had to be created to get rid of bogus key-up-signals which caused problems when testing the game on Linux (Windows did not have these problems). The debouncer class rejects key-up-signals, which are followed by a key-down-signal too fast.

GUI

The GUI consists of standard ClanLib GUI-components. The componets are mostly located with the help of a function that gives absolute postition from relative postion defenitions. The different setting frames are all derived from the base class OptionPane, which provides some basic functionality for such frames. ClanLib signals are heavily used in the GUI to connect components and functionality. A game status window is also implemented with ClanLib GUI-components.

All settings are loaded and saved to/from files via the class ConfigFile. It provides piping operators and error handling for all datatypes that need to be saved into config files.

Terrain Generator

The terrain generator is based on an algorithm originally made for creating random 3D-terrain. It was adapted for 2D-use for the program. The algorithm adds parabols of random size and position to a height map (std::vector<double>). After adding the parabols many operations including multiplication, addition, squareing etc. are performed to the values to get the desired result. The behaviour of the generator can be adjusted from the GUI. The generated terrain can be scaled and drawn into a ClanLib GUI-component or transformed into a form usable by the physics engine.

Graphics Engine

The graphics engine is run in its own thread and it is responsible for drawing the in-game graphics, including tanks' ammo, health and other info and also for generating the terrain texture. All drawing is done by using ClanLib's drawing functions, sprites and surfaces.

Most of the drawing is done while iterating through the std::list of pointers to Phys::Objects. Pointers to players (i.e. tanks) are also stored separately to have a direct access to them. The objects are used under mutexes to prevent other threads from accessing them while they are in use, and their pointers are sent to the graphics engine by the physics engine. Individual, stationary effects such as smoke, sand dust and explosions don't need pointers so only their locations are sent to the graphics engine.

Terrain texture generation

When a new round starts and a new terrain outline is sent to the graphics engine, the engine is reseted and a new terrain texture is drawn. During the drawing process a loading bar is displayed for the users' amusement. The texture is generated in 4 main parts:

The 1600x800 terrain texture is divided into two CL_Surfaces of size 800x800 (drawn next to each other during gameplay) because on windows low-end machines cannot operate and/or draw over 1024x1024 sized textures.

In-game graphics

The in-game graphics consists of different elements drawn in specific order, together forming the graphical outlook of the game. The elements are drawn in the following order:

Known bugs

We have found no severe bugs that affect the gameplay, and what's left are a few hard-to-fix bugs.

The following bugs are either directly or indirectly caused by ClanLib:

The rest of the bugs are all caused by the physics engine's complex terrain modification functions and functions that implement ClanLib's collision detection system.

Tasks sharing and schedule

Sharing the program to three parts succeeded quite well. The group met only twice, but communicated continuously on an IRC channel. That proved to be a good communication channel for this purpose.

In addition to the predefined tasks, group members also helped others on some bugs and other issues.

Real project schedule

12.11.2007: Basic implementations ready

We planned to have working basic implementations of the main modules on 12.11., just a few days after returning the project plan, and we did. However we had serious problems in integrating the modules together because we use 3 threads (Graphics, Physics and Sound) and while the program worked perfectly on Windows, we could not get it to work on Linux. Ultimately the problem turned out to be in ClanLib, as its drawing functions were not multi-thread safe on Linux, even with mutexes and even then when only 1 thread was running. We had similar problems later on too, and eventually concluded that all the graphics must be drawn from the same thread.

22.11.2007: Minimum requirements not met

We were supposed to have met the minimum requirements on 22.11., but the program wasn't anywhere near finished. Most of the requirements set for physics world were implemented but had serious bugs in them and overall they needed very much fine-tuning. At that point the graphics engine was only little more advanced than its basic implementation was, and the sound engine wasn't finished either.

28.11.2007: Program not ready

At this point we were supposed to be almost ready with the program, but because of Joonas Ryynänen fell ill, physics engine programming got delayed by a week and none of the other parts of the program were in schedule either, though basic implementation of the gui was ready.

2.12.2007: Light at the end of the tunnell

Things looked better as loads of bugs in physics engine were fixed and gui, graphics and sound engines started to look promising. Because of falling back in schedule we got a real boost in motivation and started making good proggress. Also a working terrain generator had been implemented. We also planned a new schedule as on 3.12. we got the deadline date for the project (13.12.).

9.12.2007: Nearly finished

Program is almost ready and most of the time goes to bug fixing and fine-tuning. We also started writing the documentation and decided not to make any more big changes or additions to the program.

14.12.2007: More time...

We were given more time for coding at the project demo session and decided to do some improvements here and there. We decided to improve memory handling with smart pointers, spice up the graphics, add presets to gameplay settings and do lots of other small improvements.

23.12.2007: Nearly done

The graphics have been improved greatly and the settings have been fine tuned - the game is basically ready but we still have to update the documentation and do final extensive bug testing. The bad news is no-one has time for over a week to do any of those things.

4.1.2007: Finally done

The final testing and bug fixing began on 2.1. and we we're done on 3.1. Most all improvements have been implemented and the documentation updated and most importantly all severe bugs have been fixed.

In retrospect, biggest problems

When designing the interfaces between physics, graphics and sound engines, we decided to use ClanLib's CL_Pointf coordinate, and CL_Vector vector classes. Also in physics world the collision detection was done with ClanLib's own collision detection system.
These turned out to be big mistakes, as most of the functions that were supposed to help, did not work correctly. For example some ClanLib functions dealing with angles used degrees while some used radians, and in most of their declarations it wasn't even told which ones were used. Also because the collision detection uses lists of points as object's outlines, we decided to use a list of points as world's terrain too.

Using a list of points for the terrain was maybe the biggest mistake timewise. In graphics engine it was hard to convert to triangles, mainly because of extremely poorly documented Clanlib triangulation functions. In physics engine the point list caused a lot of headache and wasted many hours because it made modifying terrain a very tough job to do, as seen in several terrain modification functions in physicsWorld.cc. It was too easy to make small mistakes which were hard to identify in source code and only seldomly occured during gameplay.

In the end we decided to ditch the triangles in graphics engine and just drew vertical lines insted. In physics world on the other hand, we just debugged and debugged it until everything worked fine.

Differences to the original plan

After all, the plan was followed well. A logger tool for the physics was planned but not implemented, since it was neither needed nor part of the actual game. However, all optional requirements, except for AI, were implemented. Even though the terrain cannot be drawn by user, numerous parameters of the terrain generator may be adjusted to achieve desired terrain.

Source code

The source code can be (will be) released under the GNU General Public License version 2 or later.

References

ClanLib is used as the basis of our game.