Introduction

In the previous lesson we created our menu, added an animated graphic and used multiple timer functions. In this lesson we will be adding collision detection to the car so that we can bump into foreground items (e.g. trees, mountains) and removing the dual control method we talked about removing in a previous lesson, dynamically adding the cups to the tile map and adding an override timer to show that we have won the game. This is the start of the game taking shape and actually doing something :)

Structure

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

Mappy

tile value Every tile you create within Mappy can store a number of custom values. They are set by double clicking on the tiles within Mappy. The screenshot shows the dialog that contains each tile's custom values. Note, within Mappy, the tiles are referred to as Blocks. If you open the mappyal.h header file from the libraries directory, this is defined near the top and is a struct called BLKSTR. The seven text boxes (with zero's) are called user1 to user7 and represent numeric values, the three checkboxes labelled 'other' are called unused1, unused2, unused3, and the four checkboxes labelled 'collision' are called tr, tl, bl, br (corresponding to the four corners they are drawn within the dialog box). The checkboxes are bit field values. These values can be set and used for any reason. The section titled 'collision' is more of a historical thing and does not represent collisions - it is simply another set of four checkboxes to go alongside the 'other' checkboxes. The only flag that means something to Mappy is the 'BG Transparency' and tells Mappy to take into account magic pink colours.
If you notice in the picture our tile/block consists of two actual tiles - the BG tile which is the standard map background, and the cactus (FG1). This is how we create tiles comprising multiple images.
In our game we will be using them as follows: As you can see from the picture, this tile is a cactus, has the 'tl' item checked and is made up of a background tile (sand) and a foreground tile (the cactus). In our code (explained in earlier lessons) that draws the tiles (game_game.cpp) the function MapDrawBGT(...) will draw all the background items (the sand in this instance) and the function MapDrawFG(...) will draw the foreground tiles (the cactus in this instance).
If you are in the editor, double click on the first tile and use the two buttons to the right of the BG/FG pictures ('<' and '>') to navigate through the tiles and see what values are set.

Mappy provides a number of functions for retrieving block information. What we will be doing is finding out where the car is, getting the block details and checking the values. The function to retrieve block information has the signature: BLKSTR* CurrentBlock=MapGetBlockInPixels (this->mapx,this->mapy);. This function will get the block at a pixel location, which is how we are storing the car details). BLKSTR as mentioned returns a block structure containing the values of the tile. In the code walkthrough for the Car we will discuss this further.

Code Walkthrough (game_game.cpp)

When a new game/level starts we will randomly place the cups on the map (the point of the game is to collection all the cups). This is done in the LoadLevel() function.
The code relevant to us is the loop at the bottom of the function. MapGetBlock(x,y) is a Mappy function that gets a block (stored in a struct called BLKSTR, as mentioned above) using tile locations. We are using the value 'br' to signify that it is a block that is empty and can be used to hold a cup. In our map 'br' is stored for the blank ground tile (the yellowish coloured tile). Once our randomly searched tile is of this type we use the Mappy function MapSetBlock(x,y,n), where x/y are the locations we found using MapGetBlock, and n is the tile number (in the picture above for the cactus, you can see that it is 42.). We then hold the locations of the cups in an array.
What we've also done in the code is an option to reload completely a map during InitialiseLevel(), using the 'Reload' parameter. This is so that we only load and setup the map on first entry (from the menu).
Within the TransAmGameDrawing function we are showing (as debug data) on screen the locations of the cups, so when we test the game we can find them. We are also updating the display to show the number of lives left and cups collected.
We've also added (to TransAmGameLogic) an update to handle dying. Within the Car class a data member 'State' is added to show the car's status (NORMAL, DYING, DEAD). This is explained in the next section.

Branching Timers

Within your timer logic (and drawing, but usually logic) you might want to perform an extended or complex task. For example, a game pause/options menu. But we don't want to make it a completely separate timer event (as in one of our four GAME_GAME, GAME_LOGO, GAME_MENU, GAME_EXIT) as it is linked in some way to the current timer event. You may want to do this, for no reason other than to avoid lots of conditional checks in the logic.
We are doing this in the game when you've won the game, as ultimately we want a fancy ending :)
In the game_game.cpp timer Function TransAmGameLogic we have the following code at the start:
if(NumCups==MaxNumCups) { newLevelRequired=true; clear_keybuf(); if(!WinGame) GameFramework->SetAutoGameLoopOverride(WinGameLogic,WinGameDrawing); else { NumCups=0; GameFramework->ChangeTimerType(GAME_MENU); } WinGame=true; return false; }
Firstly the code is only entered when we have all the cups. Secondly, when the game is over we call the ChangeTimerType to the menu so that on exit we go back to the menu. This is the same call as we use when the ESCape key is pressed.
As mentioned earlier, calling SetAutoGameLoopOverride will tell the Framework to start calling the two functions passed in at it's next pass, rather the ones it has currently (in this case GAME_GAME). The key difference is that on exiting the function, control returns back to the suspended loop (in our case GAME_GAME).
So, ignoring what WinGameLogic/WinGameDrawing does, once our game over routine has finished, control will be returned to TransAmGameLogic. At this point we still have all the cups but the flag WinGame has been set to true, so the end game is not called again and instead the GAME_MENU is set.

game_wingame.cpp/h

This file contains the logic/drawing functions we set in the timer override function in the previous section. It will eventually hold a glorious game over screen. For now, all it does within the WinGameLogic is wait for the ENTER key to be pressed, and in the WinGameDrawing it outputs a message.
What has happened is within the TransAmGameTimer function we told the Framework that it should stop calling TransAmGameLogic/Drawing within it's FPS based timer, and instead call WinGameLogic/Drawing functions. This is all it does. When we press ENTER, it returns true, which signifies to the Framework that our override function has finished and it should return to calling the functions it has. In effect, we are simply changing the drawing/logic. This is really good as the overhead is minimal (i.e. two pointer changes) but we can modularise our game's functionality and know that it is within a FPS based timer loop.

Collision Detection

In our Car class we know what location on the map we are, so in order to perform collision detection and process each tile, we use the MapGetBlock(...) function to get the block details of the tile the car is currently on. Obviously because the car can move in pixel increments and our tiles are quite large (32x32), we could end up spanning multiple tiles. In this version what we are going to do is presume that the car is moving at a reasonable speed at all times and only check the centre point of the car (full pixel perfect collision is probably not necessary, though at some point in the future, a polygon based tile detection may be a good idea).

game_car.cpp/h

In the Car header file we add a function 'CheckCollision' that we will call every frame to check for collisions and we add the variables we have already discussed, e.g. State, Damage. We are also going to add a turbo option. This will simply increase the top speed if the player is on a road.
At the end of the NextMove method (our main method that is called every frame from within our TransAmGameLogic function) we call this new method (CheckCollision).
The top of this method gives a summary of what the block values do. Because our car is placed in pixels, we call the MapGetBlockInPixels Mappy function that returns a block using pixels rather than tile locations. From this block we will then check each of the custom variables of the tile/block to see if it is set, then perform the relevant actions: I'm hoping at this point, you've followed the code and saw that it is pretty simple to get/set/use tile values and apply them within a game.

Animations, Fading, Countdown Timers

Animations

Hopefully you know that when you create an animation, the constructor is passed an ID so that we know what graphic/animation to use from the animations.xml file. For example,
DisplayTemperature =new Animation(GameLibrary,"temperature"); Cup =new Animation(GameLibrary,"cup");
The Animation class has a method, SetNewAnimation, that lets us change the animation of the animation object. What this does, is changes it's internal data to point to the new graphics and it's values. For example, if you have a player and you have different animations for walking left and right you could do something like:
if(key[KEY_LEFT]) Player->SetNewAnimation("walk_left"); if(key[KEY_RIGHT]) Player->SetNewAnimation("walk_right");
And on pressing the key, the animation would change.
In our animations.xml file we have the following entry:
<animation id="dying" bmp_file="transam.bmp" millisecondwait="300" sheetitemwidth="32" sheetitemheight="32" originx="99" originy="0" sequencecount="2" />
Which describes an animation of two frames called 'dying' and corresponds to the following two images in our sheet graphics file transam.bmp
dying
It will repeat itself every 300 milliseconds.
Going back to the game, what we want to do on dying is change the car to this dying animation. Within the game_car.cpp, checkcollision() method, you'll see in the statements that check the 'br' and 'user6' flags, we determine if the player is to die. If so, we change the animation: car->SetNewAnimation("dying"); When the level or game restarts, the Player object is deleted and recreated, thus returning back to normal.
The only thing left to explain is why we have CAR_DYING and CAR_DEAD, and what 'car->SetCustomCountDown' does.
Addressing the first point, when the player is dead, as well as changing to the dying animation, we wish to show the animation for a fixed time before continuing with the game. If we didn't, we would never see the dying animation. For this reason we have two flags. The first CAR_DYING tells the code that the player is dying (i.e. it will continue to draw the game and update logic, but it will not do any more collision detection). The second flag, CAR_DEAD will be set after a number of seconds after CAR_DYING to tell the game that we are actually dead. This then lets the TransAmGameLogic know that we need to reduce the life count and start the level again (or end it if no more lives are left). How it knows a number of seconds are passed is done via the Animation object...

Countdown Timers

As you know, the game runs in a timer context and is how animations work. This is done because each animation's NextMove() method is called every frame. So, as a no cost extra to the timer there is a method available for each Animation called 'SetCustomCountDown'. This takes in two parameters, an id number and a value in milliseconds. The id is there because we allow a maximum of five (which seemed a nice compromise between speed and size when writing the Animation class). This can easily be changed, indeed it is a #define at the top of the axl.h file for you to change to optimise the system.
Once this function is called, the Animation object updates it's countdown timers every frame. To find out when a countdown timer has finished we need to check whether the timer has reached zero. Rather than code another method to retrieve the value, probably due to laziness, you access the timer value via the array ReadOnly_Timers. It holds a value of 1 if that timer has expired.
Within our game_game.cpp file we check this as follows:
if(Player->State==CAR_DYING && Player->car->ReadOnly_Timers[1])
This says that if the timer 1 has been set (i.e. it is down to zero) and the car's state is dying then the we know the player is dead. In this case (it is within TransAmGameLogic) we reduce the life count and either start the level again or quit if game over.
Hopefully, you will see that using custom timers is very useful. btw, 'car' is the Player object's animation.

Fading

Although not used in this game so far, each animation object can be faded. The functionality is similar to the way the custom timers are used. To fade an animation you call the FadeIn or FadeOut methods. These either fade from the current bitmap to a colour or fade in from a colour to the current bitmap. Examples can be found in the AXL documentation for the Animation (from the menu to the left).
The method does not support alpha-channels, hence why you have to specify a colour. However this is not a problem. One of the parameters you pass in, 'alterbmp', is a true/false flag and tells the animation object whether to actually perform the fade (i.e. alter the bitmap) or just pretend to do it. This sounds odd, but is useful in the case of the alpha-fade: The fade value of each Animation object is reflected in the ReadOnly_Timers array (that is used for the custom countdown) element zero, i.e. ReadOnly_Timers[0]. However, instead of simply showing a flag of when it is set, it contains the actual fade value in the range 0 to 255. This value is updated each frame and goes from 0 to 255, 255 to 0 in the time you specify. This then lets you use the Animation object to control the fade speed accurately for you, and you simply use this array element in your own code for fading, blending or whatever else you need this value for.

Allegro

Just to recap, we've seen how to change animations, how to add countdown timers, how to suspend the current timer code and use another piece of code, how to store and read tile values to perform collision detection and other triggers within the game.
So, all that remains is to compile the code and try out the game.

Next

In the next lesson, we will be updating the final two areas of our car's panel, the national and local radar maps.