Introduction

In the previous lesson we added the radar to make our car's control panel near enough complete. In this lesson we will add the enemy cars.

Structure

If you are manually creating the project make file then use the one from lesson 7, there are no changes.
The following files will be changed in this lesson:

Tranzam

Every game needs enemies. In our game we have enemy cars that will chase after the player and either crash into him/her or force them to crash into an obstacle. The enemy cars will have the same functionality as the main player's car with the exception of a slightly lower top speed.
We want the enemies to make chase when the player is within a certain range (e.g. a radius of 40 tiles), and stop when out of range. The enemy will crash into obstacles the same as the player. If we alter a few key characteristics of the enemy car, e.g. the range check, whether they use the handbrake, how they slow down to turn, it should make things more interesting. For now, we will simply add the cars to the game, make them collidable but not move them. For that, we will need the next lesson, which is all about path finding.
The enemy car is found in the transam.bmp file and is simply a black version of the player car.

Code Walkthrough of game_Vars.cpp/game_transam.cpp/game_transam.h

game_vars.cpp has just a few updates. Firstly we have moved the mapXoff/mapYoff to be globals so that we can access it in the car class. Secondly, because we are adding the enemy cars to the local radar, we need a graphic for it (LocalEnemy).
It is created within the game_transam.cpp the same way as all the other static bitmaps:
LocalEnemy =GameLibrary->GetFirstGraphic("localenemy");
(remember, this returns the first graphic referenced by the id localenemy)

We are storing the enemies in a simple array. Within the game_transam.h file we've added a constant to determine the maximum number of enemies within the map, MaxEnemies. This has been set initially at 20.
The array of enemies is an array of pointers to EnemyCar objects. The EnemyCar is a class derived from our Car class. All we are doing for now is overriding the methods that we know are going to be different: SetupCar (because we need to initialise the enemies at random locations on the map), NextMove (because it will ultimately be moved automatically using a path-finding algorithm), CheckCollision (collision will be handled differently, e.g. the car will not die when hitting a mountain, does not need fuel, etc).
class EnemyCar : public Car { public: void SetupCar(); bool NextMove(); protected: void CheckCollision(); };
We could of course have made these virtual methods, but we will (probably) never need to loop through all instances of cars in one go (i.e. we will control the player car at one point and the enemy car at another) and so can avoid the extra overhead of virtuals. The class will probably evolve once we start moving the enemies, but for now this is sufficient.
Within SetupCar() we need to place the enemies. This will be done in a similar way to how we placed the Cups: We pick a random number representing a x/y location on the map and check if it is a blank tile:
loc=MapGetBlockInPixels((int)mapx,(int)mapy); if(loc->br) break;
Once found, it's mapx/mapy contains it's location the same way as mapx/mapy stores the players location. Remember, 'br' is the flag we set aside as being a blank tile.
Because we don't want to move the car or do anything fancy, EnemyCar::NextMove() does two things. First it updates the screenx/screeny variables that set the correct location of the car on the map (as an offset of where the map is actually placed on screen), and second we call the collision detection method.
The CheckCollision method (for now), does nothing other than reset a flag - HitCar is a flag set within the Car code to show it has hit an enemy.
We will check for collisions between the player and the enemies from within the player's car check collision method. This is done by looping through the enemies and doing a bounding box check (i.e. we generate a rectangle to show the player and the enemy and check if the rectangles overlap), as follows:
for(int i=0;i<MaxEnemies;i++) { if(check_bb_collision_general(screenx+7,screeny+7,CarWidth-14,CarHeight-14,Enemies[i]->screenx+7,Enemies[i]->screeny+7,Enemies[i]->CarWidth-14,Enemies[i]->CarHeight-14)) { HitCar=true; Enemies[i]->HitCar=true; break; } }
'check_bb_collision_general' is a macro and is part of the PPCOL library. It simply makes box to box collision detection an inline piece of code. Because we aren't going to use pixel perfect collision (we could, but it is probably overkill in this game) we need to ensure the box check works no matter what angle the cars are at, i.e. because the cars are not perfect polygons and are longer then they are wide we will end up with problems when the cars are at an angle. The simply solution is to make our bounding box smaller. Hence why in the code we are shrinking the rectangle by a number of pixels. This can be seen in the game when we draw these rectangles to show a collision.

game_game.cpp/h Code Walkthrough

TransAmGameLogic is called every frame, one thing it does is update the player's car using Player->NextMove(). This is the car's main movement and control method, as we've discussed in previous lessons.
We now need to update the enemy cars, and it is done in the same way:
for(int i=0;i<MaxEnemies;i++) { Enemies[i]->NextMove(); }

In the TransAmGameDrawing we need to update the enemy cars, both on the map screen and the local radar. To determine if the enemy car should be shown on the screen, we check if it is roughly in range of the player, and we draw it based on it's angle - obviously the enemy car isn't moving at present, but the code is now in place for when it does. As a temporary measure in order to see that we have made a collision we are going to draw a red rectangle around the enemy car.
We also need to update the local radar to show the enemy car's if they are in range. This is done by looping through the enemies and checking how far they are from the player.
To remove the need to show the enemy car locations on screen using a series of textout debug statements, we will update the national radar (the map of America) to show the location of the enemy cars. In the original game this was not done, so once the game is finished we can take it out or if we are going to add a difficulty option for the game, the easy mode could include this feature. It is coded in the TransAmDrawing in the same way as we drew the fuel (using a scaling factor), as in:
for(int i=0;i<MaxEnemies;i++) { newx=(((int)Enemies[i]->mapx/mapblockwidth)/LocalMapScale); newy=(((int)Enemies[i]->mapy/mapblockheight)/LocalMapScale); //putpixel(localMap,Enemies[i]->mapx/LocalMapScale,Enemies[i]->mapy/LocalMapScale,makecol(0,0,255)); rectfill(GameFramework->DrawingSurface,LocalMapX+newx,LocalMapY+newy,LocalMapX+newx+1,LocalMapY+newy+1,makecol(0,0,255)); }

In the InitialiseLevel routine (called on starting a new level) we need to initialise the enemy cars as well as the player.
Player->SetupCar("player",Reload); for(int i=0;i<MaxEnemies;i++) Enemies[i]->SetupCar();
The player setup has not changed.
Finally we need to create the enemy cars, and that is done in the LoadLevel() function and is no different to the way we create our player,
Player=new Car(); for(int i=0;i<MaxEnemies;i++) Enemies[i]=new EnemyCar();

Allegro

Just to recap, we've create an enemy car based on the player car and updated the code to draw the enemy car on the local radar. Also, wherever the player car is updated or drawn, we are repeating with the enemy cars. Remember, if you use something like WinMerge, the code differences are made obvious.
So, all that remains is to compile the code and try out the game. Remember to collide with the enemy and see the red rectangle show a hit and try to find the enemies using the national radar.

Next

The next lesson we will be learning about the A* path finding algorithm so that we can make the enemy cars chase the player. We won't be using the TransAm code, rather creating a new program to test out path finding.