GENWiki

Premier IT Outsourcing and Support Services within the UK

User Tools

Site Tools


archive:programming:spranim.pro

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 * [Shameless Plug]

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?

  1. 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.

/home/gen.uk/domains/wiki.gen.uk/public_html/data/pages/archive/programming/spranim.pro.txt · Last modified: 1999/08/01 17:20 by 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki