Introduction

In the previous lesson we made parts of the car's display panel active. In this lesson we will be adding a menu and learning about animations. The menu will allow us to change graphics modes (e.g. windowed/fullscreen), drawing modes (double/triple buffering and page flipping), enabling vsync for double buffering, displaying and updating animations, using XML as our main data store.
We will also be seeing how to update the Framework's automated calling system (currently we are using TransAmGameLogic/ TransAmGameDrawing function pair) to have multiple timer loops. And while we are here, we'll add some proper animations to see how they are coded.

Structure

If you are manually creating the project make file then use the one from lesson 4 and add the file game_menu.cpp
The following files will be changed in this lesson:

Animations

In the transam.bmp sheet graphic is the animations for a spinning cup, we will be putting this on the menu. There are two reasons for this, first it is to show that the menu is running inside a timer loop, just like the actual game, and secondly because the main game won't be using many animations!:

In the animations.xml file it is referenced as:

<animation id="cup" bmp_file="transam.bmp" originx="0" originy="70" sheetitemwidth="60" sheetitemheight="92" sequencecount="9" />
What the above is saying is that the cup animation is located at point 0,70 in the bitmap file, it has 9 frames that make up the animation and each frame is 60x92 pixels in size. When the animation is loaded (using the Framework constructor explained in lesson 1) the Animation library generates the frames correctly.
At the top of the animation file is the 'global' section. This stores attributes that will be given to each animation/graphic item where no value is specified. The file's global section is as follows:
<global loop="start" millisecondwait="100" depth="32" sheetgrid="1" isasheetgraphic="1" ppmask="0" bmp_type="1" />
All attributes are fully explained in the XML and in the AXL documentation. The item 'sheetgrid' is set as 1 because this tells the animation/graphic loader that each graphic/frame has a single pixel border around it that needs to be removed. It is there just to make it easy for a human to determine the edge of each graphic item. Note also, that these items can be applied at different levels, e.g. millisecondwait can be on a per frame basis.
Stepping through animations is no more difficult than displaying static graphics. With the animation object that you create (using the 'new AXL::Animation' constructor that we have used for the display panel, etc) we simply call it's NextMove() public member every frame, and as always the 'ReadOnly_CurrentItem' bitmap pointer is used to display the item. The code walk-through below shows how this is done in more details.

Configuration

In previous lessons, we briefed upon the config.xml. You may have modified a few parts of it to change the way the game is displayed. Our menu system will let us do all the changes within the game rather than modifying the XML by hand.
So first up, a brief introduction to using the configuration XML file and how it makes it easy to set up your system (e.g. initialising allegro, graphics modes, etc).
When the framework is initialised it looks for the file config.xml (or whatever you have told it to call). There are two parts to this file, the Allegro values and your custom values. A sample config.xml is as follows:
<config> <custom name="version" valuestring="1.0" /> <custom name="skipintro" valueint="1" /> <system fps="60" debugon="0" autowritemain="1" autowritecustom="1" enablejoystick="1" enablekeyboard="1" /> <graphics vsync="0" graphicsmode="3" depthpreferred="32" depthfallback="16" /> <window width="640" height="480" autodetect="windowed" /> <sound maxvoicearray="32" samplevolume="150" musicvolume="128" /> </config>
All known elements (the one's above) are held in the AXL Configurator class (remember, in the setup function we get our instance of this via the 'GameConfiguration' variable).
Remember, within the game_transam.cpp file we initialise the system using the code:

GameFramework=new Framework("config.xml","animations.xml");

And it is from this call that the config.xml file is read and the system initialised.

Custom Configuration Items

All custom elements have two attributes, the name and the value. The name is a unique string used to identify the item. The value is the value it holds. The AXL Configurator supports strings, integers or floats (using the attribute names valuestring, valueint and valuefloat respectively). When the configuration file is read, all the custom values are stored and can be retrieved using the GetCustom() method. This takes two parameters, the first is the name of the custom value (i.e. the 'name' attribute) and the second takes a default value in case of it not being there or some kind of error (e.g. a string was stored for an integer field).
For example, to read a custom element called 'last_high_score' you would do:int LastHighScore=GameConfiguration->GetCustom("last_high_score",0);
To update a custom value, or to create a new custom value, use the SetCustom method, for example:
int GameConfiguration->SetCustom("last_high_score",50);
There are two ways to write the data to the XML file. First is by calling the method FlushCustom(string) with the file to update as the parameter, e.g. GameConfiguration->FlushCustom("config.xml");, if no parameter is supplied it will be the same as the file used for reading during the Framework initialisation.
The second is to simply let the system do it for you, i.e. they can be written automatically when the Framework object is deleted. In order for the values to be automatically written, the allegro value 'autowritecustom' needs to be set to "1". As can be seen in the XML snippet above, it is set and so will automatically update the XML on exit of the game.

Allegro Configuration Items

The Allegro/game configuration items were touched upon in the previous lessons. The AXL Configurator help section and the default config.xml file contains full descriptions of all values. These values are grouped into system, graphics, window and sound sections. All values are stored as structs within the Configurator object. For example, assuming GameConfiguration is our configuration object (as custom section above), we have access to these values via GameConfiguration->CapsSystem, GameConfiguration->CapsGraphics, GameConfiguration->CapsSound.
Full details are given in the Configurator section. Each of these corresponds to the elements in the config.xml (or your equivalent) file, i.e. system, graphics/window, sound elements.
To get or set a value, you simply update the struct value (all values are public), e.g. GameConfiguration->CapsSound.MusicVolume=200;. To save the value to the XML you can either call the FlushMain() method - this is the same process as calling FlushCustom method described above, or you can set the attribute 'autowritemain' in the xml to 1 (as the Custom configuration item section) and let the Framework save it for you. Of course you can set this value in code (e.g. GameConfiguration->CapsSystem.autowritemain=1 or GameConfiguration->CapsSystem.autowritecustom=0).
However, when you update the Allegro values, they will not be reflected in the game immediately because Allegro will already have been initialised. To get Allegro to update itself with any changes to these values, call the Framework (not Configurator) method RestartSystem, e.g. success=GameFramework->RestartSystem(). This will destroy all graphics and buffers, restart Allegro, load all graphics and animations and set Allegro buffers, etc. This method will only work if the configuration was called via an XML file (you can actually do everything in code without XML) and you must ensure than any objects you have created (e.g. Animation) are deleted first, otherwise you will cause your system to crash (all pointers will be invalidated and probably point to other areas of memory). One cautionary note, while this function has been tested, it has been seen a few times where the keyboard has gone funny on re-initialisation.
Finally, one feature of the Framework is that it will (if permitted) try it's best to give you what you want, and if not available, will try other settings. For example if you specify triple buffering and windowed mode, but it can only do paging with windowed mode, it will do this instead.
These values are stored in CapsActualSystem (rather than CapsSystem). You still write to CapsSystem if updating, but when retrieving the following values, it is best to read from CapsActualSystem, otherwise the results may not be correct: UseSound, UseMidi, DepthUsed, GraphicsMode. The first two show simply whether sound/midi were initialised (no direct Allegro System equivalent), the colour depth achieved (CapsSystem.Depth) and the graphics mode (CapsGraphics.RenderMode).
In the code walkthrough below, we will be making use of these values and methods/functions.

Code Walkthrough (game_vars.cpp, game_transam.cpp)

We add our cup animation to the game_vars.cpp the same way as we have so far added our static graphics, by declaring an Animation object:

Animation* Cup;

Is declared in the game_vars.cpp file, and the cup is initialised in the SetupGame() function inside game_transam.cpp

Cup =new Animation(GameLibrary,"cup");

In the same way as we have created our other graphics.

Multiple Timer Routines

So far we have used one timer logic/drawing function pair: TransAmGameLogic/TransAmGameDrawing. The two functions are stored in the game_game.cpp file and invoked by the Framework from the Framework object's StartGameLoop() method as follows:
GameFramework->StartGameLoop( NULL,NULL, NULL,NULL, TransAmGameLogic,TransAmGameDrawing, NULL,NULL, false)
This is called in our main() function in game_transam.cpp. With the Framework you can store up to four default function callbacks (the first non-null being the one called by the StartGameLoop).
We are now going to add our menu callback so the code will be changed as follows:
GameFramework->StartGameLoop( NULL,NULL, TransAmMenuLogic,TransAmMenuDrawing, TransAmGameLogic,TransAmGameDrawing, NULL,NULL, false)
Because our Menu callback functions are now first in the list, these two function pairs (logic/drawing) will be called first. They are stored in game_menu.cpp
As you can probably see now, for each function pair we are creating a new source file with a similar name. This will allow us to easily track our code.
All well, but you may be wondering, a) why four pairs of functions, b) how do you change between them, c) what happens when you change, d) what if I want more.
First up, the Framework object has a method called ChangeTimerType(). This tells the Framework's timer code (the one that runs all the time calling your logic/drawing your bitmaps) to simply call a different function on each frame. It isn't bothered what it calls as internally it is just calling a function pointer. Obviously you need to use the same drawing buffer, etc.
There are four constants (enumerators) called GAME_LOGO, GAME_MENU, GAME_GAME, GAME_EXIT and these correspond to the four function pointers you pass into StartGameLoop. To change functions, you call the ChangeTimerType() method with the relevant constant. For example,
GameFramework->ChangeTimerType(GAME_MENU);
Will change the Framework's timer loop to call the functions set by the GAME_MENU (in our case we have called them TransAmMenuDrawing/TransAmMenuLogic). Hopefully you will see that the names are arbitrary, you can call whatever you want. It just made sense to call them these as these are usually the four stages of a game.
These functions will be highlighted in the code walkthrough below. Adding more function pointers is made available via the Framework's method SetAutoGameLoopOverride(). This will be covered by a later lesson, but briefly it lets you pass in a function pointer pair on a temporary basis, i.e. returning true from it will not exit the game as happens with the StartGameLoop function pointers, instead it will return back to the current logic function that called this method. This is useful when within your logic/drawing you need to do a lot of coding and separating it out will make sense, for example within the game you might want a pause screen to provide an options screen.
Finally in this overview section, quitting the game logic (e.g. TransAmGameLogic) does not automatically call the function pointed to by GAME_EXIT, you have to do this yourself via the ChangeTimerType(), as these are just a set of unrelated function pointers, nothing more.
Probably that made no sense. Fear not, the next section should make it a bit clearer.

Code Walkthrough (game_menu.cpp)

Open up the file game_menu.cpp and have a read. What you'll see is pretty basic and nothing you haven't learnt in the previous lessons.
We have two functions called TransAmMenuLogic and TransAmMenuDrawing. These (from the previous section) are now called first when you start the program, rather than the TransAmGameLogic/TransAmGameDrawing functions. The Logic function is used to handle the keypresses and the Drawing function is used to draw the menu.
These two functions are also used to draw the animated cup. Remember the Logic function is called once per frame. At the top if this function we have:
Cup->NextMove();
This method is available to all Animation objects and tells it to update all it's animations. It changes animation frames, updates countdown timers, performs fading and updates collision masks. The frames are updated according to the time set within the XML file (in our case one new frame every 100ms), the end result is the ReadOnly_CurrentItem points to the correct frame for the animation.
Within the TransAmMenuDrawing function we draw the cup like all other graphics, as follows:
draw_sprite(GameFramework->DrawingSurface, Cup->ReadOnly_CurrentItem, 280, 130);
That is all is needed for animations. We will be seeing in later lessons how to use countdown timers and the other features of animation objects.
Earlier we showed how to update the config.xml (or your equivalent) values by updating the Configuration objects structs. This is the bulk of the TransAmMenuLogic code. For example, to change between windowed and fullscreen, the values are set as follows:
case KEY_2: GameConfiguration->CapsGraphics.WindowMode="windowed"; break; case KEY_3: GameConfiguration->CapsGraphics.WindowMode="fullscreen"; break;
If the system needs to be re-initialised then the function we talked about earlier, RestartSystem(), is called. This is shown at the end of the TransAmMenuLogic function.
Note that in order to free up the memory we used in the Animations and to reinitialise them, we wrap this call around our EndGame and SetupGame functions. This works because it is only in these two functions that we create and destroy our allocated memory objects.
The TransAmMenuDrawing function should be pretty straightforward as it is just a sequence of Allegro textout calls to display the current system status and the menu options available. As per usual we are drawing everything to GameFramework->DrawingSurface.
Hopefully you will see that there is no difference between how we code these two functions and the two TransAmGamexxx functions.
In order to start the game, we need to tell the Framework to change timers, using the method described in the section above:
case KEY_1: restart=false; GameFramework->ChangeTimerType(GAME_GAME); break;
And this calls the functions we allocated to GAME_GAME (TransAmGameLogic/TransAmGameDrawing). To exit we simply return true, as we did within the game in the previous lesson.

Code Walkthrough (game_game.cpp)

The only real change to the game is when we press ESCape we do not want to quit the game, instead we wish to return to the menu. So instead of returning 'true' from the TransAmGameLogic, we change the timer type, as in:
GameFramework->ChangeTimerType(GAME_MENU); return false;

TranzAm

The game works the same as before, with a minor tweak to fix a bug in the way the car's temperature is handled.

Allegro

Again, very little Allegro knowledge has been required, other than using textout and draw_sprite
I'm hoping at this point, you will have understood the way in which we've coded the menu. Because we are using the AXL Framework to handle a lot of the stuff it's sometimes hard to follow why things happen. Just remember that on invoking the StartGameLoop a FPS based timer loop is running non-stop calling either a logic or a drawing function that you can change in code, and it handles triple/double buffering and page flipping as long as all you do is use the 'DrawingSurface' BITMAP pointer for all your drawings.
So, all that remains is to compile the code and try out the menu. Keep a backup just in case you set an option that makes your game fail to start!

Next

In the next lesson, things start to get exciting ;)
We will be adding collision detection so that you can bump into all the foreground items, such as trees and mountains. Adding collision detection also means we can get the fuel supply working (i.e. you will be able to increase your fuel, instead of just dying when you run out!) and set up the system of dying (everytime you hit an object you will sustain damage and ultimately lose a life).
We will also learn about how we set up our tile map to store custom values for each tile, for example setting the fuel tile, the road, collision items, etc).