Jump to content

  • Log In with Google      Sign In   
  • Create Account

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


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
16 replies to this topic

#1 GKANG   Members   -  Reputation: 163

Like
0Likes
Like

Posted 14 February 2013 - 04:01 PM

I want to code something similar to how the animate function works with jQuery:

 

var duration = 500, moveY = "50px", moveX = "70px";
$("#someBox").animate({top: moveY, left: moveX}, duration);

 

Here I have a box or container which would move right 70px and down 50px, and that animation would take 500 milliseconds. Now to make this relevant to my game, I'm attempting to make Pokémon style, grid-locked movement:

 

- Press right

--- Player loses control

--- Character runs through one animation cycle

--- Moves right one tile (eg. 16px)

--- Happens over 0.5 seconds

--- Character stops, player gains control

 

Right now I can make it so that the character moves in a direction for X amount of time, but it doesn't explicitly move a set amount of pixels per say.. I have a float for moveSpeed and it's just playerPos.x += moveSpeed for the amount of time. I also tried doing playerPos.x += GRID_SIZE, which guarantees that he moves the exact amount of space, but it just teleports and doesn't animate in a transitional movement. It's important that the player sticks strictly to the grid and is never off-centre.

 

In short, I'd like to make this function:

 

player.move(direction, distance, duration);

 

I'm also having trouble with limiting my game loop / making things happen at regular intervals, so this is likely making my problem much harder. Anyway I hope this is clear enough. If not, I'd be happy to try and explain further. Thanks in advance.

 



Sponsor:

#2 agentultra   Members   -  Reputation: 190

Like
0Likes
Like

Posted 14 February 2013 - 04:05 PM

http://www.koonsolo.com/news/dewitters-gameloop/

 

Is an excellent article on programming game loops I've found pretty useful.

 

The function you're going to want to google for is "tweening."

 

I'm not sure how to implement it but that definitely sounds like what you might be interested in.



#3 GKANG   Members   -  Reputation: 163

Like
0Likes
Like

Posted 14 February 2013 - 04:07 PM

I read the dewitters article and that one other article people always suggest multiple times but a lot of it goes over my head. They explain how the loops work, but since I've never done game loops before, I'm not sure what each variable is supposed to represent. Eg. Skip ticks. It confuses me, so does interpolation.

 

I'll look that up now, thanks for the suggestion (:



#4 L. Spiro   Crossbones+   -  Reputation: 14232

Like
3Likes
Like

Posted 14 February 2013 - 04:12 PM

You can’t do it properly that way due to floating-point imprecision. You need to pass the actual target location or the actual offset to the target location in pixels.

player.move(50, 50, duration);

[50,50] here is the offset.
The player then calculates the final location in pixels and stores it as endPos.

direction is then calculated by the player class as ||[50,50]|| (normalize the input X and Y vector).
Distance is of course the length of the vector [50,50]. These are not passed to the player but calculated by the player class.

Then the player calculates how fast to move per second.
moveVector = (direction * distance) / duration;
After this, each logical update (which should use a fixed time-step) moveVector is added to the player’s position after being multiplied by the amount of time that has passed.

When the amount of passed time exceeds duration, the player’s position is specifically set to endPos to avoid floating-point and time-related errors.


L. Spiro

Edited by L. Spiro, 14 February 2013 - 04:13 PM.

It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#5 GKANG   Members   -  Reputation: 163

Like
0Likes
Like

Posted 14 February 2013 - 04:41 PM

I have a couple of questions which would help clarify, if you could answer them. I'm really new to this.
 

[50,50] here is the offset.

So I could just make a Vector2i offset(50, 50) right?

The player then calculates the final location in pixels and stores it as endPos.

endPos = player.pos + offset?

direction is then calculated by the player class as ||[50,50]|| (normalize the input X and Y vector).
Distance is of course the length of the vector [50,50]. These are not passed to the player but calculated by the player class.

This bit confused me. All I'm doing at the minute is:
if(keypress == down) player.y += move
if(keypress == left) player.x -= move 
.. kinda thing. I'm not really sure what values for direction you're thinking of here, sorry.

Then the player calculates how fast to move per second.

moveVector = (direction * distance) / duration;
After this, each logical update (which should use a fixed time-step) moveVector is added to the player’s position after being multiplied by the amount of time that has passed.


See above. I can see how moveVector = distance / duration would work though.

#6 L. Spiro   Crossbones+   -  Reputation: 14232

Like
0Likes
Like

Posted 14 February 2013 - 04:47 PM

So I could just make a Vector2i offset(50, 50) right?

Yes.

endPos = player.pos + offset?

Yes.

This bit confused me. All I'm doing at the minute is:

if(keypress == down) player.y += move
if(keypress == left) player.x -= move 
.. kinda thing. I'm not really sure what values for direction you're thinking of here, sorry.


Isn’t that contradictory with your original statement that during these little animations the player has no control?
This is supposed to be an automatic movement by the character, yes?


L. Spiro

Edited by L. Spiro, 14 February 2013 - 04:48 PM.

It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#7 Khatharr   Crossbones+   -  Reputation: 3038

Like
1Likes
Like

Posted 14 February 2013 - 04:50 PM

Here's a simplified implementation I threw together in SciTe. It's a bit rough and it's not tested, but I hope it can demonstrate one way of implementing the concept.

 

//Assume directions match numbers on the numeric keypad: 2 is down, 8 is up, etc.

class Actor {
  int update() { //do frame logic
    if(pendingMotionFrames == 0) { //no motion in progress
      int inputDir = Input.dir4(); //get a direction from the input state
      if(inputDir != 5) { //a direction was input
        facing = inputDir;
        int targetTileX = tileX;
        int targetTileY = tileY;
        switch(facing) {
          case 2: ++targetTileY; break;
          case 4: --targetTileX; break;
          case 6: ++targetTileX; break;
          case 8: --targetTileY; break;
        }
        if(tileIsPassable(targetTileX, targetTileY)) {
          tileX = targetTileX;
          tileY = targetTileY;
          pendingMotionFrames = FRAMES_FOR_SINGLE_TILE_MOTION;
        }
      }
    }
    else { //motion already in progress
      --pendingMotionFrames;
      updateWalkAnimationFrame(); //select the correct spritesheet frame
      int positionOffset = pendingMotionFrames * STEPPING_FRAME_DISTANCE;
      sprite.y = tileYPosToScreenYPos(tileY);
      sprite.x = tileXPosToScreenXPos(tileX);
      switch(facing) {
        //in the simulation we already occupy the destination tile, so we position the sprite there
        //and then move it backwards based on the position offset. As the frames advance the offset
        //shrinks to zero and the sprite moves smoothly to the new tile.
        case 2: sprite.y -= positionOffset; break;
        case 4: sprite.x += positionOffset; break;
        case 6: sprite.x -= positionOffset; break;
        case 8: sprite.y += positionOffset; break;
      }
    }
  }
  int tileX;
  int tileY;
  int pendingMotionFrames;
  int facing;
  Sprite sprite;
}


Edited by Khatharr, 14 February 2013 - 04:54 PM.

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.

#8 GKANG   Members   -  Reputation: 163

Like
0Likes
Like

Posted 14 February 2013 - 06:06 PM

Isn’t that contradictory with your original statement that during these little animations the player has no control?
This is supposed to be an automatic movement by the character, yes?

Sorry for the confusion, you're correct. I just tried to think of some example where the direction would basically choose an operator and an axis (+/-, y/x) and not necessarily hold its own value like your example seems to.

Khatharr thanks a ton for posting that class. I'll read over it a few times to make sure I understand.

Edited by GKANG, 14 February 2013 - 06:06 PM.


#9 GKANG   Members   -  Reputation: 163

Like
0Likes
Like

Posted 14 February 2013 - 06:56 PM

I think I understand all but one part of the function:
      sprite.y = tileYPosToScreenYPos(tileY);
      sprite.x = tileXPosToScreenXPos(tileX);
What exactly are these functions doing?

Also, FRAMES_FOR_SINGLE_TILE_MOTION would be the number of frames for a single animation cycle and STEPPING_FRAME_DISTANCE would be the width of a tile (16px)?

#10 Khatharr   Crossbones+   -  Reputation: 3038

Like
1Likes
Like

Posted 15 February 2013 - 04:16 AM

Those functions are placing the sprite in the location of the destination tile. Whatever you have that determines where a specific tile position is on the screen, those functions should return a pixel x/y in terms of the screen based on the map tile x/y passed in. Immediately after that the sprite gets placed in this position it gets 'backed up' to its interpolated position between the destination tile and the origin tile.

FRAMES_FOR_SINGLE_TILE_MOTION would be the 'duration' that Spiro was telling you about. The number of frames it takes to walk from one tile to the next.

STEPPING_FRAME_DISTANCE would be the number of pixels traveled in one frame.

So if you had 32-pixel wide tiles and you want to make the trip in 8 frames then FRAMES_FOR_SINGLE_TILE_MOTION would be 8 and STEPPING_FRAME_DISTANCE would be 4, which is 32 divided by 8.

So say you're on a tile and you start a move to the left.
Your tile position gets changed and:
pendingMotionFrames = FRAMES_FOR_SINGLE_TILE_MOTION; //8
The sprite facing gets changed (which is not in the code) but the sprite is not moved yet.

Next frame:
pendingMotionFrames is reduced to 7
int positionOffset = pendingMotionFrames * STEPPING_FRAME_DISTANCE; //7 * 4 = 28
sprite gets moved to the target tile, it's facing is still leftwards
since it's facing leftwards sprite.x -= positionOffset, so the sprite gets moved back by 28 pixels. It's effectively moved 4 pixels from its starting tile.

The next frame pendingMotionFrames is reduced to 6, so positionOffset will be 24. The sprite gets put on the destination tile and then moved backwards 24 pixels. It now appears to have moved 8 pixels.

This repeats until pendingMotionFrames gets set to zero. The sprite will get placed on the destination tile, but positionOffset will be zero, so it will stay put there and the movement is complete.

This is just a different way of implementing the same interpolation Spiro-sama was telling you about. She's discussing it in a more general sense. The method I'm talking about is specific to the type of game you're working on. You should really look into what she's talking about and learn the general case, then try to see how that's implemented in what I'm talking about.
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.

#11 GKANG   Members   -  Reputation: 163

Like
0Likes
Like

Posted 15 February 2013 - 09:41 PM

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.



#12 Khatharr   Crossbones+   -  Reputation: 3038

Like
0Likes
Like

Posted 15 February 2013 - 09:57 PM

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.

Edited by Khatharr, 15 February 2013 - 10:02 PM.

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.

#13 GKANG   Members   -  Reputation: 163

Like
0Likes
Like

Posted 15 February 2013 - 10:45 PM

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.



#14 Khatharr   Crossbones+   -  Reputation: 3038

Like
1Likes
Like

Posted 16 February 2013 - 03:00 AM

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!]

Edited by Khatharr, 16 February 2013 - 03:11 AM.

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.

#15 Satharis   Members   -  Reputation: 1253

Like
0Likes
Like

Posted 16 February 2013 - 03:21 AM

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.



#16 GKANG   Members   -  Reputation: 163

Like
0Likes
Like

Posted 16 February 2013 - 09:36 PM

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



#17 Khatharr   Crossbones+   -  Reputation: 3038

Like
1Likes
Like

Posted 19 February 2013 - 02:44 AM

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.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS