Chapter 18. Using Datafiles to Store Game Resources

Suppose you have written one of the greatest new games to come out of the indie market in years, and you are chomping at the bit to get the game out into the world. You poured your blood, sweat, and tears into the game and it has cost you every moment of your free time for three years. Your friends and relatives have abandoned you, and you haven't emerged from your room in months, focused on and dedicated to one goal—making this the most unbelievable game ever. You have come up with a new gaming technology that you think will create a whole new genre. It's the next Doom or Warcraft.

There's just one problem. You have spent so much time getting the game running and polished that you have paid no attention to the game's resources. Now, faced with distribution, you are struggling to come up with a plan for protecting your game's resources—all the amazing artwork (that you had commissioned from a professional artist), sound effects and music (commissioned from a sound studio), and professional voice acting at various parts throughout the game. You have valuable assets to protect. You have thought about finding a ZIP decompression library, but are not looking forward to the problems associated with temp files.

Luckily for you, you planned ahead and developed your new cutting-edge game with Allegro. And it just so happens that Allegro has support for datafiles to store all of your game resources with encryption and compression. Best of all, it's extremely easy to use, and you don't need to deal with temp files. Ready to learn how to do this?

Here is a breakdown of the major topics in this chapter:

Understanding Allegro Datafiles

Allegro datafiles are similar to ZIP archive files in that they can contain multiple files of different types and sizes, with support for encryption and compression. However, Allegro datafiles differ from the files of general-purpose archival programs in that they are geared entirely to store game resources. Allegro datafiles use the LZSS compression algorithm when compression is used.

Datafiles are created by the Allegro Datafile archiving utility and have a .dat extension. They can contain bitmaps, sounds, FLI animations, Mappy levels, text files, and any other type of file or binary data that your game will need. You can distribute your game with a single executable and a single datafile.

One of the best things about datafiles is that, because they are so easy to create and use, you can use a datafile for any program you write, not just games. This really adds a strong degree of appeal to Allegro even for general-purpose programming, developing command-line utilities and support programs in addition to full-blown game editors and similar programs. Instead of distributing a background image, sprite image, and sound file in a small game, simply bundle it all together in a datafile and send it off to your friends with the program file. In other words, you don't have to reserve datafile use only for big projects; you can use datafiles frequently even on non-game projects.

Datafiles use a struct to keep track of their resources. The struct looks like this:

typedef struct DATAFILE
{
  void *dat;   //pointer to the actual data
  int type;    //type of the data
  long size;   //size of the data in bytes
  void *prop;  //list of object properties
} DATAFILE;

When you refer to an object in a datafile, you must use a DATAFILE struct to get at the resource. Usually you will not need to be concerned with anything other than the dat member variable, which is a void pointer to the object in the file (or in memory after the datafile has been loaded).

Would you like a quick example? Although I haven't covered the load_datafile function yet, here is an example of how you might load a datafile into memory and then grab a sprite directly out of the datafile:

DATAFILE *data = load_datafile("game.dat");
draw_sprite(screen, data[PLAYER_SPRITE].dat, x, y);

If you want to identify a resource by type (for instance, to verify that the resource is a valid type), you can use the type member variable of the DATAFILE struct. Table 18.1 provides a list of the various types of objects that can be stored in a datafile.

Table 18.1. Datafile Object Types and Formats

Data Type

Format

Description

DAT_FILE

"FILE"

Nested datafile

DAT_DATA

"DATA"

Block of binary data (miscellaneous)

DAT_FONT

"FONT"

Font object

DAT_SAMPLE

"SAMP"

Sound file

DAT_MIDI

"MIDI"

MIDI file

DAT_PATCH

"PAT"

GUS patch file

DAT_FLI

"FLIC"

FLI animation file

DAT_BITMAP

"BMP"

Bitmap file

DAT_RLE_SPRITE

"RLE"

Compressed sprite

DAT_C_SPRITE

"CMP"

Linear compiled sprite

DAT_PALETTE

"PAL"

256-color RGB palette

DAT_END

N/A

Special flag to mark the end of the data list

Creating Allegro Datafiles

Before you can practice using datafiles, you need to learn how to create and manage them. This will also give you a heads-up on extracting the game resources from other people's games that use Allegro datafiles! Not that I would condone the theft of artwork. . .but it is interesting to see how some people develop their artwork.

Note

You might need to compile the Allegro 4.2 source code in order to get the dat program, which is distributed in source code form. For instructions on compiling Allegro, refer to the build instructions for your operating system, located on the CD-ROM in Allegro 4.2manualuild. Once you have compiled Allegro, the dat program will be found in the tools folder under the main Allegro folder on your hard drive. I have pre-compiled the Allegro 4.2 library for the Windows platform (look on the CD-ROM in Allegro 4.2Allegro 4.2 sourcesall420). The Visual C++ and Dev-C++ versions have been pre-compiled for your convenience. You will find the dat.exe program in the folder for this chapter on the CD-ROM.

Allegro comes with a command-line utility program called dat.exe that you will use to create and manage your datafiles. The Allegro utilities are located in the tools folder inside the root Allegro folder, wherever you installed it (based on the sources). If you have extracted Allegro to your root drive folder, then the dat.exe program is likely to be found in allegro ools. You will need to open a command prompt or shell and change to that folder to run the program. Alternatively, you might want to just add allegro ools to your system path. In Windows, you would do that by typing

path=C:allegro	ools;%path%

After you do this, you will be able to maintain your datafiles from any folder on the hard drive because dat.exe will be included in the path. Here is the output from dat.exe if you run it with no parameters:

Datafile archiving utility for Allegro 4.2.0, MinGW32
By Shawn Hargreaves, 2005

Usage: dat [options] filename.dat [names]

Options:
  '-a' adds the named files to the datafile
  '-bpp colordepth' grabs bitmaps in the specified format
  '-c0' no compression
  '-c1' compress objects individually
  '-c2' global compression on the entire datafile
  '-d' deletes the named objects from the datafile
  '-dither' dithers when reducing color depths
  '-e' extracts the named objects from the datafile
  '-g x y w h' grabs bitmap data from a specific grid location
  '-h outputfile.h' sets the output header file
  '-k' keeps the original file names when grabbing objects
  '-l' lists the contents of the datafile
  '-m dependencyfile' outputs makefile dependencies
  '-o output' sets the output file or directory when extracting data
  '-p prefixstring' sets the prefix for the output header file
  '-pal objectname' specifies which palette to use
  '-s0' no strip: save everything
  '-s1' strip grabber specific information from the file
  '-s2' strip all object properties and names from the file
  '-t type' sets the object type when adding files
  '-transparency' preserves transparency through color conversion
  '-u' updates the contents of the datafile
  '-v' selects verbose mode
  '-w' always updates the entire contents of the datafile
  '-007 password' sets the file encryption key
  'PROP=value' sets object properties

I'm not going over all these options; consider it your homework for the day. The really important thing to know about the dat.exe syntax is the usage.

Usage: dat [options] filename.dat [names]

When you run dat.exe, first you must include any options, then the name of the datafile, followed by the files you want to add to (or extract from) the datafile. Looking through the options, you see that –a is the parameter that adds files to a datafile. But you must also use the –t option to tell dat what kind of file you are adding. Go ahead and try it. Locate a bitmap file, change to that directory from the command prompt (or shell), and adapt the following command to suit the bitmap file you intend to add to the datafile.

dat -a -t BMP -bpp 16 test.dat back.bmp

Do you see the -bpp 16 parameter? You must specify the color depth of the bitmaps you are adding to the datafile or it will treat them as 8-bit images (one byte per pixel). I have used the -bpp16 parameter to instruct the dat program to store the file as a 16-bit bitmap. The output from dat should look something like this:

test.dat not found: creating new datafile
Inserting back.bmp -> BACK_BMP
Writing test.dat

Now you can find out whether the bitmap image is actually stored inside the test.dat file.

dat -l test.dat

You should see a result that looks something like this:

Reading test.dat
- BMP - BACK_BMP        - bitmap (640x480, 16 bit)

Great, it worked! Now there's just one problem. I see from the options list that I can add compression to the datafile using the -c2 option, so I'd like to reduce the size of the file. Here is the command to do that:

dat -c2 test.dat

The output looks like this:

Reading test.dat
Writing test.dat

I see that the file has been reduced from 900 KB to about 100 KB. Perfect!

Now let's add another file to the datafile, and then I'll explain how to get to these objects from an Allegro program. Let's add another bitmap file called ship.bmp to the datafile:

dat -a -t BMP -bpp 16 test.dat ship.bmp

Here is the output:

Reading test.dat
Inserting ship.bmp -> SHIP_BMP
Writing test.dat

Now that you have added two files to the datafile, take a peek inside:

dat -l test.dat

produces this output:

Reading test.dat
- BMP - BACK_BMP                    - bitmap (640x480, 16 bit)
- BMP - SHIP_BMP                    - bitmap (111x96, 16 bit)

If you take a look at the file size, you'll see that it is still compressed. Trying to compress it again results in the same file size, so it's apparent that once -c2 has been applied to a datafile, compression is then applied to any new files added to it.

I should also point out that you should reference the objects in the file in the order they are displayed using dat -l test.dat. You can reference the back.bmp file using array index 0, explode.wav using array index 1, and so on.

The dat tool is able to generate a header file containing the datafile definition of values using the -h option. This will save you the trouble of adding constants or definitions in your program to reference the objects in the datafile, because the dat.exe program can do this for you. Here is an example:

dat test.dat -h defines.h

That produces a file that looks like this:

/* Allegro datafile object indexes, produced by dat v4.2.0, MinGW32 */
/* Datafile: test.dat */
/* Date: Sat Sep 16 20:49:59 2006 */
/* Do not hand edit! */

#define BACK_BMP                  0          /* BMP */
#define SHIP_BMP                  1          /* BMP */

It is best to include this header file directly in your project and not edit it manually. When you include the datafile definition header in your project, and then you re-run the dat program to rebuild the header, it will automatically be updated in your game project. For this reason, it's best to include the file rather than paste the definitions in your code—unless your program is small.

Using Allegro Datafiles

You have learned some details about what datafiles are made of and how to create and update them. Now it's time to put them to the test in a real Allegro program that will load the datafile and retrieve game objects directly out of the datafile. First you need to go over the datafile functions to learn how to manipulate a datafile with source code.

Loading a Datafile

The load_datafile function loads a datafile into memory and returns a pointer to it or NULL. If the datafile has been encrypted, you must first use the packfile_password function to set the appropriate key. See grabber.txt for more information. If the datafile contains true color graphics, you must set the video mode or call set_color_conversion() before loading the datafile.

DATAFILE *load_datafile(const char *filename);

Note

If you are programming in C++, you will get an error unless you include a cast for the type of object being referenced in the datafile. Here is an example:

draw_sprite(screen, (BITMAP *)data[SPRITE].dat, x, y);

Unloading a Datafile

The unload_datafile function frees all the objects in a datafile and removes the datafile from memory.

void unload_datafile(DATAFILE *dat);

Loading a Datafile Object

The load_datafile_object will load a specific object from a datafile, returning the object as a single DATAFILE * pointer (instead of the usual array).

DATAFILE *load_datafile_object(const char *filename, const char *objectname);

Here is an example:

sprite = load_datafile_object("datafile.dat", "SPRITE_BMP");

Unloading a Datafile Object

The unload_datafile_object function will free an object that was loaded with the load_datafile_object function.

void unload_datafile_object(DATAFILE *dat);

Finding a Datafile Object

If you would rather not use pre-defined object names as constants in your program, there is another option: you can search for an object by name inside the datafile. The find_datafile_object function searches an opened datafile for an object with the specified name, returning a pointer to the object or NULL. This method is probably more convenient when using a really huge datafile, especially if it changes frequently, but it may slow down load times.

DATAFILE *find_datafile_object(const DATAFILE *dat, const char *objectname);

Testing Allegro Datafiles

Now that you have a basic understanding of how datafiles are created and what the data inside a datafile looks like, it's time to learn how to read a datafile in an Allegro program. I have written a short program that loads the test.dat file you created earlier in this chapter and displays the back.bmp and ship.bmp files stored in the datafile. You should be able to use this basic example (along with the list of datafile object types) to use any other type of file in your programs (such as samples or Mappy files). Figure 18.1 shows the output of the TestDat program.

The TestDat program demonstrates how to read bitmaps from an Allegro datafile.

Figure 18.1. The TestDat program demonstrates how to read bitmaps from an Allegro datafile.

#include <allegro.h>

#define MODE GFX_AUTODETECT_WINDOWED
#define WIDTH 640
#define HEIGHT 480
#define WHITE makecol(255,255,255)

//define objects in datafile
#define BACK_BMP 0
#define SHIP_BMP 1

int main(void)
{
  DATAFILE *data;
  BITMAP *sprite;

  //initialize the program
  allegro_init();
  install_keyboard();
  install_timer();
  set_color_depth(16);
  set_gfx_mode(MODE, WIDTH, HEIGHT, 0, 0);

  //load the datafile
  data = load_datafile("test.dat");

  //blit the background image using datafile directly
  blit(data[BACK_BMP].dat, screen, 0, 0, 0, 0, WIDTH-1, HEIGHT-1);

  //grab sprite and store in separate BITMAP
  sprite = (BITMAP *)data[SHIP_BMP].dat;
  draw_sprite(screen, sprite, WIDTH/2-sprite->w/2,
      HEIGHT/2-sprite->h/2);

  //display title
  textout_ex(screen,font,"TestDat Program (ESC to quit)",0,0,WHITE,-1);

  //pause
  while(!keypressed());

  //remove datafile from memory
  unload_datafile(data);

  allegro_exit();
  return 0;
}
END_OF_MAIN()

Enhancing Tank War

This chapter will see the final enhancement to Tank War! It's been a long journey for this game, from a meager vector-based demo on through the various stages to bitmaps, sprites, scrolling backgrounds, and animation. The final revision to the game (the ninth) will add sound effects to the game and incorporate the resources into a datafile, as explained in this chapter. In addition, since this is the last update that will be made to Tank War, I have decided to throw in a few extras for good measure.

At the time when we covered joysticks back in Chapter 5, it was premature to add joystick support to Tank War. Much time has passed, and you have learned a great deal in the intervening chapters, so now you'll finally have the opportunity to add joystick support to the game. Along the way, I'll show you how to limit the input routines a little to make the tanks move more realistically.

By the time you have finished this section, Tank War will have sound effects, joystick support, and improved gameplay. All that remains is for you to create some new map files using Mappy to really see how far you can take the game! I would also suggest that you play with the techniques learned in Chapter 16 for testing collisions with Mappy tiles in order to add solid blocks to Tank War. As that is beyond the goals of this chapter, I leave the challenge to you. Now let's get started on the changes to the game.

Note

Although this chapter concludes the official progression of work on Tank War, I have not stopped working on the game! A whole new version is available on the CD-ROM in sourcesTankWar-Final. The changes are so dramatic that it would have required scores of pages to list the entire source code, and the changes were too dramatic to feature a step-by-step process of changes as we have been doing thus far.

So, I have decided not to cover the final revision of the game in the text. Instead, you may open the project to examine the source code and play the game. The Epilogue at the end of Chapter 22 includes screenshots and a rundown of the final version of Tank War.

Modifying the Game

The last revision to the game was back in Chapter 14, when you added Mappy support to it. Now let's work on adding sound effects, joystick support, and tweak the gameplay a little. If you haven't already, open up the Tank War project from Chapter 14 to make the proposed changes. You can also open the completed project in chapter18 ankwar_r8 if you wish. At the very least, you'll need to copy the wave files out of the folder and into the project folder on your hard drive. Here is a list of the files needed for this enhancement:

  • ammo.wav

  • fire.wav

  • goopy.wav

  • harp.wav

  • hit1.wav

  • hit2.wav

  • ohhh.wav

  • scream.wav

These wave files have been added to a datafile called sounds.dat, which is stored in the tankwar_r8 folder. The sound files provide a good example for using a datafile in a complete game. You can look in the setup.c source file for the loadsounds function, which still uses an array of samples, but that array is filled with sound pointers from the datafile, rather than directly from the wave files.

Generating the Datafile Header

After adding the wave files to sounds.dat, I have generated the header of constants representing the samples in the file, which produced this output:

/* Allegro datafile object indexes, produced by dat v4.2.0, MinGW32 */
/* Datafile: sounds.dat */
/* Date: Wed Aug 30 02:11:54 2006 */
/* Do not hand edit! */

#define AMMO_WAV                         0        /* SAMP */
#define FIRE_WAV                         1        /* SAMP */
#define GOOPY_WAV                        2       /* SAMP */
#define HARP_WAV                         3        /* SAMP */
#define HIT1_WAV                         4        /* SAMP */
#define HIT2_WAV                         5        /* SAMP */
#define OHHH_WAV                         6        /* SAMP */
#define SCREAM_WAV                       7        /* SAMP */

Save this in a file called datafile.h or produce it yourself with the following command:

dat sounds.dat -h datafile.h

Modifying tankwar.h

The first change occurs in tankwar.h, as there are some variables that are needed for this enhancement and a new function prototype. First, let's add a new header file to the project up near the top, and a definition for the DATAFILE pointer variable.

#ifndef _TANKWAR_H
#define _TANKWAR_H

#include <stdlib.h>
#include "allegro.h"
#include "mappyal.h"
#include "datafile.h"

//define the datafile object
DATAFILE *datafile;

Scroll down in tankwar.h to the variables section and add the lines noted in bold:

.
.
.
//sprite bitmaps
BITMAP *tank_bmp[2][8][8];
BITMAP *bullet_bmp;
BITMAP *explode_bmp;

//double buffer
BITMAP *buffer;

//screen background
BITMAP *back;

//variables used for sound effects
#define PAN 128
#define PITCH 1000
#define VOLUME 128
#define NUM_SOUNDS 8
#define AMMO 0
#define HIT1 1
#define HIT2 2
#define FIRE 3
#define GOOPY 4
#define HARP 5
#define SCREAM 6
#define OHHH 7
SAMPLE *sounds[NUM_SOUNDS];

//some variables used to slow down keyboard input
int key_count[2];
int key_delay[2];

//function prototypes
void loadsounds();
void readjoysticks();
void loaddatafile();
void animatetank(int num);
void updateexplosion(int num);

Modifying setup.c

Now open the setup.c source code file.

Add the new loaddatafile and loadsounds functions to the top of the file. This function loads all the new sound effects that will be used in Tank War.

void loaddatafile()
{
  datafile = load_datafile("sounds.dat");
  if (datafile == NULL) {
      allegro_message("Error loading datafile");
      return;
  }
}

void loadsounds()
{
     //install a digital sound driver
     if (install_sound(DIGI_AUTODETECT, MIDI_NONE, "") != 0) {
         allegro_message("Error initializing sound system");
         return;
  }

  //load the ammo sound
  sounds[AMMO] = (SAMPLE *)datafile[AMMO_WAV].dat;
  if (!sounds[AMMO]) {
      allegro_message("Error loading ammo.wav");
      return;
  }
  //load the hit1 sound
  sounds[HIT1] = (SAMPLE *)datafile[HIT1_WAV].dat;
  if (!sounds[HIT1]) {
      allegro_message("Error reading hit1.wav");
      return;
  }
  //load the hit2 sound
  sounds[HIT2] = (SAMPLE *)datafile[HIT2_WAV].dat;
  if (!sounds[HIT2]) {
      allegro_message("Error reading hit2.wav");
      return;
  }
  //load the fire sound
  sounds[FIRE] = (SAMPLE *)datafile[FIRE_WAV].dat;
  if (!sounds[FIRE]) {
      allegro_message("Error reading fire.wav");
      return;
  }
  //load the goopy sound
  sounds[GOOPY] = (SAMPLE *)datafile[GOOPY_WAV].dat;
  if (!sounds[GOOPY]) {
      allegro_message("Error reading goopy.wav");
      return;
  }
  //load the harp sound
  sounds[HARP] = (SAMPLE *)datafile[HARP_WAV].dat;
  if (!sounds[HARP]) {
      allegro_message("Error reading harp.wav");
      return;
  }
  //load the scream sound
  sounds[SCREAM] = (SAMPLE *)datafile[SCREAM_WAV].dat;
  if (!sounds[SCREAM]) {
      allegro_message("Error reading scream.wav");
      return;
  }
  //load the ohhh sound
  sounds[OHHH] = (SAMPLE *)datafile[OHHH_WAV].dat;
  if (!sounds[OHHH]) {
      allegro_message("Error reading ohhh.wav");
      return;
  }
  //cannons are reloading
  play_sample(sounds[0], VOLUME, PAN, PITCH, FALSE);
}

A little ways farther down in this file we come to the loadsprites function. Make the following change to add datafile support:

void loadsprites()
{
     //load explosion image
     if (explode_bmp == NULL)
     {
         explode_bmp = load_bitmap("explode.bmp", NULL);
     }

     //initialize explosion sprites
     explosions[0] = (SPRITE*)malloc(sizeof(SPRITE));
     explosions[1] = (SPRITE*)malloc(sizeof(SPRITE));
}

Modifying bullet.c

Now open the bullet.c file, in order to add some function calls to play sounds at various points in the game (for instance, during an explosion). The first function in this file is updateexplosion. Down at the bottom of this function is an else statement. Add the play_sample line as shown.

  }
  else
  {
     //play "end of explosion" sound
     play_sample(sounds[HARP], VOLUME, PAN, PITCH, FALSE);

     explosions[num]->alive = 0;
     explosions[num]->curframe = 0;
  }
}

Now scroll down a little to the explosion function. Add the new lines of code as shown. You might be wondering why there are three sounds being played at the start of an explosion. It's for variety! The three sounds together add a distinctive explosion sound along with a light comical twist. Remember that Allegro mixes sounds, so these are all played basically at the same time.

void explode(int num, int x, int y)
{
    //initialize the explosion sprite
    explosions[num]->alive = 1;
    explosions[num]->x = x;
    explosions[num]->y = y;
    explosions[num]->curframe = 0;
    explosions[num]->maxframe = 20;

    //play explosion sounds
    play_sample(sounds[GOOPY], VOLUME, PAN, PITCH, FALSE);
    play_sample(sounds[HIT1], VOLUME, PAN, PITCH, FALSE);
    play_sample(sounds[HIT2], VOLUME, PAN, PITCH, FALSE);
}

Now scroll down to the movebullet function. You'll be making a ton of changes to this function, basically to add more humorous elements to the game. Whenever a bullet hits the edge of the map, a “reload” sound is played (ammo.wav), which tells the player that he can fire again. Remember that bullets will keep on going until they strike the enemy tank or the edge of the map. The next change to this function is quite funny, in my opinion. Whenever there is a “near miss” of a bullet close to your tank, one of two samples is played. If it's player 1, the scream.wav sample is played, while ohhh.wav is played for a near miss with player 2. This really adds a nice touch to the game, as you'll see when you play it. Now, just go ahead and make all the changes noted in bold.

void movebullet(int num)
{
   int x, y, tx, ty;
   x = bullets[num]->x;
   y = bullets[num]->y;

   //is the bullet active?
   if (!bullets[num]->alive) return;

   //move bullet
   bullets[num]->x += bullets[num]->xspeed;
   bullets[num]->y += bullets[num]->yspeed;
   x = bullets[num]->x;
   y = bullets[num]->y;

   //stay within the virtual screen
  if (x < 0 || x > MAPW-6 || y < 0 || y > MAPH-6)
  {
     //play the ammo sound
     play_sample(sounds[AMMO], VOLUME, PAN, PITCH, FALSE);

     bullets[num]->alive = 0;
     return;
  }

  //look for a direct hit using basic collision
  tx = scrollx[!num] + SCROLLW/2;
  ty = scrolly[!num] + SCROLLH/2;
  if (inside(x,y,tx-15,ty-15,tx + 15,ty + 15))
  {
     //kill the bullet
     bullets[num]->alive = 0;

     //blow up the tank
     x = scrollx[!num] + SCROLLW/2;
     y = scrolly[!num] + SCROLLH/2;

     //draw explosion in enemy window
     explode(num, tanks[!num]->x, tanks[!num]->y);
     scores[num]++;

     //kill any "near miss" sounds
     if (num)
         stop_sample(sounds[SCREAM]);
     else
         stop_sample(sounds[OHHH]);
  }

  else if (inside(x,y,tx-30,ty-30,tx + 30,ty + 30))
  {
    //it's a near miss!
    if (num)
        //player 1 screams
        play_sample(sounds[SCREAM], VOLUME, PAN, PITCH, FALSE);
    else
        //player 2 ohhhs
        play_sample(sounds[OHHH], VOLUME, PAN, PITCH, FALSE);
  }
}

Now, scroll down a little more to the fireweapon function. I have added a single play_sample function call that plays a sound whenever a player fires a bullet. This is the basic “fire” sound. Add the line shown in bold.

void fireweapon(int num)
{
  int x = scrollx[num] + SCROLLW/2;
  int y = scrolly[num] + SCROLLH/2;

  //ready to fire again?
  if (!bullets[num]->alive)
  {
     //play fire sound
     play_sample(sounds[FIRE], VOLUME, PAN, PITCH, FALSE);

     bullets[num]->alive = 1;

Modifying input.c

Next, open the input.c file. The first thing that must be done in this file is to add a new function called readjoysticks. This function first verifies that a joystick is connected, and then tries to scan the input of one or two joysticks if present. If you have two joysticks or gamepads, try plugging them into your PC to see how much fun Tank War can be when played like a console game! Add the new readjoysticks function to the top of input.c.

void readjoysticks()
{
     int b, n;

     if (num_joysticks)
     {
         //read the joystick
         poll_joystick();

         for (n=0; n<2; n++)
         {
              //left stick
              if (joy[n].stick[0].axis[0].d1)
                  turnleft(n);

              //right stick
      if (joy[n].stick[0].axis[0].d2)
          turnright(n);

      //forward stick
      if (joy[n].stick[0].axis[1].d1)
          forward(n);

      //backward stick
      if (joy[n].stick[0].axis[1].d2)
          backward(n);

      //any button will do
      for (b=0; b<joy[n].num_buttons; b++)
           if (joy[n].button[b].b) {
               fireweapon(n);
               break;
         }
    }
  }
}

Next, you need to make some modifications to the forward, backward, turnleft, and turnright functions. These changes help to slow down the device input so it's easier to control the tanks (previously, you may recall, the tanks would turn far too fast). This also makes the tank movement feel more realistic, as you must speed up gradually rather than going from 0 to 60 in 0.5 seconds, as the game played before. Note the changes in bold.

void forward(int num)
{
  if (key_count[num]++ > key_delay[num]) {
      key_count[num] = 0;

      tanks[num]->xspeed++;
      if (tanks[num]->xspeed > MAXSPEED)
          tanks[num]->xspeed = MAXSPEED;
  }
}

void backward(int num)
{
  if (key_count[num]++ > key_delay[num]) {
   key_count[num] = 0;

   tanks[num]->xspeed--;
   if (tanks[num]->xspeed < -MAXSPEED)
       tanks[num]->xspeed = -MAXSPEED;
  }
}

void turnleft(int num)
{
  if (key_count[num]++ > key_delay[num]) {
      key_count[num] = 0;

      tanks[num]->dir--;
      if (tanks[num]->dir < 0)
          tanks[num]->dir = 7;
  }
}

void turnright(int num)
{
  if (key_count[num]++ > key_delay[num]) {
      key_count[num] = 0;

      tanks[num]->dir++;
      if (tanks[num]->dir > 7)
          tanks[num]->dir = 0;
  }
}

Now the last change we'll make is to the getinput function. There has been a rest function call in here since the first version of the game, while the timing of the game belongs in the main loop. Just delete the line indicated in bold (and commented out).

void getinput()
{
  //hit ESC to quit
  if (key[KEY_ESC])   gameover = 1;

  //WASD - SPACE keys control tank 1
  if (key[KEY_W])     forward(0);
  if (key[KEY_D])     turnright(0);
  if (key[KEY_A])    turnleft(0);
  if (key[KEY_S])    backward(0);
  if (key[KEY_SPACE]) fireweapon(0);

  //arrow - ENTER keys control tank 2
  if (key[KEY_UP])       forward(1);
  if (key[KEY_RIGHT]) turnright(1);
  if (key[KEY_DOWN])   backward(1);
  if (key[KEY_LEFT])   turnleft(1);
  if (key[KEY_ENTER]) fireweapon(1);

  //short delay after keypress
  //rest(20);
}

Modifying main.c

Next up is the main.c file, the primary source code file for Tank War, containing (among other things) that game loop. Scroll down to main and add the calls to loaddatafile and loadsounds as indicated in bold. It's important that you call loaddatafile before any other of the helper functions.

//main function
void main(void)
{
  int anim;

  //initialize the game
  allegro_init();
  install_keyboard();
  install_timer();
  srand(time(NULL));
  loaddatafile();
  setupscreen();
  setuptanks();
  loadsprites();
  loadsounds();

Next, scroll down a little bit past the section of code that loads the Mappy file, and add the new code shown in bold. This code initializes the joystick(s) and sets the input delay variables.

//load the Mappy file
if (MapLoad("map3.fmp")) {
    allegro_message ("Can't
    return;
}
//install the joystick handler
install_joystick(JOY_TYPE_AUTODETECT);
poll_joystick();
//setup input delays
key_count[0] = 0;
key_delay[0] = 2;
key_count[1] = 0;
key_delay[1] = 2;

Now, scroll down to the end of the game loop and insert or change the following lines of code after the call to getinput as shown in bold. You'll insert a call to readjoysticks and modify the rest function call to increase the delay a bit (since the delay in getinput was removed).

  //check for keypresses
  if (keypressed())
      getinput();
  readjoysticks();

  //slow the game down
  rest(30);
}

Now let's clean up the memory that was used by these new changes. Scroll down a little bit more and insert the following code after the call to MapFreeMem as shown in bold.

  //free the MappyAL memory
  MapFreeMem();

  //remove the sound driver
  remove_sound();

  //remove the joystick driver
  remove_joystick();

  return 0;
}
END_OF_MAIN()

Final Comments about Tank War

Figure 18.2 shows the latest version of Tank War. It's been a long haul, and you've seen the game grow from a meager vector game to the current incarnation with animated sprites and scrolling backgrounds. Let's list the features of the latest version of the game:

The newest version of Tank War.

Figure 18.2. The newest version of Tank War.

  • Two-player split-screen gameplay

  • Scrolling battlefield

  • Support for new maps created with Mappy

  • Advanced update code shows all the action in both windows

  • Keyboard and joystick support

  • 64 animated frames for each tank

  • Numerous sound effects enhance gameplay

  • Supports maps with up to 30,000 tiles

  • Battlefield can be up to 5,500 × 5,500 pixels in size

  • Runs on Windows, Linux, Mac OS X, and many other systems

Summary

This chapter provided an introduction to Allegro datafiles and showed you how to create them, modify them, and read them into an Allegro program or game. Datafiles make it much easier to distribute your games to others because you need only include the datafile and executable program file. Datafiles can contain any type of file, but some items are predefined so they are recognized and handled properly by Allegro. Although we are technically done with Tank War now, be sure to check out the final version of the game, revealed in the Epilogue at the end of Chapter 22.

Chapter Quiz

You can find the answers to this chapter quiz in Appendix A, “Chapter Quiz Answers.”

1.

What is the shorthand term for an Allegro datafile?

  1. datafile

  2. datfile

  3. datafile

  4. ADF

2.

What compression algorithm does Allegro use for compressed datafiles?

  1. LZSS

  2. LZH

  3. ZIP

  4. RAR

3.

What is the command-line program that is used to manage Allegro datafiles?

  1. data.exe

  2. datafile.exe

  3. datafile.exe

  4. dat.exe

4.

What is the Allegro datafile object struct called?

  1. DATA_FILE

  2. DATAFILE

  3. DAT_FILE

  4. AL_DATFILE

5.

What function is used to load a datafile into memory?

  1. open_data_file

  2. load_dat

  3. load_datfile

  4. load_datafile

6.

What is the data type format shortcut string for bitmap files?

  1. BITMAP_IMAGE

  2. BITMAP

  3. BMP

  4. DATA_BITMAP

7.

What is the data type constant for wave files, defined by Allegro for use in reading datafiles?

  1. DAT_RIFF_WAV

  2. DAT_WAVE

  3. DAT_SAMPLE

  4. DAT_SOUND

8.

What is the dat option to specify the type of file being added to the datafile?

  1. -t <type>

  2. -a <type>

  3. -d <type>

  4. -s <type>

9.

What is the dat option to specify the color depth of a bitmap file being added to the datafile?

  1. -c <depth>

  2. -d <depth>

  3. -bpp <depth>

  4. -color <depth>

10.

Which function loads an individual object from a datafile?

  1. load_data_object

  2. load_object_file

  3. load_datafile

  4. load_datafile_object

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.186.143