Action Arcade Adventure Set
Diana Gruber

Chapter 10
Birth of a Computer Game


We have the tools, we put in the time, and we have the enthusiasm. Let's make a game!

We have spent enough time talking about development tools, and it's time to start building our game. In the next five chapters we will see how a side-scrolling game is constructed from the bottom up. In this chapter, we'll discuss how the source code is organized, and we'll look at the defining features of the items we'll be manipulating in the game: the tiles, levels, sprites, and objects. Then in Chapter 11, we'll take a closer look at scrolling the tile- based levels. This scrolling technique is based on the scrolling theory we discussed in Chapter 5, but the code has been modified slightly to achieve a higher frame rate. In Chapter 12, we'll take a close look at the sprite and object data structures. These data structures are a bit complicated, but they are important because they speed up the animation and reduce the amount of necessary code. The code that controls the motion of the objects is organized into action functions, and we'll examine several action functions in detail in Chapter 13. Finally, in Chapter 14, we will see how all the components can be put together to create smooth, fast animation. Last but not least, we'll add our special effects in Chapter 15.

Components of a Side-Scroller Game

I've included Table 10.1 to help you sort out the different components that are used in our side-scroller game, Tommy's Adventures.

Table 10.1 Components of a Side-Scroller Game

Description
ItemTypeChapter
Leveldata11Background scenery
Objectdata12Characters inhabiting the game
Spritesdata12Graphical representation of an object
Tilesdata11Building blocks of levels
Action functionscode13Determine motion of the objects
Controlling functionscode14Organize everything
Scrolling functionscode11Determine motion of the levels

If the relationship between sprites and objects is giving you trouble, read on. In previous chapters, we always referred to Tommy as a sprite. Now we are going to begin talking about Tommy as an object. The reason is this: a sprite is defined to be a graphical representation of only one position of an object. That is, a sprite can be one walking frame or one shooting frame of Tommy. Tommy, in fact, consists of 37 different sprites, any of which can be viewed and modified in the sprite editor. But he is still just Tommy, one character in our game. In casual speech, we will still refer to Tommy as a sprite, but for the purposes of code we must define Tommy more precisely. Tommy is an object, and each of his various manifestations: a walking frame, a jumping frame, a shooting frame, and so on, is a sprite. This will become clearer as we examine the data structures in Chapter 12.

Introducing Tommy's Adventures Source Code

The source code for Tommy's Adventures is organized into seven source code files and two header files. If you have installed the software using the INSTALL program on the companion disk and the default directories, you will find these source code files in the \FG\TOMMY\ subdirectory. Each of the files is listed and discussed in the next few chapters. Here is a list of the source code files, in the order in which they are discussed:

Notice that the last file, CHAR.C, was introduced in Chapter 9 when we discussed the game editor. That leaves us six source files and one header file to discuss. We'll present these files as we introduce the key game-programming topics. For instance, when we discuss sprite animation in Chapters 12 and 13, we'll look at the functions in TIMER.ASM, ACTION.C, and MOTION.C.

For the most part, you shouldn't have too much trouble following the code because of the way it is organized. But keep in mind that many of the game components and operations, such as the tiles, sprites, scrolling system, and animation are all tightly integrated. It is difficult to discuss one component of the game without mentioning the other parts of the game, which means this is not a sequential discussion. Please bear with me, and I will try to define the elements of the game as I introduce them, and by the end of Chapter 15 we will have covered everything.

Working with the Source Code

All the C source code has been tested with the Borland C++, Turbo C/C++, Microsoft C/C++ and Microsoft Visual C++ compilers, and should work with other ANSI C/C++ compilers as well. Project: Compiling the Tommy's Adventures Game If you have not yet done so, now would be a good time to recompile Tommy's Adventures. Be sure to keep a clean copy of the source code and the data files in a backup directory so that you can refer to them later if you need to. Use your favorite C compiler and compile the following source code files: TOMMY.C CHAR.C EFFECTS.C MAP.C MOTION.C

This will generate five OBJ files. Link these OBJ files with TIMER.OBJ and the appropriate large model Fastgraph library (FGL.LIB for Fastgraph or FGLL.LIB for Fastgraph/Light). This will give you a new TOMMY.EXE.A note on troubleshooting: Check for batch files in the \FG\TOMMY\ subdirectory with compile commands and switches for the most popular compilers. If you are using the Borland C++ or Turbo C/C++ compilers and you want to compile in the IDE, you will need to make a project file. In general, all you need to do is open a project file, add the five source code files plus TIMER.ASM, and the FGL.LIB or FGLL.LIB. If you have any difficulty with this (many people do!), consult your compiler manuals or call Borland. Do not compile and link the ACTION.C source code file. It is included in the TOMMY.C source code file. I will explain why in Chapter 13.

If you are using Fastgraph/Light, you will need to run the FGDRIVER.EXE program before running TOMMY.EXE. Do not try to use FGDRIVER.EXE in a Windows DOS box. Exit Windows before running programs linked with Fastgraph/Light.

Introducing GAMEDEFS.H

The best place to begin with our game source code is the GAMEDEFS.H definition file that is used by all of the source code files. This file contains the definitions for all the constants, data structures, and function prototypes used in the game. In particular, you'll find the sections shown in Table 10.2 in this file.

Table 10.2 Sections in GAMEDEFS.H

SectionDescription
File I/O VariablesFilename strings and file handles for all the files we will open, including level data, sprite data, and so on
Map DeclarationsGlobal variable declarations and data structure definitions for coordinate systems, scrolling, and levels
Sprite DeclarationsGlobal variable declarations and data structure definitions for sprites
Object DeclarationsGlobal variable declarations and data structure definitions for objects
Special EffectsDeclarations for arrays and variables used in special effects
Key DeclarationsDefinitions of the keys and keyboard scan codes
Miscellaneous Definitions and VariablesDeclarations and definitions of all the other constants, macros, and global variables
Function DeclarationsFunction prototypes for all of the main source files including TIMER.ASM, ACTION.C, CHAR.C, EFFECTS.C, MAP.C, MOTION.C, and TOMMY.C

Let's examine the header file and then we'll discuss some of the more important data structures. Here is the complete GAMEDEFS.H file:

 
/******************************************************************\ 
*  GameDefs.h -- Main header file for Tommy's Adventures game      * 
*  copyright 1994 Diana Gruber                                     * 
*  compile using large model, link with Fastgraph (tm)             * 
\******************************************************************/ 
 
/********************* standard include files *********************/ 
#include <fastgraf.h>                /* Fastgraph function declarations*/ 
#include <conio.h> 
#include <ctype.h> 
#include <string.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <dos.h> 
#include <io.h> 
 
/* Borland C and Turbo C have different names for some of the 
   standard include files */ 
 
#ifdef __TURBOC__ 
  #include <alloc.h> 
  #include <mem.h> 
#else 
  #include <malloc.h> 
  #include <memory.h> 
#endif 
 
#ifdef tommy_c 
    #define DECLARE                  /* declarations are not extern */ 
#else 
    #define DECLARE extern           /* declarations are extern */ 
#endif 
 
/********************* file i/o variables *************************/ 
 
DECLARE int  nlevels;                /* total number of levels */ 
DECLARE int  current_level;          /* current level number */ 
DECLARE char game_fname[13];         /* file name of game file */ 
DECLARE char level_fname[13];        /* file name of level data */ 
DECLARE char background_fname[13];   /* pcx file -- background tiles */ 
DECLARE char backattr_fname[13];     /* background tile attributes */ 
DECLARE char foreground_fname[13];   /* pcx file -- foreground tiles */ 
DECLARE char foreattr_fname[13];     /* foreground tile attributes */ 
 
#define MAXLEVELS 6                  /* max 6 levels per episode */ 
typedef struct levdef                /* level structure */ 
{ 
   char level_fname[13]; 
   char background_fname[13]; 
   char backattr_fname[13]; 
   char foreground_fname[13]; 
   char foreattr_fname[13]; 
   char sprite_fname[13]; 
}  LEVDEF; 
DECLARE LEVDEF far level[MAXLEVELS];  /* array of level structures */ 
 
DECLARE int  nspritelists;            /* total number of sprite lists */ 
DECLARE char sprite_fname[13];        /* sprite file name */ 
DECLARE char list_fname[13];          /* sprite list file name */ 
#define MAXSPRITELISTS 8              /* max 8 sprite lists per level */ 
DECLARE char list_fnames[MAXSPRITELISTS][13]; /* array of sprite lists 
*/ 
 
DECLARE FILE *stream;                 /* general purpose file handle */ 
DECLARE FILE *dstream;                /* used for debugging */ 
DECLARE FILE *level_stream;           /* file handle: level data */ 
DECLARE FILE *sprite_stream;          /* file handle: sprite file */ 
 
/********************  map declarations *************************/ 
 
#define BACKGROUND   0                /* tile type is background */ 
#define FOREGROUND   1                /* tile type is foreground */ 
DECLARE int tile_type;                /* foreground or background */ 
 
DECLARE int tile_orgx;                /* tile space x origin */ 
DECLARE int tile_orgy;                /* tile space y origin */ 
 
DECLARE int screen_orgx;              /* screen space x origin */ 
DECLARE int screen_orgy;              /* screen space y origin */ 
DECLARE int screen_xmax;              /* max screen space x coordinate 
*/ 
DECLARE int screen_ymax;              /* max screen space y coordinate 
*/ 
 
DECLARE int world_x;                  /* world space x origin */ 
DECLARE int world_y;                  /* world space y origin */ 
DECLARE int world_maxx;               /* max world space x coordinate */ 
DECLARE int world_maxy;               /* max world space y coordinate */ 
 
DECLARE int vpo;                      /* visual page offset */ 
DECLARE int vpb;                      /* visual page bottom */ 
DECLARE int hpo;                      /* hidden page offset */ 
DECLARE int hpb;                      /* hidden page bottom */ 
DECLARE int tpo; 
 
#define MAXROWS 200                   /* maximum rows of tiles */ 
#define MAXCOLS 240                   /* maximum columns of tiles */ 
DECLARE int nrows;                    /* number of rows */ 
DECLARE int ncols;                    /* number of columns */ 
 
/* tile arrays for levels */ 
DECLARE unsigned char far background_tile[MAXCOLS][MAXROWS]; 
DECLARE unsigned char far foreground_tile[MAXCOLS][MAXROWS]; 
 
/* tile attribute arrays */ 
DECLARE unsigned char background_attributes[240]; 
DECLARE unsigned char foreground_attributes[28]; 
 
DECLARE char layout[2][22][15];       /* layout array */ 
 
DECLARE int warped;                   /* flag: warped this frame? */ 
DECLARE int scrolled_left;            /* flag: scrolled left? */ 
DECLARE int scrolled_right;           /* flag: scrolled right? */ 
DECLARE int scrolled_up;              /* flag: scrolled up? */ 
DECLARE int scrolled_down;            /* flag: scrolled down? */ 
 
/********************  sprite declarations *************************/ 
 
typedef struct _sprite                /* sprite structure */ 
{ 
   char far *bitmap;                  /* pointer to bitmap data */ 
   int width;                         /* width of bitmap */ 
   int height;                        /* height of bitmap */ 
   int xoffset;                       /* x offset */ 
   int yoffset;                       /* y offset */ 
   int bound_x;                       /* x coord of bounding box */ 
   int bound_y;                       /* y coord of bounding box */ 
   int bound_width;                   /* width of bounding box */ 
   int bound_height;                  /* height of bounding box */ 
}  far SPRITE; 
 
#define MAXSPRITES 100                /* maximum number of sprites */ 
DECLARE SPRITE *sprite[MAXSPRITES];   /* sprite array */ 
DECLARE int nsprites;                 /* number of sprites */ 
 
#define STANDFRAMES  3                /* number of frames in sprite list 
*/ 
#define RUNFRAMES    6 
#define JUMPFRAMES   4 
#define KICKFRAMES   8 
#define SHOOTFRAMES  7 
#define SCOREFRAMES  3 
#define ENEMYFRAMES  6 
 
DECLARE SPRITE *tom_stand[STANDFRAMES]; /* sprite lists */ 
DECLARE SPRITE *tom_run  [RUNFRAMES]; 
DECLARE SPRITE *tom_jump [JUMPFRAMES]; 
DECLARE SPRITE *tom_kick [KICKFRAMES]; 
DECLARE SPRITE *tom_shoot[SHOOTFRAMES]; 
DECLARE SPRITE *tom_score[SCOREFRAMES]; 
DECLARE SPRITE *enemy_sprite[ENEMYFRAMES]; 
 
#define LEFT   0                      /* direction of sprite */ 
#define RIGHT  1 
 
/************************** object declarations *******************/ 
DECLARE struct OBJstruct;             /* forward declarations */ 
typedef struct OBJstruct OBJ, far *OBJp; 
 
typedef void near ACTION (OBJp objp); /* pointer to action function */ 
typedef ACTION near *ACTIONp; 
 
typedef struct OBJstruct              /* object structure */ 
{ 
  OBJp next;                          /* linked list next node */ 
  OBJp prev;                          /* linked list previous node */ 
  int x;                              /* x coordinate */ 
  int y;                              /* y coordinate */ 
  int xspeed;                         /* horizontal speed */ 
  int yspeed;                         /* vertical speed */ 
  int direction;                      /* LEFT or RIGHT */ 
  int tile_xmin;                      /* tile limits */ 
  int tile_xmax; 
  int tile_ymin; 
  int tile_ymax; 
  int frame;                          /* frame of animation */ 
  unsigned long time;                 /* time */ 
  SPRITE *sprite;                     /* pointer to sprite */ 
  ACTIONp action;                     /* pointer to action function */ 
}; 
 
DECLARE OBJp player;                  /* main player object */ 
DECLARE OBJp top_node, bottom_node;   /* nodes in linked list */ 
DECLARE OBJp score;                   /* score object */ 
 
#define MAXENEMIES 5 
DECLARE OBJp enemy[MAXENEMIES];       /* array of enemy objects */ 
DECLARE int nenemies;                 /* how many enemies */ 
 
/*********************  special effects **************************/ 
DECLARE char far *slide_array; 
DECLARE int slide_arraysize;          /* size of slide array */ 
 
DECLARE int player_blink;             /* flag: is Tommy blinking? */ 
DECLARE int nblinks;                  /* how many times has he blinked? 
*/ 
DECLARE unsigned long blink_time;     /* how long since the last blink? 
*/ 
DECLARE char far blink_map[4000];     /* bitmap mask for the blink */ 
 
/*********************  key declarations *************************/ 
#define BS           8             /* bios key values */ 
#define ENTER       13 
#define ESC         27 
#define SPACE       32 
 
#define KB_ALT      56             /* low-level keyboard scan codes */ 
#define KB_CTRL     29 
#define KB_ESC       1 
#define KB_SPACE    57 
#define KB_UP       72 
#define KB_LEFT     75 
#define KB_RIGHT    77 
#define KB_DOWN     80 
#define KB_F1       59 
#define KB_F2       60 
#define KB_W        17 
#define KB_D        32 
 
/************ miscellaneous defines and variables ***********/ 
#define MAX(x,y) ((x) > (y)) ? (x) : (y) 
#define MIN(x,y) ((x) < (y)) ? (x) : (y) 
#define OFF   0 
#define ON    1 
#define ERR  -1 
#define OK    1 
#define FALSE 0 
#define TRUE  1 
 
DECLARE int hidden;                   /* hidden page */ 
DECLARE int visual;                   /* visual page */ 
DECLARE int seed;                     /* random number generator seed */ 
 
DECLARE int white;                    /* colors for status screen */ 
DECLARE int black; 
DECLARE int blue; 
 
DECLARE unsigned long game_time;      /* total clock ticks */ 
DECLARE unsigned long last_time;      /* time last frame */ 
DECLARE unsigned long delta_time;     /* time elapsed between frames */ 
DECLARE unsigned long max_time;       /* how long Tommy stands still */ 
 
 
DECLARE int nbullets;                 /* how many bullets */ 
DECLARE unsigned long shoot_time;     /* how long between shots */ 
DECLARE long player_score;            /* how many points */ 
 
DECLARE int show_score;               /* flag: scoreboard on? */ 
 
DECLARE int forward_thrust;           /* horizontal acceleration */ 
DECLARE int vertical_thrust;          /* vertical acceleration */ 
DECLARE int kicking;                  /* flag: kicking? */ 
DECLARE int kick_frame;               /* stage of kick animation */ 
DECLARE int kick_basey;               /* y coord at start of kick */ 
DECLARE int nkicks;                   /* how many kicks */ 
DECLARE int nshots;                   /* how many shots */ 
DECLARE int nhits;                    /* how many hits */ 
DECLARE int nlives;                   /* how many lives */ 
 
DECLARE int warp_to_next_level;       /* flag: warp? */ 
DECLARE char abort_string[50];        /* display string on exit */ 
 
/*****************  function declarations *******************/ 
 
void set_rate(int rate);              /* external timer function */ 
 
typedef void far interrupt HANDLER (void); 
typedef HANDLER far *HANDLERp; 
DECLARE HANDLERp oldhandler; 
 
/* action function declarations: action.c */ 
void  near bullet_go(OBJp objp); 
void  near enemy_hopper_go(OBJp objp); 
void  near enemy_scorpion_go(OBJp objp); 
void  near floating_points_go(OBJp objp); 
void  near kill_bullet(OBJp objp); 
void  near kill_enemy(OBJp objp); 
void  near kill_object(OBJp objp); 
void  near launch_bullet(void); 
void  near launch_enemy(int x,int y,int type); 
void  near launch_floating_points(OBJp objp); 
void  near player_begin_fall(OBJp objp); 
void  near player_begin_jump(OBJp objp); 
void  near player_begin_kick(OBJp objp); 
void  near player_begin_shoot(OBJp objp); 
void  near player_fall(OBJp objp); 
void  near player_jump(OBJp objp); 
void  near player_kick(OBJp objp); 
void  near player_run(OBJp objp); 
void  near player_shoot(OBJp objp); 
void  near player_stand(OBJp objp); 
void  near put_score(OBJp objp); 
void  near update_score(OBJp objp); 
 
/* function declarations: char.c */ 
void  put_string(char *string,int ix,int iy); 
void  center_string(char *string,int x1,int x2,int y); 
 
/* function declarations: effects.c */ 
void  get_blinkmap(OBJp objp); 
void  load_status_screen(void); 
void  redraw_screen(void); 
int   status_screen(void); 
void  status_shape(int shape,int x,int y); 
 
/* function declarations: map.c */ 
void  load_level(void); 
void  page_copy(int ymin); 
void  page_fix(void); 
void  put_foreground_tile(int xtile,int ytile); 
void  put_tile(int xtile,int ytile); 
void  rebuild_background(void); 
void  rebuild_foreground(void); 
int   scroll_down(int npixels); 
int   scroll_left(int npixels); 
int   scroll_right(int npixels); 
int   scroll_up(int npixels); 
void  swap(void); 
void  warp(int x,int y); 
 
/* function declarations: motion.c */ 
int   can_move_down(OBJp objp); 
int   can_move_up(OBJp objp); 
int   can_move_right(OBJp objp); 
int   can_move_left(OBJp objp); 
int   collision_detection(OBJp objp1,OBJp objp2); 
int   how_far_left(OBJp objp,int n); 
int   how_far_right(OBJp objp,int n); 
int   how_far_up(OBJp objp,int n); 
int   how_far_down(OBJp objp,int n); 
int   test_bit(char num,int bit); 
 
/* function declarations: tommy.c */ 
void  main(void); 
void  activate_level(void); 
void  apply_sprite(OBJp objp); 
void  array_to_level(int  n); 
void  fix_palettes(void); 
void  flushkey(void); 
void  getseed(void); 
void  get_blinkmap(OBJp objp); 
void  interrupt increment_timer(void); 
int   irandom(int  min,int max); 
void  init_graphics(void); 
void  level_to_array(int n); 
void  load_sprite(void); 
void  load_status_screen(void); 
void  terminate_game(void); 

Notes on GAMEDEFS.H

The GAMEDEFS.H file is included in all the source code files. This could present a problem. Global variables should be declared one time in one source code file, and then seen elsewhere as "extern" variables. This ensures all the functions in all the source code files are looking at the same memory location for a variable. In order to solve this problem, we define a symbol DECLARE, as follows:

#ifdef tommy_c 
    #define DECLARE                  /* declarations are not extern */ 
#else 
    #define DECLARE extern           /* declarations are extern */ 
#endif 
This means DECLARE will be defined to mean nothing in the TOMMY.C source code file, and elsewhere it will be defined to mean "extern." This solves the problem quite nicely. A global variable can now be declared like this:

 
DECLARE int current_level; 

The declaration will be extern in all source code files except TOMMY.C. To facilitate the definition of DECLARE, we define tommy_c at the top of TOMMY.C like this:

 
#define tommy_c 
#include "gamedefs.h" 
Now the tommy_c symbol will only be defined in the TOMMY.C source code file, and elsewhere it will be invisible to the compiler.

Exploring the Data Structures

If you look closely at GAMEDEFS.H, you'll see that our game is designed using four data structures to support a layout array, levels, sprites, and objects. You'll need a good understanding of how these structures work in order to follow the game code we'll present in Chapters 11 through 15. When we cover the source code, you'll see how these data structures are used.

Supporting Levels

The game code is designed to support six levels. We define a constant named MAXLEVELS in GAMEDEFS.H to specify the number of levels that can be used:

 
#define MAXLEVELS 6 
If you want to add more levels to the game, you'll need to change this constant.

If you recall from Chapter 9 when we completed the game editor, we explained that the level data is stored in an array named level, which is declared like this:

 
DECLARE LEVDEF far level[MAXLEVELS]; 
In the game, we use this same data structure. Recall that it is simply an array of LEVDEF structures. This structure simply holds the names of each of the six data files that are used to create the game:

 
typedef struct levdef                /* level structure */ 
{ 
   char level_fname[13]; 
   char background_fname[13]; 
   char backattr_fname[13]; 
   char foreground_fname[13]; 
   char foreattr_fname[13]; 
   char sprite_fname[13]; 
}  LEVDEF; 
Here we have compartments for the filenames of the level data, background tiles, background tile attributes, foreground tiles, foreground tile attributes, and the sprite list. The names of the these files are read by the main() function in TOMMY.C and then they are assigned to the level array by calling the level_to_array() function, which is also located in TOMMY.C:

 
void level_to_array(int n) 
{ 
   /* update all the levels */ 
   strcpy(level[n].level_fname,     level_fname); 
   strcpy(level[n].background_fname,background_fname); 
   strcpy(level[n].backattr_fname,  backattr_fname); 
   strcpy(level[n].foreground_fname,foreground_fname); 
   strcpy(level[n].foreattr_fname,  foreattr_fname); 
   strcpy(level[n].sprite_fname,    sprite_fname); 
} 
Once this data has been read in, it can easily be accessed by the main game functions.

Introducing the Layout Array

When we get to Chapter 12, we'll be spending quite a bit of time discussing game animation. In particular, we'll look at how our animated sprites interact with the tiles in our game levels. This type of animation can get a little tricky so we've devoted a few chapters to showing you the subtleties of fast sprite animation. For now, let's explore the data structures that are used. First, we'll need two arrays to hold pointers to our background and foreground tiles:

 
/* tile arrays for levels */ 
DECLARE unsigned char far background_tile[MAXCOLS][MAXROWS]; 
DECLARE unsigned char far foreground_tile[MAXCOLS][MAXROWS]; 

Because MAXCOLS is set to 240 and MAXROWS is set to 200, these arrays can reference as many as 48,000 tiles for our background and foreground art. Second, we need an important structure we call the layout array:

 
/* declare the layout array */ 
DECLARE char layout[2][22][15]; 
The layout array holds the information about the status of the tiles displayed on the current screen. We need it to help us keep track of when tiles need to be redrawn on the screen when sprites are being animated. Notice that the layout array has three dimensions. The first subscript, [2], refers to the two pages: hidden and visual. Tiles are tracked on both the hidden and visual pages. The second subscript, [22], refers to the number of columns. The third subscript, [15], is the number of rows. It's easy to visualize the layout array as an array of Boolean values superimposed on the tiles, as shown in Figure 10.1.

Figure 10.1 How the layout array is set up.

If the array element is assigned a value of 0, the corresponding tile has not changed in the current animation frame (see the next Tommy's Tip) and we'll call this a clean tile. If the array element is 1, the tile has been overwritten with something, probably a sprite, and it needs to be redrawn. When there are no sprites visible and all the tiles are clean, the layout array contains all 0s, as shown in Figure 10.1. As sprites are added, they cover up tiles, and the corresponding elements of the layout array are set to 1, or TRUE.

The process of rebuilding all the tiles can be the most time-consuming part of the frame. Since we want to maximize our frame rate, we can take a shortcut on this step. Instead of replacing all the tiles every frame, we can update the bare minimum number of tiles that must be redrawn to clear the screen. If we scrolled during the frame, we will need to update a row or column of tiles along the edges. The only other tiles we'll need to replace are those that were covered by sprites. In order to differentiate these tiles from the "clean" tiles, we need a mechanism to keep track of the tile status. And that's where the layout array comes in.

Tile Attributes

Tile attributes are byte values assigned to individual tiles that contain information about how a sprite may interact with the tile. The most important tile attribute is solid on top (the sprite "walks" on the tiles that are solid on top). Paths and platforms are made up of tiles with the solid-on-top attribute. Not all tiles are solid on top, of course, or Tommy would walk on walls and in the sky. We want Tommy to keep his feet on the floor, and if he happens to venture out into empty space, we want the rules of physics to apply. Figure 10.2 shows how tile attributes are used to keep Tommy's feet on the ground.

Figure 10.2 Tile attributes keep Tommy from falling through the floor.

Similarly, we have attributes for solid on the bottom so that Tommy will bump his head on ceilings and ledges if he's not careful, and solid on the left or right, so he won't walk through walls.

Each tile has eight attributes; besides the four attributes for solid on the top, bottom, left, and right, there are four more attributes that you can use for anything you want. You may want to use attributes to flag a tile as the end of a level, as a door, as a remappable tile (as in the case of a flickering torch, where tiles are replaced periodically), as a starting point for a sprite, or as a hazard, such as spikes. Additionally, passing over a tile may change the action of the sprite. If a tile is a patch of ice, the sprite will slide over it, or a tile may accelerate sprite movement, such as a fan or catapult. There are obviously many ways to use a tile attribute.

Each tile is assigned a tile attribute byte. The eight bits in the byte indicate which attributes are set. Table 10.3 shows how I have assigned the bits.

Table 10.3 Tile Attributes

BitAttribute
0Solid on top
1Solid on bottom
2Solid on left
3Solid on right
4unassigned
5unassigned
6unassigned
7unassigned

The tile attributes are assigned to the unique tiles in the tile library. That means that if a tile is a floor, it will be a floor throughout the level. If a tile is solid on the top in one position in the level, every occurrence of that tile in the level will also be solid on the top. Assigning tile attributes to tiles in the tile library is more efficient than assigning attributes to every tile in the level individually. That method would also work, but you would need an array as big as the level to hold the attributes and you would use up much more RAM. I don't do it that way, but I can see how it would be possible for some games to work better with attributes assigned to level positions rather than unique tiles. Feel free to experiment, but for our discussion, we'll assume the attributes are assigned to unique tiles in the tile library.

The attribute bytes are stored in two arrays, which are declared like this in GAMEDEFS.H:

 
   char background_attributes[240]; 
   char foreground_attributes[28]; 
Each of the 240 unique background tiles has an attribute byte associated with it, as does each of the 28 unique foreground tiles. The attributes are usually set in the game editor. To check an attribute by testing a bit, either in the game or in the editor, the following function will do the job:

 
test_bit(char num,int bit) 
{ 
   /* test bit flags, used for tile attributes */ 
   return((num >> bit) & 1); 
} 
This function is found in the file MOTION.C.

Objects and Sprites

Is Tommy a sprite or is Tommy an object? As discussed earlier, Tommy is both, but for the purposes of the code we are going to discuss, Tommy must be defined very precisely. Therefore, we will define a structure of type object in GAMEDEFS.H that will completely describe Tommy. The structure looks like this:

typedef struct OBJstruct              /* object structure */ 
{ 
  OBJp next;                          /* linked list next node */ 
  OBJp prev;                          /* linked list previous node */ 
  int x;                              /* x coordinate */ 
  int y;                              /* y coordinate */ 
  int xspeed;                         /* horizontal speed */ 
  int yspeed;                         /* vertical speed */ 
  int direction;                      /* LEFT or RIGHT */ 
  int tile_xmin;                      /* tile limits */ 
  int tile_xmax; 
  int tile_ymin; 
  int tile_ymax; 
  int frame;                          /* frame of animation */ 
  unsigned long time;                 /* time */ 
  SPRITE *sprite;                     /* pointer to sprite */ 
  ACTIONp action;                     /* pointer to action function */ 
};

This structure gives all the information about Tommy: his current position, what he is doing, how long he has been doing it, and what sprite he is currently displayed as. That's right, Tommy's object keeps track of Tommy's sprite. There are many sprites that could be the current representation of Tommy. He may be standing still, walking, or running. The chosen sprite is one of the images we created in the sprite editor, and it will be stored in a structure that looks like this:

 
typedef struct _sprite                /* sprite structure */ 
{ 
   char far *bitmap;         /* pointer to bitmap data */ 
   int width;                         /* width of bitmap */ 
   int height;                        /* height of bitmap */ 
   int xoffset;                       /* x offset */ 
   int yoffset;                       /* y offset */ 
   int bound_x;                       /* x coord of bounding box */ 
   int bound_y;                       /* y coord of bounding box */ 
   int bound_width;                   /* width of bounding box */ 
   int bound_height;                  /* height of bounding box */ 
}  far SPRITE; 

So Tommy's object structure points to Tommy's sprite structure. While there is only one object structure for Tommy, there are 37 sprite structures, and Tommy's object can point to any one of them. We will examine this relationship in more detail in Chapter 12.

Object or Tile?

There will be times, as we build our game, that we'll have an item that we don't know what to do with. There are some items that can be represented as either a tile or an object, and it is not always obvious which is the best way to define it. Take the case of a cheeseburger, for example. Suppose Tommy is running around the level and his energy level goes down. He is hungry. Let's give him a cheeseburger. How are we going to do it?

The cheeseburger can be displayed as either a tile or a sprite. If it is stored as a tile, it should be as a foreground tile, so it can be put anywhere in the level, and the background will show through it. How will we know Tommy has grabbed the cheeseburger? We can use a tile attribute to mark the tile as food. Then every time Tommy passes a foreground tile, we can check it to see if the food attribute is set. If it is, we remove the foreground tile from the foreground_tile array and give Tommy the energy boost he has earned.

On the other hand, if the cheeseburger is stored as an object it would have no tile attribute. We would have to use collision detection techniques to determine how the two objects should interact with each other. If a collision is detected, we would remove the cheeseburger object from the linked list and give Tommy his snack.

Both methods would work, and the one you choose is related to space and speed considerations. Since we only have enough room in video memory for 28 foreground tiles, we may want to use them sparingly. On the other hand, sprite and objects take up room in RAM, and if we are facing a RAM crunch, tiles, which are stored in video memory, may be the better option. Video-to-video transparent blits are a little slower than RAM-to-video transparent blits, so a "cheeseburger sprite" would be a little faster than a "cheeseburger tile." Except that objects are drawn every frame, and foreground tiles are not. We only redraw tiles when a object passes over them (or behind them). Also, if there are many cheeseburgers on a level, we will have to do a collision check on each one every frame. That would slow us down, but just a little bit.

Are there other factors we have not considered? We only have four unassigned tile attributes, what if we want to use them for something else? What if we want the cheeseburger to use the background palettes rather than the sprite palettes? Do we want the cheeseburger to blink or display a floating score when it is grabbed?

As you can see, the decision on how to store the cheeseburger is complex. The optimal solution is not always obvious during the early design phase of a game. Sometimes a little experimentation is needed. It is a good idea to keep an open mind about things like this. Different games will yield different results.

Function Declarations

At the bottom of GAMEDEFS.H are the function declarations for all the source code files. Function declarations are important to get clean compiles without compiler warnings. The function declarations are organized in the same order as the source code files and are presented in roughly alphabetical order. Notice that ACTION.C is treated as a separate source code file even though it is not compiled alone. Rather, it is included in TOMMY.C using the #include preprocessor directive. The reason for includeing the file in this manner is all the action functions in ACTION.C are declared to be near, and must reside in the same code segment as the functions that call them. We'll discuss this concept further in Chapter 13.

Getting Organized

Some of the global variables in GAMEDEFS.H are visible in only one source code file, others are visible in several source code files. I have not differentiated between them-- all globals variables are universally visible in the Tommy's Adventures source code files. I realize this runs contrary to the current programming style of data encapsulation. My only defense of this practice is to say: this is the way I like to do it; it works well for me. Development is speeded up because I always know where my globals are. Having them in one file makes it easy for me to find them. I can modify them or add new globals quickly. I don't waste a lot of time worrying about which variables are visible to which functions, and I don't waste RAM on duplicate copies of variables. It seems like a perfectly efficient way to organize things to me.

You may, of course, feel free to encapsulate your own data with my blessing.

Next Chapter

_______________________________________________

Cover | Contents | Downloads
Awards | Acknowledgements | Introduction
Chapter 1 | Chapter 2 | Chapter 3 | Chapter 4 | Chapter 5 | Chapter 6
Chapter 7 | Chapter 8 | Chapter 9 | Chapter 10 | Chapter 11 | Chapter 12
Chapter 13 | Chapter 14 | Chapter 15 | Chapter 16 | Chapter 17 | Chapter 18
Appendix | License Agreement | Glossary | Installation Notes | Home Page

Fastgraph Home Page | books | Magazine Reprints
So you want to be a Computer Game Developer

Copyright © 1998 Ted Gruber Software Inc. All Rights Reserved.