This article was published as:

"Attached Sprites",
Dr. Dobbs Sourcebook, Volume 20, Issue 14, (Games Sourcebook)
May/June 1995, page 29

What appears here is the original manuscript, as submitted to Jon Erickson. Any changes in the published version are due to editing by the staff at Dr. Dobbs Journal.

Attached Sprites
copyright 1995 Diana Gruber

Game programmers are always looking for efficient ways to accomplish sprite animation. As the science of game programming evolves, certain techniques have become standard. The use of attached sprites is a standard game programming technique that has proven useful in many games.

Consider the case of a mid-air battle between airplanes, as in the Quickfire demo. A player-controlled airplane confronts one or more enemy airplanes, shoots bullets at them, and when the enemy airplane is hit, it explodes and dies. To achieve a pleasing effect, the airplane does not vanish immediately, but rather it catches fire, and then gradually becomes engulfed in flames, eventually disolving into a puff of smoke.

Data structures are used to keep track of the airplanes as they move accross the scrolling background. Each data structure holds the airplane's current x and y position, its speed, a pointer to the function that controls its action (called the action function), and a pointer to a bitmap which describes the current image of the airplane.

An interesting thing happens when an airplane explodes. To achieve the explosion effect, a single sprite must become two sprites. One sprite is the airplane, which remains unchanged. The other sprite is the explosion, which starts as a small ball of fire in the nose of the airplane, then grows over the course of several frames to a large smoky fireball which eventually covers the entire airplane. Finally, the airplane disappears as the fireball covers it, and all that is left is the fireball, which dissipates and eventually disappears.

The motion of the fireball is dependent on the motion of the airplane. Since the motion of the airplane has random elements, its motion cannot easily be predicted. However, it is important that the fireball sprite know where the airplane sprite is. If two airplanes are exploding at the same time, it is also necessary to match the explosion with the proper airplane. Therefore, we need a mechanism to pass information from the airplane to the explosion.

Similarly, the airplane needs to get information from the explosion. In particular, the airplane needs to know how big the fireball is, so it will know when it is time to die.

The easiest way to pass information between the airplane and the explosion is to use an attached sprite. The data structures of both sprites attach themselves to each other by simply using a structure member to point to each other. The action of one sprite is then conveniently influenced by the status of the other sprite.

All the objects, including the airplanes, the explosions, and the bullets, are stored in a linked list. Because of the nature of the game, nodes are constantly being added to and removed from the list. This happens when bullets go off the edge of the screen, enemies are killed, and so on. An object and its attached sprite may exist anywhere in the list, as shown in Figure 1.

  player-bullet-enemy-bullet-bullet-explosion-bullet-enemy-explosion
     ^     |      |     |     |         ^        |     ^         ^
     |     v      v     v     v         |        v     |         |
     |    NULL   NULL  NULL  NULL       |       NULL   |         |
     |                                  |              |         |
     ------------------------------------              -----------
          attached sprite pointer

Figure 1. Objects in the list may point to each other or to nothing.

Notice that only the enemy that is currently exploding has an attached sprite. Also notice it is possible for the player to have an attached sprite. When the player is hit, it shows a small fireball even though it does not die from the strike. When there is no explosion, the attached sprite is set to NULL.

A structure, called OBJstruct, holds the information about each sprite object, including the airplanes, the bullets, and the explosions. The structure looks like this:

typedef struct OBJstruct {
  OBJp  next;
  OBJp  prev;
  int x;
  int y;
  int xspeed;
  int max_xspeed;
  int yspeed;
  int direction;
  int frame;
  int tile_xmin;
  int tile_xmax;
  int tile_ymin;
  int tile_ymax;

  SPRITE *image;
  ACTIONp action;
  OBJp attached_sprite;
};

The first two members are the pointers to adjacent nodes in the linked list. The next two members, x and y, specify the current position of the sprite. These values change each frame according to the speed and direction of the the sprite, which are described in the next five members. The frame member describes something about the animation, for example whether the plane is upright or turning. The next four elements specify the tile extents and are used to determine when an object has moved off the edge of the screen.

The image member is a pointer to the object's bitmap data, which is the actual physical representation of the sprite. This is stored in the SPRITE structure, as follows:

typedef struct _sprite
{
   char *bitmap;
   int width;
   int height;
   int xoffset;
   int yoffset;

}  SPRITE;

This structure holds all the information necessary to display the sprite, including its width and height, and the offset values. The offsets are used to adjust the position of the sprite, and are especially useful with explosions, which need to be centered around their midpoint, rather than displayed from a corner..

The action member of the object structure is a pointer to a function, such as the do_explosion() function listed in the code fragment. This function is called an action function, and is executed once each frame. The action function determines the current state of the object, such as going, falling, or dying.

The final member of the object structure is the one that interests us:

OBJp attached_sprite;

This is a pointer to another object structure, or in other words, the pointer to the attached sprite. The pointer is always a bidirectional pointer, meaning the object points back to whichever object points to it.

The code that controls the creation of the explosion is shown in the function start_explosion(). As you can see, this function spawns an object and adds it to the linked list in the traditional way. It also forms the attachment between the the airplane sprite (objp) and the explosion sprite (node).

  /* set up the links between the explosion and the enemy plane */
  node->attached_sprite = objp;
  objp->attached_sprite = node;

The relationship is illustrated in Figure 2.

     ------------                     -------------
     |          |                     |            |
     |          |  <----------        |            |
     |  objp    |           |   ----->|   node     |
     |          |           |   |     |            |
     |          |           |   |     |            |
     ------------           |   |     -------------
     | attached |           ----|-----| attached  |
     | sprite   |----------------     | sprite    |
     ------------                     -------------
       airplane                         explosion

Figure 2. The airplane and the explosion point to each other.

The motion of the explosion is controlled by the function do_explosion(). The first thing this function does is examine the current state of the explosion. If the state of the animation has reached the third frame, that means the explosion is big enough to cover the airplane. At that point, it is time to kill the airplane. The airplane is killed by setting its action function to kill_enemy(), like this:

  if (objp->attached_sprite != (OBJp)NULL)
    objp->attached_sprite->action = &kill_enemy;

After the enemy airplane has been killed, the attached sprite is set to NULL, indicating there is no longer a airplane attached to the explosion. The explosion may now move independently for the next few frames, until it also is killed.

During those three frames when both the airplane and the explosion are visible, the x and y coordinates of the explosion are determined by the x and y coordinates of the airplane, as follows:

  /* The position of the explosion depends on the position of
   the airplane */
  if (objp->attached_sprite != (OBJp)NULL)
  {
    objp->x = objp->attached_sprite->x+16;
    objp->y = objp->attached_sprite->y-4;
  }

These coordinates include a 16-pixel horizontal adjustment and a four-pixel vertical adjustment to center the explosion over the nose of the airplane.

Attached sprites have many applications. When a character needs to hold an object, such as a gun, the use of attached sprites can greatly simplify the code. It can also save room, which is always at a premium when designing games. If you have a sprite with 30 positions (running, jumping, falling, standing) and you add a gun to each of those positions, you will need to generate 30 more sprites. If the sprites have an average width of 30 pixels and height of 40 pixels, this will use 36,000 bytes of sprite space. If you can reuse the non-shooting sprites by simply adding a attached gun arm to each one, the savings in RAM and disk space will be significant. For more information about sprite animation, see chapters 12 and 13 in the book Action Arcade Adventure Set.

/******************** sprite declarations *************************/
int nsprites;
typedef struct _sprite
{
   char *bitmap;
   int width;
   int height;
   int xoffset;
   int yoffset;

} SPRITE;
SPRITE *sprite[40];

/* forward declarations */
struct OBJstruct;
typedef struct OBJstruct OBJ, near *OBJp;

/* pointer to object action function */
typedef void near ACTION (OBJp objp);
typedef ACTION *ACTIONp;

/* data structure for objects */
typedef struct OBJstruct
{
  OBJp next;
  OBJp prev;
  int x;
  int y;
  int xspeed;
  int max_xspeed;
  int yspeed;
  int direction;
  int frame;
  int tile_xmin;
  int tile_xmax;
  int tile_ymin;
  int tile_ymax;

  SPRITE *image;
  ACTIONp action;
  OBJp attached_sprite;
};
SPRITE *explosion[11];

/**********************************************************************/
void near start_explosion(OBJp objp)
{
   OBJp node;

   /* allocate space for the object */
   node = (OBJp)malloc(sizeof(OBJ));
   if (node == (OBJp)NULL) return;

   /* assign values to the structure members */

   /* after the plane has been killed, the explosion moves at a slower
      speed because smoke drifts slower than metal */
   node->xspeed = objp->xspeed/2;
   node->yspeed = objp->yspeed/2;

   /* tile extents */
   node->tile_xmin = 2;
   node->tile_xmax = 21;
   node->tile_ymin = 0;
   node->tile_ymax = 14;

   /* the sprite will be the first frame explosion bitmap */
   node->image = explosion[0];
   node->x = objp->x+16;
   node->y = objp->y-4;
   node->frame = -1;

   /* insert at the top of the linked list */
   node->prev = top_node;
   node->prev->next = node;
   top_node = node;
   node->next = (OBJp)NULL;

   /* set up the links between the explosion and the enemy plane */
   node->attached_sprite = objp;
   objp->attached_sprite = node;

   /* assign the action function */
   node->action = do_explosion;
}
/**********************************************************************/
void near do_explosion(OBJp objp)
{
   /* If the explosion has reached the frame 3 state, at which point
      the bitmap is bigger than the airplane, it is time to kill the
      airplane. */

   if (objp->frame > 3)
   {
      /* if the attached sprite is NULL that means the airplane was
       already killed */
      if (objp->attached_sprite != (OBJp)NULL)
       objp->attached_sprite->action = &kill_enemy;

      objp->x += objp->xspeed;
      objp->y += objp->yspeed;
   }
   else
   {
      /* The position of the explosion depends on the position of
       the airplane */
      if (objp->attached_sprite != (OBJp)NULL)
      {
       objp->x = objp->attached_sprite->x+16;
       objp->y = objp->attached_sprite->y-4;
      }

      /* it is possible for the explosion to be at less than frame 3
       but there is no attached sprite. That happens when the enemy
       plane has drifted off the edge of the screen. */
      else
      {
       objp->x += objp->xspeed;
       objp->y += objp->yspeed;
      }
   }

   /* Increment the explosion frame */
      objp->frame++;

   /* define which sprite will be displayed this frame */
   objp->image = explosion[objp->frame];

   /* We have 10 frames for the explosion */
   if (objp->frame > 10)
   {
      objp->image = explosion[10];
      objp->action = kill_explosion;
   }
}

_______________________________________________

Product Catalog | Price List | Windows FAQ | Windows CE FAQ | DOS FAQ
Demos | Patches | Games | More Games | Diana's Reprints
Quotes | Links | Contact Information | Orders
_______________________________________________

Copyright © 2002-2007 Ted Gruber Software, Inc. All Rights Reserved.