Introduction

In the previous lesson we used the A* algorithm to plot the target path from the enemy car to the player and display it on screen. In this lesson we will be translating this path into actual movements to make the enemies chase the player.
This is the last lesson and while not a complete game, contains everything required to make it one.

Structure

If you are manually creating the project make file then use the one from lesson 10, there are no new files.
This version will mostly be updating the game_car.cpp and game_game.cpp files. If you already have the tutorial other than this lesson 11 you can download this update via
THIS LINK (470kb)

Tranzam

As a recap from the previous lesson, the idea for the enemy car movement is that they will remain where they are until the player comes in range (about 40 tiles - 3 screens worth). When in range an enemy will start using the path finding algorithm to track and follow the user.
Because the player is moving most of the time, we need to update each enemy's path, otherwise it will never find the player. Obviously doing so every frame is overkill and will slow down the game too much, so the Animation's custom countdown timer will be used to redo the path every few seconds.
The code to be written will be similar to that which we used in the previous lesson for drawing the path, so hopefully will make perfect sense :)
To see what it is that this code will do, compile and run the program. Remember that on the national map the enemies are in blue. Finding two or more close together will show how they can all generate their own paths.
One thing that has caused much in the way of difficulty is stopping the enemy cars from hitting collision objects. Although the A* path finds the route, one of the problems is that it usually ends up changing direction just before a collision object (e.g. a tree) rather than some random tile along the path. This can be seen by running the previous tutorial.
This is a problem because the car is usually moving too fast to change direction quick enough and usually hits collision items.
Another problem is diagonal movement. In the screenshot you can see the enemy is trying to reach the car and picks a valid route diagonally to the car. The red tiles are collision items and will stop/bounce the car if hit. As far as the A* algorithm is concerned these aren't a problem because they are not in the path, however if you think about it when the car moves diagonally, when it hits the edge of the red, the car is actually spanning four tiles, one of which is a red one.
The simplest solution for this game was to stop the enemy cars from going past collision items like this.

Code Walkthrough of game_astar.cpp/h

A new method was added, PeekNextNode, to the A-Star class. This allows you to get the next tile in the generated path without updating the path (currently NextNode method gets the next item in the path but also changes to that node.)
This was to allow the enemy car to see the next tile to make a better judgement on whether to alter course or not, but it didn't work out, but I left in the method anyway!

Code Walkthrough of game_vars.cpp

Three new variables are added, AStarDistance1 (2 and 3). These are used to determine when to start using the A* path and how often to update it. In the previous lesson we updated it every second, in this game we will change it's speed based on how close to the player the car is.
The idea is to reduce the amount of times it has to be re-generated while still keeping it accurate. This game (and will be seen when the game_car.cpp file is analysed) updates the A* map more often the closer to the player the enemy car gets.

Code Walkthrough of game_game.cpp

Firstly the code for drawing the path as green rectangles (from the enemy car to the player) is removed as we don't need it - although the same methods for using the A* class will be implemented in the game_car.cpp file for moving the enemy car. This is/was in the function TransAmGameDrawing.
RegenerateAstarMap() function is called when the level is loaded or the player dies. The purpose is to let the A* class know which tiles are open/free and which are blocked. Because of the problems discussed above, one of the changes is to surround each blocked tile (e.g. a tree) with another tile that cannot be entered, this will give the car a chance to navigate around obstacles at speed. However, the disadvantage being that the enemy car may not enter 'clumps' of collision items if all gaps are filled with these new blocked tiles.
The change made is that for every tile with a collision tile that is added to the A* array, the surrounding 8 tiles are updated to show that they are blocked too. However, to make it so that errors can happen and paths may still be made through clumps of tiles, a random number of these 8 tiles will not be set.
If you open up the map file within the editor you will notice a new tile, which is main brown land tile with a red dot. This was used for debugging, but may be used as an aid to see how these generated blocked tiles are made. With the RegenerateAstarMap() function is the following code repeated for each of the eight tiles:
if(!temp->tl) MapSetBlock(j-1,i+1,57);
Simply uncomment each of these, and when the map is shown, all these blocked tiles will be shown as the tile just described.
Another minor change is that each enemy car has a new data member, 'num', and is simply a unique number. This is used in the car code when the code to stop enemy cars colliding with each other. In the LoadLevel() function, each generated enemy car is given a unique number.

Code Walkthrough of game_car.h

To make it easier to code, all the constants are changed from integers to floats, e.g. MAXVEL. Also a couple of new constants are added to provide a top speed for the enemy cars (Because they will have a slower top speed). Because we've changed these from int to float, we have to move the initialisation of the variables from the header file to the source code, as const float types can't be set in the header file.
Two new data members, AutoMove and AutoDirection are added. These are to override movement of the enemy car, regardles of what path it is supposed to be following. They will be populated in the collision detection code to make the enemy cars not bump into each other.
Also, a method 'CrossProduct' is added, however it isn't used! it returns the cross product of the two vectors effectively. One thing it can be used for is determining what direction (clockwise or anti-clockwise) one object has to turn to face another, or in this case we could use it to work out which way to turn the car to get to it's next angle the quickest. However, the code settled for is simply to use one of 8 pre-determined angles (45 degree intervals).

Code Walkthrough of game_car.cpp

Basically we want the car to follow our A* path. What we need to do is move the car from where it is to the next node in the path. There are a number of problems associated with this. After determining the next tile in the path we need to rotate the enemy car to the correct angle so that it will actually pass through this tile, we also need to update the path to point us to the next node at exactly the right time (i.e. when we move from one tile to the next we need to find the next tile to go to). We also need to constantly update the map to keep track of the player's movements as the player can change at any time.
So, the rules we'll be using are as follows: These are coded as follows. Firstly, if you're using a file comparison utility you'll notice quite a few minor changes, these are not documented here as they mostly tidy up the code and fix minor bugs or apply small tweaks (e.g. change the braking speed).
At the bottom of the 'NextMove' method we updated the timers (by calling the NextMove method of the animation) if the player was dying - as that is the only animation. However, we are using timers for each car now (detailed below) so need to update the timers every frame, this is why the method is changed from being called when the player is dying to every frame.
In the CheckCollision() method a change is made that when an enemy is hit, it changes to a random angle to disorient it.
In the enemy code, SetupCar() method, a change is made so that the enemy car is only placed if it is at least 40 tiles away. This is to avoid the player getting hit immediately and to allow the player to think about moving.
The main changes to the code are in the enemy's NextMove and CheckCollision methods. If you have the code open, visit the NextMove method for the enemy. If no route has been generated from the enemy to the player, one is created. A timer is set up so that we know when to regenerate the path. As mentioned before, this is done at different frequencies based on how close the enemy car is to the player.
Next, if a route has been generated and the timer has ran out (this->car->ReadOnly_Timers[1] flag is only set when a custom timer has been created and it has reached it's time), a new route is generated.
Once we have a route and a new route is not required we can move the enemy. Firstly we get the the tile that the A* is currently pointing at (Route->NodeGetX and Route->NodeGetY). Next we get the tile the enemy car is at. If the car is not at the same tile as the one the A* route is pointing at then we simply update the route to point to the next tile in the path (Route->PathNextNode).
Next we work out which of the surrounding 8 tiles the next tile is (i.e. where the enemy should be going) and set the target angle, which is one 45 degree increments (i.e. movement diagonally and horizontal/vertical). We then decide whether to turn the enemy car left or right.
The next section is one of much change and determines whether to accelerate, brake or coast along. This is based on the angle the enemy car has to turn. A minor turn means the enemy car can be accelerating as not much turning is required, a major turn means the car needs to slow down and so it is set to brake.
Next the auto direction (the override for avoiding other enemy cars) is checked.
After this the collision detection is called and all calculations for moving the car are performed. This code (the altering of velocity, etc.) is taken directly from the player's car code.
The CheckCollision method of the enemy is used to determine if the enemy car has hit something. The first check determines if the enemy has hit the player or a collision item, in which case it rebounds and sets a timer. This timer is used to stop the enemy car from moving for a couple of seconds.
Next, all the enemy cars are looped and check for collisions (hence why we added the 'num' variable mentioned earlier - we don't want the enemy car to check for collision against itself). The basic premise of the enemy car avoidance is simple enough: if a collision between this enemy car an another happens then move it left if the other enemy is not being moved by a collision against another enemy, and if the other enemy car is moving right automatically then this car moves left.
After this, all the same checks as the player car are performed, e.g. hitting a mountain, a blank tile, etc. The exception being the enemy car never dies, simply rebounds and slows down, giving the player a chance to get away from it.
We also check for the enemy car hitting the special tile that surrounds real collision items. We need this because of a consequence of using this to make the enemy car move around objects better - if a car hits a collision item it bounces back and stops, if it ends up in one of these special tiles (remember, they are 'invisible' to the user as they are only set in the A* map and not on the actual map). This will result in the enemy car not escaping as it will end up with it's velocity stopped due to the 'collision'. This special tile is set by the mappy user variable, user7.

Next

That is it really. I hope you enjoyed the lessons and took something away from it, other than a few wasted minutes :)
Feel free to use the code for any purpose.