💾 Archived View for spam.works › mirrors › textfiles › programming › spranim.pro captured on 2023-07-22 at 20:46:02.

View Raw

More Information

⬅️ Previous capture (2023-06-16)

-=-=-=-=-=-=-

From umcaner0@cc.umanitoba.ca Wed Sep 14 20:16:09 1994
From: umcaner0@cc.umanitoba.ca (Richard Theodore Caners)
Newsgroups: rec.games.programmer
Subject: A method for sprite animation (long)
Date: 13 Sep 1994 23:01:51 GMT
Organization: The University of Manitoba
NNTP-Posting-Host: mira.cc.umanitoba.ca


Hi, what follows is a text file I wrote describing a method of
sprite animation I am using in a game.  I hope people find it
informative.

A SIMPLE METHOD FOR SPRITE ANIMATION

By Darren Gyles

  Author of HOOP 
  ( Avaliable at  ftp.funet.fi /pub/msdos/games/cards/hoop11.zip )

Note : The following implementation of sprite animation uses linked lists
fairly extensively.  If you are not familiar with this concept, get any
first year computer science textbook and look into it.

I have recently been programming a simple Galaxian type shoot-em-up and
needed a method for sprite animation.  This is the solution that I came up
with.  I was fairly pleased with the results.  I'm sure that this method is 
not unique, but I haven't seen anybody mention it before so I will take
a stab at it.

I am assuming that you already have your sprite graphics routines written.
I will not cover machine specific graphics routines, just algorithms for 
implementing sprites. 

This document describes a simple sprite system and then expands this
system to include animation.  Be forewarned that even though there is
quite a bit of code given, you will never get it to work unless you
understand the methods completely.  It is really not very hard, the 
most confusing thing are the linked lists.  Good luck.


THE BASIC IDEA BEHIND SPRITE ANIMATION

The basic idea behind sprite animation (or any animation, for that matter) 
is:

     Show frame 1 -> wait delay period -> Show frame 2 -> delay -> etc..

Pretty simple, but how do you implement this?


                          - IMPLEMENTATION -

GENERAL SPRITE STUFF

For this discussion I will assume you have a linked list of sprites, and
each node in the list represents a sprite and contains all the information
relevant to that sprite (of course you could use an array for this as well
with some simple modifications).  I am using C type pseudo code - I hope
that everybody can understand it.

Each node may look like:

struct sprite {

   int      xpos, ypos;     // X and Y coordinates for sprite
   int      xspeed, yspeed; // X and Y speed of sprite
   int      type;           // Type of sprite (ie 1 = ship, 2 = bullet etc..)
                            // These types will be defined by you
   char     background[256];
                    // Stores the piece of the background 
                    // that this sprite covers
   .
   .    // Various other parameters describing the sprite that you may 
   .    // require.
   .    // (ie does the sprite collide with other sprites?,
   .    //     does the sprite bounce around or just disappear at edge of
   .    //     the screen?  etc.. )
   .
   struct sprite  *next;  // Pointer to next sprite (NULL means end of list)

};

And you will have a pointer to the top of this list:

    struct sprite *sprite_list;

So you will have a list that looks like this:

sprite_list --> Sprite Data    /----> Sprite Data     /-->NULL
                Next ---------/       Next  ---------/


NOTE: For simplicity, I am assuming that all sprites are the same size.
Which is a dumb assumption, but it should be pretty easy to alter
these functions to work with variable sized elements.

You should have an add_sprite() function and a delete_sprite() function 
(which are not given here, but are pretty simple) which add and delete
elements to and from the linked list.  And you will have a function that 
looks like this:

// This function goes through the sprite list and updates all sprites
void do_sprites(void) {

   struct sprite *current;

   current = sprite_list;

   while (current != NULL) {

      // This function restores the background, by placing the 
      // data pointed to by current->background at current->xpos,
      // current->ypos.  You write this one.
      RESTORE_BACKGROUND(current->background, current->xpos, current->ypos);
      
      // Update x and y coordinates
      current->xpos = current->xpos + current->xspeed;
      current->ypos = current->ypos + current->yspeed;

      // Do whatever bounds checking or whatever you want here

      // This function will make a copy of the background that will be 
      // covered by the sprite in the array pointed to by 
      // current->background. You have to write this one too.
      CUT_BACKGROUND(current->background, current->xpos, current->ypos);

      // Draw sprite - This function draws the sprite at xpos, ypos
      // the current->type would indicate what data to use when drawing.
      // You'll have to write this one also.
      // Uses the sprite_data[] array which contains the bitmaps for the
      // sprites, see below for more details.
      DRAWSPRITE(sprite_data[current->type], current->xpos, current->ypos);

      current = current->next;
   }
}

You would call this function every time you want to move the sprites (ie
every time you go through your main loop).  Note that this is very simple
and you don't have to do it this way, I am only providing this example
to make the discussion of the animation more understandable.

To draw the sprite, you send the drawing function the address where the 
sprite data starts. This data would be stored in an array that looks 
something like:

unsigned char sprite_data[MAX_NO_OF_SPRITES][MAX_SIZE_OF_SPRITES];

So the data for sprite type 10 starts at sprite_data[10][0]. Remember that
I am assuming all sprites are the same size. 

So, when do we animate?  Right now.


ANIMATION

For animation I use linked lists.  You could use arrays instead, but 
I like linked lists because they can be referenced faster and more neatly.
The downside is that they are a little messy to set up and to deallocate when
finished.

Each particular sprite animation has its own linked list and each node
in that list represents a frame.  Each node has a structure that looks like:

struct frame{
  unsigned char *data;  // Pointer to the bitmap data for this frame.
                        // If you are using compiled bitmaps, you could put
                        // a pointer to the compiled bitmap function here.
  
  int           delay;  // This value indicates how long to wait before
                        // going to the next frame.

   struct frame *next;  // Pointer to the next frame
}

So a sprite that just does the same thing over and over again.  Say an
enemy ship that has blinking lights would have a linked list that looks
like :

/-->   FRAME 1      ---->  FRAME2         -----\
|      (Lights on)         (Lights off)        |
|                                              |
|                                              |
 \---------------------------------------------/

Note that this animation only has two frames, but you could have as many as
you like.  Also note that each frame has its own delay time so you can 
make some frames appear for longer than others.


Now this is where linked lists get a little messy, to create a linked
list that had the above format it would look like:

   struct frame *current, *last;

   ...
   
   current        = allocate( sizeof(struct frame) );
   // We store the pointer to the start of the animation in the array
   // ANIMATIONS, see below for more details.
   animations[0]  = current;                      
   current->data  = POINTER_TO_FRAME1_DATA;
   current->delay = 20;
   last           = current;

   current        = allocate( sizeof(struct frame) );
   current->data  = POINTER_TO_FRAME2_DATA;
   current->delay = 20;
   current->next  = last;
   last->next     = current;

   ...


Now assuming that we have several animation lists set up, how
do we keep track of them?  Simply use an array.  We can use our TYPE
field to index this array.  For example:

struct frame *animations[MAX_ANIMATIONS];

So say that element 0 in the animations[] array is a ship and element
1 is a bullet, then the array would look like:

    animations[0] --> Animation linked list for ship.
    animations[1] --> Animation linked list for bullet.

So suppose we are creating a sprite that has type 4.  We know that the 
animation starts at animations[4].


Now we must add a couple of fields to the sprite struct that was defined 
a little earlier in this document to help with our animation.  
These fields are:

struct sprite {
   ...
   struct frame *a_frame;   // The animation frame that we are currently on
   int          a_count;    // A counter that keeps track of the delay
   ...
};

So for each sprite we have these extra two fields.  Now when we are adding
a sprite to the sprite_list, in addition to initializing xpos, ypos, xspeed,
yspeed etc..  we have the following two lines:
 
   new_sprite->a_frame = animations[new_sprite->type];
   new_sprite->a_count = new_sprite->a_frame->delay;

So for each sprite, we now will know what animation frame we are currently 
on and how long we have left on this frame.

Each time we update the sprite, we subtract one from the a_count value.
When a_count == 0, we move to the next frame.  
   

Here is a new version of do_sprites() using animation:

void do_sprites(void) {

   struct sprite *current;

   current = sprite_list;

   while (current != NULL) {

      RESTORE_BACKGROUND(current->background, current->xpos, current->ypos);

      // NEW PART STARTS HERE
      current->a_count--;
      if (current->a_count ==0) {
         // If we have reached end of animation, move to next frame and
         // reset a_count.
         current->a_frame = current->a_frame->next;
         current->a_count = current->a_frame->delay;
      }
      // NEW PART ENDS HERE

      current->xpos = current->xpos + current->xspeed;
      current->ypos = current->ypos + current->yspeed;

      // Do whatever bounds checking or whatever you want here

      CUT_BACKGROUND(current->background, current->xpos, current->ypos);
      DRAWSPRITE(sprite_data[current->type], current->xpos, current->ypos);

      current = current->next;
   }
}

Once you have set up the animation linked list and added the sprite to 
the sprite linked list, every time you call do_sprites the animation is done
automatically.


FINITE ANIMATIONS

Finite animations are animations that don't have a loop, that is, they end.
Say for example an explosion, may have three frames: A small explosion,
a bigger explosion and the final biggest explosion.  Once this animation 
has ended we want to delete the explosion from sprite_list.  This is easy
to do.


The animation linked list for the described explosion would look like:

  EXPLOSION  ----->  EXPLOSION  -----> EXPLOSION -----> NULL
  FRAME # 1          FRAME # 2         FRAME # 3


The NEXT field of the last frame will be a NULL pointer.  So when you are
advancing an animation, simply check to see if the next frame if NULL, if
it is, then delete the sprite.

This alteration would look like:      

void do_sprites(void) 
{
   ...
      
      current->a_count--;
      if (current->a_count ==0) {
         // If we have reached end of animation, move to next frame and
         // reset a_count.
         if (current-a_frame->next == NULL)
            // This function deletes the sprite from sprite_list
            delete_sprite(current);
         else {
            current->a_frame = current->a_frame->next;
            current->a_count = current->a_frame->delay;
         }
      }

   ...
}


Two things I must mention:
   1. I didn't bother to show it in the above code fragment, but if you
      delete the sprite, make sure that you jump over the following lines
      that update the x and y positions and draw the sprite.  Since the node
      no longer exists, these operations are no longer valid.

   2. This has nothing to do with sprites, but it is important to
      realize that on MS-DOS machines comparing two pointers will
      not always work because of the segmented architecture. 

      This is important when deleting a node.  For example suppose
      CURRENT is the node to be deleted, and LAST is the node directly
      before CURRENT in the list.  To delete CURRENT we would do the 
      following:

          last->next = current->next;
          free(current);

      So we need to know which node precedes the node to be deleted.
      Since the nodes don't keep track of the previous node (only the next) 
      we must traverse the list and find the node that precedes the node 
      to be deleted.  But since we cannot compare pointers, we cannot simply 
      do the following: (current is a pointer to node to be deleted)

         last = sprite_list;
         while (last->next != current)
            last = last->next

      What I did was to give each node a unique nodeid, so to delete
      a node, do the following:

         last = sprite_list;
         while (last->next->nodeid != current->nodeid)
            last = last->next

      This version would work.  Remember that each node must have a UNIQUE 
      nodeid value.  I hope this makes sense,  if you are familiar with
      linked lists it should.  Although this isn't directly related to 
      sprite animation you must understand this problem if you wish to
      implement the system as described in this document.


CLEANING UP THE ANIMATIONS

When the program is done executing, we must deallocate all the memory
that is used up by the animation linked lists.  As I mentioned earlier, 
this is a little messy, but not too bad. (Also remember to deallocate all 
the nodes in the sprite_list linked list.  This is very easy to do.)

Remember that all the animations are pointed to by the array called
(not surprisingly) animations[].  For each element in this array, we 
delete the animation that is pointed to.

This is pretty simple, except that some of the animations have loops in them
so we cannot simply do the following.

   struct frame *current, *next;
   
   current = animations[0];
   while (current!=NULL) {
      next = current->next;
      free(current);
      current=next;
    }

What we must do is first check to see if the list is finite.  So what I do 
is follow the list for 10 frames and see if a NULL pointer is found, if we
find one, the list does not loop and we can use the above method.  If we 
don't find an end, (and we are assuming that no animations have 10 or
more frames) then we "break" the loop, turning the animation int a non-
looped list and delete the animation as above. This "breaking" looks
like:
    
   struct frame *current, *next;
   
   // Break the loop
   next = animations[0];
   current = next->current
   next->next = NULL;

   // Deallocate list
   while (current!=NULL) {
      next = current->next;
      free(current);
      current=next;
    }

It is good practice to always deallocate all dynamic memory before 
terminating your program, so always try to do it, even if it is a pain.


CONCLUSION

Well, that's pretty much it.  The basic idea is pretty simple, but I 
complicated it with all the implementation details.  I hope this makes 
sense to people.  

If you have any questions/comments/criticisims please feel free to E-mail 
me at umcaner0@ccu.umantioba.ca

Also feel free to send me any programs you write that uses this technique,
I would like to see if anybody can actually decipher this mess and 
get something working.