Introduction

In the previous lesson we loaded the map and scrolled around it using the cursors. In this lesson we will add the car and instead of scrolling the map using the cursors, we will be scrolling the map using the car (i.e. we will be driving!). As usual, because this is lesson 3, the source files and project files are stored in the 'version3' directory.

Structure

If you are manually creating the project make file then use the one from lesson 2 and additionally you will need to add the file game_car.cpp.
The file game_car.cpp will be used to store everything about the cars in the game (both the player and the enemies).
The following files will be changed in this lesson:

TranzAm

The car will be controlled by the cursor keys and will move on a per-pixel basis, employing some basic acceleration/deceleration mathematics. When moving an item around the tile map freely (as in our car), you cannot add it directly to the tile map, instead you store it as a regular sprite and synchronise it's position with the map. Why? because tile maps work with tiles and the unit of measurement in a tile map is the tile (tile size). If our car moved one tile at a time we could store the car as a tile and simply alter the tile array, however our car will be moving in pixels.
In our game, although the car will move around the map when driving, it will always be centred on the screen. There are various ways of displaying scrolling games: some use this approach and some have a defined 'window' that the player can move within without the map scrolling. While our way is much simpler, the problem arises when you get to the edges of the map - how do you ever get to the edge when you are fixed to the centre? The answer is quite simple in our game - we have more sea around the land!
If you think about it there really isn't any difference between lesson 2 (scrolling using the cursors) and scrolling by using the car - the car isn't moving around the view at a different speed to the whole screen scrolling as it is fixed to the centre. The only difference is that in the previous lesson we drew from the top-left corner of the screen, and in this lesson the car is at the centre of the screen, requiring us to offset any location by half the width/height.

Car

The class definition for our car is quite simple and is as follows:
enum CarDirection {CAR_STRAIGHT,CAR_LEFT,CAR_RIGHT}; enum CarMovement {CAR_FREEWHEELING,CAR_ACCELERATE,CAR_DECELERATE}; class Car { public: static const int MAXVEL=10; Car() { angle=screenx=screeny=angle=0; acc=vel=decel=xdistancemoved=ydistancemoved=0; mapx=mapy=0.0; car=NULL; } ~Car(); void SetupCar(); bool NextMove(); AXL_Projects::Animation* car; int screenx,screeny; //position to draw on the screen float mapx, mapy; //real position in map int angle; float xdistancemoved,ydistancemoved; //position moved CarMovement Move; CarDirection Direction; private: void Accelerate(); void Decelerate(); void TurnRight(); void TurnLeft(); public: float acc; float vel; float decel; };
The car (forget about any enemy cars, assume the Car class is purely for our player's car) has the following public data items. These are variables that you can use within the game because they are useful. Now I know most people will say you can't have public data items, you need to hide them, and that is true, however hiding data has a performance hit and quite frankly I can live with the shame. Think of it as using a subset of C++ for all it's benefits over C and you'll get over it: We are also storing a few private variables to hold our current acceleration, velocity and deceleration.
Our Car will have the following publically available methods: We are also storing a few private methods to perform the underlying actions of acceleration, braking and turning.
If you notice, the Car has two positions: one is the position on the actual map (mapx/mapy) and the other is the location on the screen to draw the car (screenx/screeny).

Code Walkthrough (game_car.cpp)

The method SetupCar() initialises all car variables.
Moving the car will be achieved by the following formula (in the method NextMove):
vel=vel-(acc*0.1)+decel; xdistancemoved=sin(angle)*vel; ydistancemoved=cos(angle)*vel;

Where vel is our current Velocity, acc is our current acceleration, decel is our current deceleration. The car's angle (angle) is increased/decreased by the user pressing the left/right cursor keys. Setting this value was just trial and error. In this case it is by 9 degrees every two frames, i.e. 4.5 degrees per frame (or if we have 60FPS, 270 degrees per second).
Velocity (vel) is simply increased or decreased by a number whenever the up or down cursor is pressed. Again by trial and error, 0.1 seems a nice enough value for now.
In later lessons, this will be enhanced somewhat. For now, we just want to get our car moving.
NextMove() is the main method in game_car.cpp, and is what is called within the game logic function TransAmGameLogic() every frame. The method does the following for now:
  1. Sets acceleration to 0 at the start of each frame (in this lesson it isn't actually used) and deceleration to be 0.5 - i.e. we assume that if no keys are pressed the car is decelerating
  2. The keyboard is checked for cursor movement and ESCape (moved from TransAmGameLogic in lesson 2) and the relevant flags set
  3. If accelerating/decelerating the velocity (described above) is altered
  4. If turning, the angle (described above) is altered
  5. A check for the map's boundary is made and if the car is at the edge, it is bounced off it
  6. Using the calculated velocity, the displacement of the car (from the previous frame) is calcuated (as above for [x/y]distancemoved variable)
  7. The car's actual map co-ordinates are adjusted

Code Walkthrough (game_game.cpp)

From the previous lesson, we are still using the two variables mapYoff/mapXoff to store the location of the map we wish to draw. However, these two values are calculated from on the Car's member variables mapx/mapy (which store the location of the car). Because mapXoff/mapYoff store the topleft of the drawing buffer, we calculate them using an offset of the Car's mapx/mapy to centre the car on the screen. I guess an advantage of this, is if we have multiple cars on the screen (e.g. enemy cars) we can easily switch view to those (e.g. a demo mode or a split screen mode for two players).
The main call within TransAmGameLogic (our logic function) is as follows:
if(Player->NextMove()) { CleanUp(); return true; }
This calls the NextMove method for the Player's Car (which updates the car's position, handles collision, etc). If ESCape is pressed the NextMove() method will return 'true'
In the drawing code (TransAmGameDrawing), the only change required is to draw the car. This is done after the background tiles and before the foreground tiles. We could equally do it after foreground tiles, it doesn't really matter at present.
Inside the animations.xml file are the definitions for the graphics we are using. The car is just one graphic (there is no animation for it), and all movements (turning) are performed by using the Allegro rotate_sprite function (using the Car's current angle).
The rest of the code is basically the same as lesson 2, but just moved around to be a bit neater and to allow for when or if we need to expand any of the functions.

The Car object

When creating variables, it's always best to keep the scope as narrow as possible, i.e. don't create globals if you can use local. Following that insightful bit of knowledge, we reckon that the Player's car will only exist within the game_game.cpp file, as such it is defined as a file level variable as follows: static Car* Player=NULL;
it is initialised in the setup code as follows: Player=new Car();
it is destroyed when the game ends.
So, all that's left is to compile the code and drive your car. It should look something like the screen below. screenshot

Allegro

Don't know if you've noticed, but in the lessons so far we have had very little to do with Allegro, other than checking the keyboard and rotating a sprite. This is because all of the Allegro code that you would normally write (timers, buffers, etc.) are all performed by the AXL library, leaving you to concentrate on writing the game. Which is how it should be. At a guess I would say the majority of the lessons will be like this.

Next

In the next lesson, we'll be making the car's movement a bit better and populating some of the display panel's sections (mileage, distance travelled, fuel, temperature).