Tutorial : Timing And Movement Part 1

A forum to store posts deemed exceptionally wise and useful

Did that tutorial help you ?

Yeah ! I liked it
18
62%
It was okay ...
7
24%
Leave me alone !
4
14%
 
Total votes: 29

Isometric God
Posts: 69
Joined: Sun Oct 12, 2003 3:42 pm
Location: Germany

Tutorial : Timing And Movement Part 1

Post by Isometric God »

Part 1 : Framerate-independent movement
---------------------------------------------------

Problem number one we are trying to solve will be object movement in a game with variable framerate. Why is that a problem, I hear you say. When it comes to movement of any kind, ( be it your hero slaying zombies or an alienship moving thorugh friction-less space ) the relationship between the framerate and movement becomes obvious. The more the framerate drops the slower your characters will move. The reason is simple : The movement code is executed every frame and the more you call the code ( per second ) the faster the object moves. Either you limit the number of calls to a certain value and render as often as possible the rest of the time ( this technique is called "decoupled update cycle" ) or you adjust your movement code
to a variable framerate. The second solution seems to be the more appropriate in my view.

As you surely know, the Irrlicht engine is a sole 3D engine. It does not provide most of the timing operations we will need. This is why we start with a wrapper class for the irrlicht timer. The main idea behind the code is in about the following :
The timer class measures the time passed since the last call and stores it. The movement code grab this value and multiply if the movement speed. Let's say your character moves 100 units per second. The timer class reports that 8ms ( ~ 125fps ) have passed since last frame. Multiplying the speed per second ( 100 ) with the time-delta value ( 0.008 ) returns the
aproximate distance to travel ( 0.8 units ). Sounds easy, right ?

The Irrlicht timer's only functions is to return the number of milliseconds that have passed since the operation system booted last. But who is interested in that ? Noone really, but still the timer is of crucial importance to us. First we store that number into a member variable of our timer class. Then we wait for the next frame to be rendered and measure the time difference.
It's as easy as this :

Code: Select all

void CTimer::Reset()
{
	m_Time = g_pDevice->getTimer()->getTime();
}


void CTimer::Update()
{
	m_DeltaTime = g_pDevice->getTimer()->getTime() - m_Time;
	m_Time = g_pDevice->getTimer()->getTime();
}
we can optimize this a bit and write :

Code: Select all

void CTimer::Update()
{
	m_DeltaTime = g_pDevice->getTimer()->getTime() - m_Time;
	m_Time =+ m_DeltaTime;
}
To avoid those nasty floating-point to integer conversion I included a f32 member called m_TimeFactor which is nothing but m_DeltaTime converted into seconds. Keep in mind that the values we used so far are measured in milli-seconds.

Code: Select all

void CTimer::Update()
{
	...

	m_TimeFactor = m_DeltaTime * 0.001f;
}
Let's return to example we had some time ago. Your character intends to move at a speed of 100 units per second. In your movement code you multiply this value with CTimer::m_TimeFactor and the characters movement should be framerate independent.
Serg Nechaeff
Posts: 162
Joined: Wed Nov 26, 2003 5:24 pm
Location: Europe

Post by Serg Nechaeff »

I've tried to implement your code in this class:

Code: Select all

#include <irrlicht.h>
using namespace irr;

#ifndef IRRLICHT_TIMER
#define IRRLICHT_TIMER



class CIrrTimer  {

 public:

	IrrlichtDevice *m_device;

	CIrrTimer() {};
	CIrrTimer(IrrlichtDevice *device) 
	{
		m_device=device; 
		Reset();
	};	

	int m_Time;
	int m_DeltaTime;
	irr::f32 m_TimeFactor;

	void Reset() {m_Time = m_device->getTimer()->getTime();};
	
	void Update()
		{ 
		m_DeltaTime = m_device->getTimer()->getTime() - m_Time; 
		m_Time =+ m_DeltaTime; 
		m_TimeFactor = m_DeltaTime * 0.001f; 
		};

};

#endif
And using it in the game:

Map.ScrollDown(myTimer->m_TimeFactor );
myTimer->Update ();

but each frame the speed is incrasing, I would really appreciate your help!
http://www.javazing.com
P-III-950, WinXP, GeForce FX5600 128 MB ForceWare 52.16, DX9, Eclipse IDE, JRE 1.6
Isometric God
Posts: 69
Joined: Sun Oct 12, 2003 3:42 pm
Location: Germany

Post by Isometric God »

Seems to be perfectly right in my view... are you sure Map.ScrollDown(myTimer->m_TimeFactor ); acts correctly ?
Serg Nechaeff
Posts: 162
Joined: Wed Nov 26, 2003 5:24 pm
Location: Europe

Post by Serg Nechaeff »

Yep, because I used it with the arrow keys fisrt with a constant number like 1, but then switched to the timer stuff, as it is better for the purpose, but unfortunately something is wrong :(
http://www.javazing.com
P-III-950, WinXP, GeForce FX5600 128 MB ForceWare 52.16, DX9, Eclipse IDE, JRE 1.6
fretnoize
Posts: 43
Joined: Sun Feb 01, 2004 4:57 am
Location: Los Angeles

Post by fretnoize »

I'm having some strages results with this too. One of which is when I throw in some code to slow down the framerate to test to make sure I implemented this right, I can see a noticable speed difference of the model i'm moving. I'm jumping from ~700 fps to ~50 fps and I can deffinately see that the model moves slower. I'm just adding a for loop before updating the timer to slow down the frame rate.

I'm having some other wacky results too. However, I based my code off of the previous poster's with a problem (probably not a great idea on my part) but I was hoping he had fixed his problem but never told us how or what it was, perhaps because the problem was elsewhere.

I was wondering if we could get a more in-depth code example which compiles, that would help other newbies like me implement this. Perhaps just a sample where we have a simple testnode box moving and reseting its position (before going to far), along with code that we can comment in and out for frame-rate differences to make sure we have it correctly implemented.
Isometric God
Posts: 69
Joined: Sun Oct 12, 2003 3:42 pm
Location: Germany

Post by Isometric God »

Sorry for that. I could'nt find out whether this is really related to my CTimer class or not. It's working perfect for me. If you find out, I would greatly apreciate if you could inform me about the bug ( is it ? ).
Thank you
fretnoize
Posts: 43
Joined: Sun Feb 01, 2004 4:57 am
Location: Los Angeles

Post by fretnoize »

yeah i'm not sure, i just decided to lock my framerate at 60/sec... which seems to work... besides, i don't think the game really needs to run faster than that anyway ;)
Tels
Posts: 65
Joined: Fri Feb 27, 2004 7:56 pm
Location: Antarctica
Contact:

Post by Tels »

Whilte this tutorial shows how to get framerate-independend motion, it still does not do a fully time-warped clock. Thats mabye why you have problems with that.

I had some code in SDL::App::FPS (which no longer does work with the new SDL release) and took it over to Games::Irrlicht. There it doesn't work yet because there is no functional example to show it working. But basically all the stuff is there for an totally independend game clock.

It works like this:

Cut and paste from SDL::App::FPS's pod because I don't want to write it up again :)

begin:

To effectively decouple animation speed from FPS, get at each frame the
current time, then move all objects (or animation sequences) according to
their speed and display them at the location that matches the time at the
start of the frame. See examples/ for an example on how to do this.

Note that it is better to draw all objects according to the time at the start
of the frame, and not according to the time when you draw a particular object.
Or in other words, treat the time like it is standing still when drawing a
complete frame. Thus each frame becomes a snapshot in time, and you don't get
nasty sideeffects like one object beeing always "behind" the others just
because it get's drawn earlier.

=head2 Time Warp

Now that we have a constant animation speed independend from framerate
or system speed, let's have some fun.

Since all our animation steps are coupled to the current time, we can play
tricks with the current time.

The function L<time_warp> let's you access a time warp factor. The default is
1.0, but you can set it to any value you like. If you set it, for instance to
0.5, the time will pass only half as fast as it used to be. This means
instant slow motion! And when you really based all your animation on the
current time, as you should, then it will really slow down your entire
application to a crawl.

Likewise a time warp of 2 let's the time pass twice as fast. There are
virtually no restrictions to the time warp.

For instance, a time warp greater than one let's the player pass boring
moments in a game, for instance when you need to wait for certain events in
a strategy game, like your factory beeing completed.

=head2 Ramping Time Warp

Now, setting the time war to factor of N is nice, but sometimes you want to
make dramatic effects, like slowly freezing the time into ultra slow motion
or speeding it up again.

For this, L<ramp_time_warp> can be used. You give it a time warp factor you
want to reach, and a time (based on real time, not the warped, but you can
of course change this). Over the course of the time you specified, the time
warp factor will be adapted until it reaches the new value. This means it
is possible to slowly speeding up or down.

You can also check whether the time warp is constant of currently ramping
by using L<time_is_ramping>. When a ramp is in effect, call L<ramp_time_warp>
without arguments to get the current parameters. See below for details.

The example application uses the ramping effect instead instant time warp.

<b>end</b>

As long as you base all your animations etc on the warped clock, you can have slow-motion etc easily. Of course, integrating this with actual physics is a bit harder.

Oh, and my code also has timers that work in the space of the warped clock. This is _very_ important, because if your game time takes 10 seconds to model 2 real-time seconds, you don't want to have a timer firing after 1 seconds but after 5 seconds.

Actually, getting the timers to fire at the right moment is also very tricky, warped time or not. I solved it by having all timers expire at the start of the frame. The also carry an "overdue" value, e.g. if the timer should have fired after 120 ms, but the current frame is drawn at 135 ms (after game-start), then the overdue value is 15 ms. This way you can adjust events in the "timeline". Timers fire in the order they are overdue, e.g. the first timer comes first, the later timer later. This way events can also happen in order they should, even though from a software point of view everyhing happens right at the start of the frame.

For instance, imagine a constant string of objects fired from somewher:Without the overdue some object would be created slightl later thanit should and instead of getting a row of neatly lined up objects you get one with arbitrarily displacements which vary with the FPS. When you take the overdue into effect, you can move the object a bit into the direction it is fired, and the all align neatly.

Of course, with 200 FPS the correction is hardly neccessary, but with 45 FPS and varying frame-times it becomes a must.

The timer-firing-at-start-of-frame has the very nice sideeffect that after you have handled all events/timers, you have a static snapshot of the world you can render without worrying that it is going to change while you render it (as would happen with timers firing while rendering).

I hope this helps,

Tels
Perl + Irrlicht + Audiere = Game: http://bloodgate.com/perl/game
jox
Bug Slayer
Posts: 726
Joined: Thu Apr 22, 2004 6:55 pm
Location: Germany

Post by jox »

Serg Nechaeff's class implementation has one little bug, which is not instantly obvious. It's kinda well camouflaged :)

The Line:

Code: Select all

m_Time =+ m_DeltaTime;
must look like this

Code: Select all

m_Time += m_DeltaTime; 
see the difference? :o
jox
Bug Slayer
Posts: 726
Joined: Thu Apr 22, 2004 6:55 pm
Location: Germany

Post by jox »

Just found that this thingy was already in Isometric God's example code. So shame on him as well (just kidding) :)
bal
Posts: 829
Joined: Fri Jun 18, 2004 5:19 pm
Location: Geluwe, Belgium

Post by bal »

Does it work correct with jox's fix :) ?
General Tools List
General FAQ
System: AMD Barton 2600+, 512MB, 9600XT 256MB, WinXP + FC3
evilmrsock

a little help please

Post by evilmrsock »

I went and copied in the code provided by Serg to try to get this working, and implementation is giving me issues.

Timer::m_TimeFactor

asking for it this way gives me "illegal nreference to data member in a static member function"

Timer->m_TimeFactor

asking this way says gives me "Timer does not have overload operator operand ->"

Can anyone tell me what I'm doing wrong?
jox
Bug Slayer
Posts: 726
Joined: Thu Apr 22, 2004 6:55 pm
Location: Germany

Post by jox »

Maybe make an instance of the class first

Code: Select all

timer = new Timer();
then access the timeFactor

Code: Select all

timer->m_TimeFactor;
hope it helps
Guest

Post by Guest »

Close -

Code: Select all

Timer * timer = new Timer();
did the trick

Thanks for the help!
Guest

Post by Guest »

Okay, I need a little clarification.

This system always outputs a whole number to multiply your movement by. Why? This results in my nodes having constant seisures, shaking violently, because they're jumping from moving at a rate of 3 a frame to a rate of 4 a frame when the framerate changes a small amount.

Now, I know mathmatically why it always puts out a whole number, that's fairly obvious. I mean aesthetically, that's a rather broad range of movement.

Does anybody have a method for getting more precision out of TimeFactor? When your framerate is fluctuating between 180 to 600 every other second, whole number changes just aren't enough.
Post Reply