gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Beginners > Sprites, animation states and animation frames

#23776 - DialogPimp - Wed Jul 21, 2004 12:07 pm

As my first noob post I'd just like to say thanks to all who have contributed to this forum. My first project (a Frogger clone) is progressing nicely thanks to lots of searching on these forums, the various tutorials and devkitarm/VC6.

I'm looking for thoughts from the more experienced about animation and state systems for sprites. Whilst not strictly needed for my first project, I'm planning for the future and would like to have a decent framework in place.

My thinking so far is revolving around a sprite having 1 to n states (climb ladder, walk left, walk right etc), each of which may have one or more frames (eg 4 frames for climbing a ladder), each of which may need to be displayed for n number of game frames/vblanks.

Add to this the limited amount of VRAM available and (it seems) that DMA'ing in each frame into the relevant OAMData area for that sprite is the way to go. The excellent document by Tepples would confirm this.

So, how do the pros handle animation systems like this ? I was thinking FSM and some sort of LUT ???

#23781 - poslundc - Wed Jul 21, 2004 1:48 pm

You've done your homework, and you're pretty much dead on.

If you're working in straight C, the way I do it is by defining a SpriteLibrary structure that basically contains the dictionary definition of a sprite character. This contains, among other things, a pointer to the raw image data of the sprite, things like the sprite's width and size, and information needed to animate the sprite.

For this animation information, I use another structure called SpriteKeyframe, which simply defines an index into the sprite data and the number of VBlanks to delay before switching to the next frame. (I'd have to check, but I may also include some other information like H/V flipping.) An array of these SpriteKeyframes will define a sequence of animation; an array of these arrays will define a dictionary of different animation sequences for different sprite states, eg. walking, running, jumping, etc. This array is then referenced by my SpriteLibrary record.

Finally, I use a SpriteEntity record for each of my active character entities in the game. This SpriteEntity is in active memory and references a SpriteLibrary, and contains state information like where the sprite is positioned, what animation state it is in, what keyframe, the animation counter, etc. It also contains some relevant hardware information, like where this sprite resides in Sprite VRAM (for purposes of where to DMA the next frame of animation to) and which palette it occupies (assuming 16-colour sprites). I also used to associate it with an OAM entry until I began generating OAM data on the fly.

Hope this helps,

Dan.

#23793 - keldon - Wed Jul 21, 2004 7:56 pm

FSM is the ultimate method of planning; for example (this is not FSM) you can write this:

VBL 1: write frame 1 to screen
VBL 2: (frame 1 drawn) test actions for frame 1 and display results for frame 2
VBL 3: (frame 2 drawn) etc.

This will remind you that when you draw something to the screen; that it will appear by the next frame; also remind yourself that your code after WaitVBL functions will occur after the next VBL frame; so the sequence of events are:

Code:
Enter Code: WaitVBL ()
- VBL Occurs: VBLHandler ()
- VBL Exits; VBL done
Code after waitVBL is executed

WaitVBL ()
- VBL Occurs: VBLHandler ()
- VBL Exits; VBL done
Code after waitVBL is executed


Also for animations you may have more than one sprite making up the animation; so you may, for example have a sprite map instead of just a sprite position. This is no difficult task. If you care creating a 2x2 map; for example with 5 frames then you create a 2x10 map with 5 frames. Now frame one is mapAddress [ 0 ], frame two is mapAddress [ 4 ]; and frame 'x' is mapAddress [ ( x - 1 ) * (2x2) ]

I personally am unsure of state machines tutorials, and have limited PC access to create a tutorial on it and upload; not to mention the computer I am doing my update is not at home right now; but I'm sure someone here will write one for you in time.

---
A little on FSA (Finite State Automa) using an example.

Consider the special moves in street fighter; The joypad has the following inputs:
d = down
f = forward
b = back
u = up
df = down,right etc.
HP = high punch

so to execute a 'fireball' you would have to give the following input:
d, df, f, HP - which is basically a quarter circle from down to forward, followed by a punch.

But how would you go about that in code? Do you wait for the HP and then work your way backward? What if he's in the air? What if he's punching at the current moment?

Keeping it simple, our starting state is stationary. Our Finite State Machine consists of nothing.

Code:
( 0 ) <-- start and end state


Now let's add a punch function

Code:
  0 <----|
  |      |
  |HP    |
  |      |
  V      |
  1 ------


How this works is that state 0 is stationary. When HP is recieved, code is now in State 1 and executes any necessary code for handling HP from state 1.

Now we can add to this FSM. How about we add a 'fireball' since we were talking about it.

Code:
    ---->  0  <-------
    |     / \        |
    |    /   \d      |
    | HP/     v      |
    |  /      2      |
    | /        \df   |
    |/          v    |
    v            3   |
    1            f\  |
                   v |
                   4 |
                  HP\|
                     v
                     5

the 'v' letter represents the down arrow. Now in this finite state machine we can execute a punch from the stationary position, but not after pressing down. When down is pressed from state 0, we then move over to state 2, from state 2 if df is recieved then we move over to state 3; and so forth. eventually at state 4 when we recieve HP we move on to state 5.

At each state we can perform an operation such duck between state 0 and 2 when down is recieved.

---

The link I have given you (in my later post) is a course which describes this better than I could; but the example I've given shows you how important the concept of finite state machines are. Unfortunately some of the files are inaccessable, such as when they ask you to go to specific file directories on the server; we're not allowed to share them files, but I can give you some of my own in a few weeks.


Last edited by keldon on Wed Jul 21, 2004 11:27 pm; edited 1 time in total

#23796 - poslundc - Wed Jul 21, 2004 9:45 pm

Keldon, what you are describing is a game state loop that can be used to process a finite state machine. It is a good architecture to do it with, but it is not an actual finite state machine, which is basically a set of states, inputs, outputs and a transformation function.

Most games are probably not very concerned with the issue of single-frame latency, especially since a game running at 60 FPS is running at approximately twice the threshold of human perception of fluid motion.

Dan.

#23801 - keldon - Wed Jul 21, 2004 11:02 pm

Oh, my bad I wrote my post wrong; I've corrected it now.

And here is a course on Language & Communication covering FSA's:
www.dcs.qmul.ac.uk/intranet/courses/ug/coursenotes/DCS103/


Last edited by keldon on Thu Jul 22, 2004 8:45 am; edited 1 time in total

#23806 - DialogPimp - Thu Jul 22, 2004 12:02 am

Thanks for your help - looks like I was moving in the right direction.

#23814 - DialogPimp - Thu Jul 22, 2004 4:03 am

Dan one last question. Did you decide upon an arbitrary maximum number of SpriteKeyFrames in your implementation ? I'm obviously wanting to keep all this info const which means I need to define my array sizes up front.

#23815 - sajiimori - Thu Jul 22, 2004 4:12 am

Quote:

Most games are probably not very concerned with the issue of single-frame latency, especially since a game running at 60 FPS is running at approximately twice the threshold of human perception of fluid motion.

30fps looks fine, but switching to 60fps makes everything look silky smooth and sharp. Try it sometime -- the difference is striking.

#23816 - sajiimori - Thu Jul 22, 2004 4:22 am

Quote:

Dan one last question. Did you decide upon an arbitrary maximum number of SpriteKeyFrames in your implementation ? I'm obviously wanting to keep all this info const which means I need to define my array sizes up front.

I can't speak for Dan, but I usually either have a pair of an array and a length, or an array with a termination marker.
Code:

// explicitly store length
const int array[] = { 100, 200, 300 };
const int length = sizeof(a)/sizeof(*a);

// use -1 as a terminator
const int array[] = { 100, 200, 300, -1 };

When you're using structs, the last field can be an array of unspecified length:
Code:

typedef struct
{
    int array1[3]; // normal struct member

    //int array2[]; can't do this because compiler can't
    // determine offset of array3

    int array3[]; // doesn't matter how big this array is
    // because there's nothing after it to access.
} S;

const S s = { { 100, 200, 300 }, { 400, 500, 600, -1 } };

#23817 - DialogPimp - Thu Jul 22, 2004 5:56 am

Mmmph - thanks sajimori. I read that in a previous post of yours, tried it early on and got build errors galore. I just went back and tried it again and all was fine, so there's another problem solved.

#23827 - poslundc - Thu Jul 22, 2004 1:38 pm

Saji's got it... I store both the pointer to the array and an integer saying the array length.

Random example of how it might be done:

Code:
static const Keyframe aniWalk[] = {
     (Keyframe){0, 4},   // assume first element is sprite's index,
     (Keyframe){1, 4},   // second element is delay to next frame
     (Keyframe){2, 4},
     (Keyframe){3, 4}
};

static const Keyframe aniJump[] = {
     (Keyframe){0, 4},
     (Keyframe){4, 3},
     (Keyframe){5, 3},
     (Keyframe){6, 1},
     (Keyframe){0, 4}
};

static const Keyframe *aniFrames[] = {aniWalk, aniJump};
static const int aniFrameLengths[] = {4, 5}; // size of the arrays

const SpriteInfo jumpMan = {
          ...
     aniFrames,
     aniFrameLengths,
          ...
};


Dan.