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 > Animation Timing

#170213 - Azenris - Tue Sep 08, 2009 8:45 pm

Hi,

I was running a search for animation timing, but I cant seem to dig much up on it, which is weird considering I remember reading something about it on these forums a while back.

Back when I first made a GBA game I just used my loop cycle, but I want to do it in a better way now.

Basically I was wonder if you could point me in the direction of any information regarding animation timings for the NDS.

TY for any help
_________________
My Homebrew Games

#170216 - gauauu - Tue Sep 08, 2009 10:21 pm

Not sure exactly what you're looking for here.

For in-game animations of things like sprites, it makes sense to have each frame of animation last for a certain amount of in-game frames, which are generally synced up with vblank (either 1 vblank per frame, or 2, which makes it 30 fps).

I could elaborate on that, but it sounds like that's not what you are looking for?

#170217 - Azenris - Tue Sep 08, 2009 11:40 pm

So your saying just update the frame every 2 calls to my sprite update (as thats 2 swiWaitForVBlank() calls)

Code:

void CGame::Run(void)
{
   m_pScreenManager->LoadLevel(0);

   while (m_running)
   {
      // update the screen manager
      m_pScreenManager->Update();

      // update the sprite manager
      m_pSpriteManager->Update();

      // wait for the V blank
      swiWaitForVBlank();
   }
}


And that wouldnt cause me problems? I just though animation should be based on timers or something. I dunno, guess Ill experiment and see.
_________________
My Homebrew Games

#170218 - headspin - Tue Sep 08, 2009 11:59 pm

I use a timer to keep track of the milliseconds and do animation based on elapsed time.

ie. Setup a timer for keeping track of milliseconds

Code:
TIMER2_DATA = (u16) TIMER_FREQ(1000);
TIMER2_CR = (TIMER_ENABLE | TIMER_IRQ_REQ | TIMER_DIV_1);


Then in your main loop call an "Update" method to update the current animation.

Code:
void Update(int elapsedTime)
{
   m_lastUpdate += elapsedTime;
   
   if(m_lastUpdate > 100) // 100 milliseoncds
   {
      m_lastUpdate = 0;
      
      // Do animation
   }
}


Obviously you need to keep track of the current frame and move the frame forward each call and then also have a "Draw" method based on the current frame.

Also consider having a "pingpong" type boolean value which allows you to go forward and back through frames instead of just forward. And perhaps consider the use of rand() for jumping to random frames.
_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game


Last edited by headspin on Wed Sep 09, 2009 2:26 am; edited 1 time in total

#170219 - DekuTree64 - Wed Sep 09, 2009 12:30 am

I prefer animation based on multiples of the game's frame rate (and consequently, I prefer fixed frame rate games). So you just have a delay counter, decrement it once per frame, and when it hits 0, go to the next frame and reset the delay.

It's not so friendly on PC games (something I'm debating on my current project) since CRTs are usually flickery at 60Hz so it's better to run at 75-85Hz, which doesn't time out well with a 30fps game... at least my LCD is 60Hz though.

It also has the problem that there's a huge difference between a delay of 1 and a delay of 2, so sometimes it's hard to avoid things like feet looking like they're sliding on the ground due to stride length versus movement speed.

But for variable frame rate, something like headspin's timer setup is the way to go. Then use an accumulator sort of thing to update the animation:
Code:
void Animation::update(int timeElapsed)
{
    mTimer += timeElapsed;
    while(mTimer >= mDelay)
    {
        mTimer -= mDelay;
        if (++mFrame >= mFrameCount)
            mFrame = 0;
    }
}

Plus you can use mTimer for interpolation between frames if you're doing 3D or vector based graphics.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#170220 - gauauu - Wed Sep 09, 2009 4:40 am

Azenris wrote:
So your saying just update the frame every 2 calls to my sprite update (as thats 2 swiWaitForVBlank() calls)


Nope, that's not it.

First, there are 2 different things with similar terminology, which makes things confusing: game frames, and animation frames. A game frame is one time through the main game loop. And THIS is generally tied to vblank. Either after each main game loop, swiWaitForVBlank is called (one game frame per vblank) or two vblanks occur for each time through the game loop.

So one way of timing animations is by saying that each frame of animation lasts, for example, x number of game frames. In that case, you increment a counter with each game loop, and when it gets to a certain point, you go to the next frame of animation.

Code:

void mainLoop()
{
    while(1)
    {
        updateGameLogic();
        updateSpriteAnimation(animation);
        swiWaitForVBlank();
    }
}

void updateSpriteAnimation(Animation * animation)
{
    animation->frameCounter++;
    if (animation->frameCounter > animation->animationDelay)
    {
        animation->goToNextAnimationFrame();
    }
}


That's way oversimplified, but hopefully you get the drift. And maybe that's what you're saying you already did and want something more advanced?

In this case the animation structure should have all the "brains" of the animation -- what is the next frame, how long to stay on each frame, etc.

#170222 - sverx - Wed Sep 09, 2009 8:21 am

IMHO the way to go for a 2D NDS game (maybe it's different with 3D, it is surely different if you're coding a game for a PC) is to calculate everything in screen native FPS, or a fraction (/2, /3, /4 ...)

So your game will run at 60 frames per second (and what's on screen can change every frame) or a 30 FPS (what's on screen can change every 2 frames) or 20 FPS, or 15 FPS (well, I wouldn't go so slow... ;) )

Btw if you've got sprite animations with different frame rate (why not? maybe your hero has a very smooth animation and some simple enemies have simple animation...) then you've got to remember that sometimes you've got to show the same frame for the sprite twice or three times...

Hope you get what I mean...

Oh, right. Everything should be timed with swiWaitForVBlank() , also because of battery saving reasons.

#170224 - Miked0801 - Wed Sep 09, 2009 5:37 pm

Keep in mind you can use fixed point numbers to get fractional animation timings. Basically, add a fixed point value to your frame every tic and then use the truncated value for the frame to display. This gets you a lot more control. It's trivial to do a 1/2/1/2 type timing with this or even frame skip if needed.

#170225 - Azenris - Wed Sep 09, 2009 5:46 pm

I went with the using the games fixed fps. Atm its being used for my tile animations (water tiles, convayer belts etc). Will get around to sprite animation soon.

Thanks for all the info.

It currently stands at

Code:

void CScreenManager::Update(void)
{
   for (int tile = 0; tile < TILES_PER_LEVEL; ++tile)                     // loop all the tiles on the screen
   {
      if (m_data[tile].tileID != 0)                                 // check its not an empty location
      {
         const TILE_DATA *pTile = g_pGame->GetTileData(m_data[tile].tileID);   // locate data on this tile

         if (pTile->aniFrames > 1)                                 // check if the tile is animated
         {
            ++m_data[tile].frameCounter;                           // increase the frame counter

            if (m_data[tile].frameCounter > pTile->delay)               // check if the frame needs updating yet
            {   
               m_data[tile].frameCounter = 0;                        // reset the frame counter

               if (m_data[tile].frame >= (pTile->aniFrames - 1))         // update the frame
                  m_data[tile].frame = 0;                           // loop the frame to start if needed
               else
                  ++m_data[tile].frame;                           // increase the frame by 1
   
               UpdateFrame(tile, m_data[tile].tileID, m_data[tile].frame);   // update the image being displayed
            }
         }
      }
   }
}


I do however intend to modify it slightly and instead of going through all the m_data, just have a list of animated only tiles, so I'll have less to loop.
_________________
My Homebrew Games

#170229 - Azenris - Thu Sep 10, 2009 1:02 am

Got my sprites animated now too. Only prob is in my design I cant have the same sprite using diff animation pictures! When I load sprites I only save 1 graphic image and have a reference count to it. For the animation im just changing that picture

I guess it doesnt matter for things like the cherries, they can just bob up and down together. Maybe I should have an option to load a sprite as special, for things I need running seperate.
_________________
My Homebrew Games

#170233 - sverx - Thu Sep 10, 2009 10:28 am

Miked0801 wrote:
Keep in mind you can use fixed point numbers to get fractional animation timings.


Yep. Easy with 1/2 and 1/4, gets a little weird with 1/3, 1/5... ;)

#170234 - Dwedit - Thu Sep 10, 2009 1:21 pm

16.16 fixed point is accurate for integer division by 3 for numbers up to 32767, and is accurate for integer division by 5 for numbers up to 16383.

So that means you would need to add 1/3 32768 times before you see any fixed point rounding errors creeping into the integer part.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."

#170236 - sverx - Thu Sep 10, 2009 1:56 pm

Dwedit wrote:
that means you would need to add 1/3 32768 times before you see any fixed point rounding errors creeping into the integer part.


True, but it means a 'frame skip' every 9 minutes and 6 seconds at 60 FPS. IMHO I woulnd't use that, I'd use a subcounter.

#170246 - Miked0801 - Fri Sep 11, 2009 2:41 pm

Lol - IMHO, a frame skip once every 30 seconds wouldn't be noticable :)

Subcounter is is basically taking the fractional portion of the fixed and manually setting the values.