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;
   }
}

----

Diana Gruber is senior programmer at Ted Gruber Software, publishers
of the Fastgraph programmers graphics library. Diana is the author
of the book Action Arcade Adventure Set (Coriolis Group). You can
reach Diana at Fastgraph@AOL.COM.
