Issues with animating for a durating, relative to game loops?

Started by
15 comments, last by Khatharr 11 years, 1 month ago

So if I understand exactly what's going here..

2vchmxc.png

The values aren't identical but is the theory correct? The player moves left 32px to the targetTile, then move back to the original position -4px, then back etc, to give the effect that the whole time the sprite was just moving at a steady -4px rate? Interpolation is where I get lost off but your walkthrough just now may have made it clear what's going on, with the whole wavey pattern thing.

Advertisement
You could consider each frame to be 'destination - 4n' where n is the frame number, beginning at 8 and reducing to zero. I separated the operations in the code in order to calculate the offset first and then apply it to the correct axis in the correct direction.

The whole idea behind interpolation is:

now = start + (total_change * current_frame / total_frames)

start -> starting value
total_change -> 'start + total_change' would equal the end value
current_frame -> starts at zero and increases by 1 each frame
total_frames -> the number of frames over which the process should take place

I just did it 'backwards' by subtracting from the destination rather than adding to the origin. The result is the same either way, but I prefer the 'backwards' method for this case because it means that at the end of the process no correction is required and it allows the test 'if(pendingMotionFrames == 0)', which simplifies the logic a little bit.

The core idea behind the math is that you're multiplying the change by the fraction 'cur_frame/total_frames'. So if half the motion is done it would be:

now = start + (total_change * 1/2)

Half of the change is applied.
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

Yeah I think I get the concept now, you're really helping me out. I also like and understand your idea of doing it backwards, since you're always going to end exactly at the destination and any leftover decimal value would be lost at the start / during the movement instead of being left at the end right?

I'm not 100% comfortable with handling time yet, and knowing exactly how fast my program is running. I think if I make a clock then do dt = clock.restart() (which also returns the current elapsed time) in my game loop then I'll know how fast my loop runs? Bah.

If you're basing on time then you need to use time instead of frames as your metric for the fraction in the interpolation. For tiled 2D RPGs you can get usually away with just v-syncing and per-pixel movement, though I may get yelled at for saying that.

In the case you'd want to decide how many milliseconds you want it to take to walk one tile and then:

start process:
motion_end_time = now + TIME_IT_TAKES_TO_WALK_ONE_FRAME;
is_moving = true;
then have:
if(is_moving) {
  if(now > motion_end_time) {
    position = destination_position;
    is_moving = false;
  }
  else {
    time_elapsed = motion_end_time - now;
    motion_completion_fraction = time_elapsed / TIME_IT_TAKES_TO_WALK_ONE_FRAME;
    position = start_position + (PIXELS_PER_TILE * motion_completion_fraction);
  }
}
If you're basing it off a timer don't reset the timer. At the start of the logical frame (the 'logical frame' being a single 'tick' of your simulation, not an animation frame) get the current time, subtract the previous frame's time from it and use that as your elapsed time for everything that gets updated in the frame. Definitely do not allow things within the logical frame to do this independently or you'll lose sync between your entities and get weird behavior, since they'll all have different values for elapsed time.

In other words, if you have 3 things that need updated:

Do:
get elapsed time
pass it to thing1's update
pass it to thing2's update
pass it to thing3's update
[everything is synchronized]

Do not:
update thing1 (thing1 determines elapsed time)
update thing2 (thing2 determines elapsed time)
update thing3 (thing3 determines elapsed time)
[time passes while things update - synchronization was lost!]
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

How this works really depends on the specifics of the movement you're trying to get and how your game loop works.

If you want to be simplistic you can set your game loop up to do set game ticks for time and basically have your character move another "step" between the start and end destination each tick, if it's free time you can get the time that passed and add it up and do the same thing.

What I mean by smoothness of motion is do you want your character to move 5 pixels towards the destination every 10 ms tick or something or do you want to move it 3 pixels then 6 pixels then 5 pixels based on how much time is elapsed between ticks? What we're talking about comes down to time steps.

Without getting into articles and such one of the best looking ways usually and how most physics engines do it is update the logic in set timesteps and then just interpolate the graphics so that the character movement looks smooth. E.g.: On my first frame of the keypress I figure out my character wants to move right, now I set it as moving right as a state, I set the start and end position and every tick I move the character closer towards the destination, it's easy to calculate the "steps" required if you know how long you want the movement to take, i.e. half a second.

But ignoring all my blabbering what it comes down to basically is moving the character step by step over a space of time and then clamping it into the final position if it is going to step over it on the next frame, all while changing the sprite or model or whatever along the way.

I've been getting bogged down by being referred to THE BEST GAME LOOPS EVER which are extremely accurate yet way over the top for what I need. Honestly for what I'm doing it just needs to be a super simple, rigid grid-based movement with somewhat smooth motion when moving between tiles (interpolation).

I was just getting the gist of your frames class, so ignore anything I've said about my own game loop since I don't really know much about them and my implementation is broken anyway. How can I set it up so that it uses frames instead of time? I really just want a game tick which is x length of time, so then I can base everything off of the tick. So like, walking one tile could take 4 ticks, running takes 2 ticks, fade to black when transitioning somewhere could take 10 ticks, auto-regen health starts after idling for 5 ticks.. Basically a solid variable which I can use to control how long everything is taking, which works alongside your nice little class. I would cry happy tears.

I really appreciate the help you're offering by the way, it means a lot. I hope I'm being clear with what I want to achieve so you don't end up explaining things that I have 'no interest in'.

This is just a fixed timestep as described in L.Spiro's article. If you want simplicity in this case then just don't implement graphics interpolation and do all your motion interpolation in the simulation as was discussed. In other words, draw as often as you like, but update the logic at a fixed rate. Something like:
loop {
  prev_time = now
  now = get_current_time() //use a high performance timer for this
  elapsed_time += (prev_time - now)
  if(elapsed_time < FRAME_DURATION) {
    //this branch will reduce your cpu usage if you're running fast
    yield_to_thread_scheduler() //on windows this function is 'SwitchToThread()'
    continue //as in return to start of 'loop'
  }
  while(elapsed_time >= FRAME_DURATION) {
    //this loop will catch you up if you're running slow
    update_logic() //process one logical frame
    elapsed_time -= FRAME_DURATION
  }
  //note that elapsed_time may be non-zero at this point. the excess will carry into the next iteration of 'loop'
  render()
}
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

This topic is closed to new replies.

Advertisement