Page 1 of 1

Frame skipping issue when using dt

Posted: Mon Apr 28, 2014 4:08 am
by tehryanx
I finished my Ludum Dare submission tonight and posted it without realizing there was a nasty bug in the code.

https://www.dropbox.com/s/qh9gq8b4kc4n63h/ld29.love

The code is pretty messy but the problem lies in charge.lua

Code: Select all

function charge:update(dt)
	self.x = self.x + self.xVel * self.direction * dt
	self.y = self.y + self.yVel * dt
	if self.xVel >= 0 then self.xVel = self.xVel - self.friction * dt end
	self.yVel = self.yVel + self.gravity * dt
	if self.y > waterSurface then self.yVel = self.yVel * 0.96 end
end
Essentially I'm throwing bombs up into the air, and they fall into the water. When they hit hte water their vertical velocity should decrease. That last line accomplishes that but I forgot to adjust it for deltatime. On my machine 0.96 is perfect. When adjusting it for delta time I got up around 200 * dt instead of 0.96:

Code: Select all

	if self.y > waterSurface then self.yVel = self.yVel * 200 * dt end
and that seems to be about the right speed, the problem is that now it doesn't fall at a constant speed. It speeds up when other bombs explode and I can't find any correlation no matter how hard I try. Is this a common problem? Is there a reason that as soon as I add delta time to the equation the movement speed becomes varied?

Thanks

Re: Frame skipping issue when using dt

Posted: Mon Apr 28, 2014 4:21 am
by micha
If you multiply a value onto the velocity every frame, then this is an exponential decay. That means that dt has to go into it exponentially, not multiplicatively. Try this:

Code: Select all

self.yVel = self.yVel * 0.96^(60*dt)
The 60 comes in, because the "normal" dt is 1/60 and in this case, the exponent should be one.

Re: Frame skipping issue when using dt

Posted: Fri May 02, 2014 3:43 am
by Blinkozo
Wanted to poke my head in and say: dt changes!

It could be 1/120 second, it could be 1/2 a second. This is why you should NOT simulate physics based on an arbitrary, unknowable dt. Physics sims almost always reference their previously calculated state (e.g. self.yVel = self.yVel * decay), and with an unknowable dt, you will never be able to predict how many times yVel = yVel*decay gets called per second. Thus, you’ll always end up with weird results.

My guess is that other bombs explode, chewing up processor time, so you get a greater dt next frame (it took longer), and thus your velocity goes large. Even if you use micha’s exponential solution, the update() loop might not change yVel enough times, or too much, depending on how many times update() is called per second. And you’re back to where you started: an unknowable dt that will accelerate your bombs at whatever speed it likes.

Try bracketing all your update() stuff in a knowable, constant time step:

Code: Select all

timestep = 1.0/100.0 -- 100 Hz, change to your liking
timeBucket = 0.0 -- for storing leftover time that's too small to simulate yet
function love.update(dt)
	timeBucket = timeBucket + dt
	while timeBucket >= timestep do
		charge:update(timestep) -- example, but all updates should be handled here...
		timeBucket = timeBucket - timestep
	end
end
This will simulate on every computer exactly the same (except for some minor floating point differences), run at a higher time resolution than the screen (better collisions), and won’t cause weird glitches. Plus, if you ever network your code, this will save you a LOT of headache.

The drawback is that you will update() more often than draw(), and if you made an assumption that update is called only once per draw(), you're in for trouble. Also, if the target computer is particularly slow, high Hertz could slow things WAY down (unlikely, but possible).

Cheers.

Re: Frame skipping issue when using dt

Posted: Fri May 02, 2014 6:17 am
by micha
Hi Blinkozo and welcome to our forum.

While I agree that your solution is beneficial in some situations (deterministic physical behavior), I disagree with some of your points:
Blinkozo wrote:Physics sims almost always reference their previously calculated state (e.g. self.yVel = self.yVel * decay), and with an unknowable dt, you will never be able to predict how many times yVel = yVel*decay gets called per second. Thus, you’ll always end up with weird results.
It is true, that you don't know beforehand, how often the update is called per second. But that is the whole point of using dt. For linear movement, you multiply each change in time by dt, which leads to the same result after one second, regardless how often the update is performed. The same holds for the exponential decay together with the code I wrote above.
Blinkozo wrote: Even if you use micha’s exponential solution, the update() loop might not change yVel enough times, or too much, depending on how many times update() is called per second. And you’re back to where you started: an unknowable dt that will accelerate your bombs at whatever speed it likes.
Because of what I said above, it does not matter, how many times the update is called. The speed after one second will be the same.

The weird glitches that one gets with variable time steps only appear if dt is used incorrectly.

And I want to point out two more drawbacks of using a constant time step:
First, one can possibly get time-aliasing. Even, if the draw is called at a constant rate, the number of updates might vary from draw to draw (this is the case, if the update rate is not an integer multiple of the draw rate). I don't know if this is a severe problem, as I haven't tried myself.
Second - you pointed into this direction already - if the calculations in the update take more time than the prescribed (fixed) timestep then you enter a spiral of growing time steps and then between each draw, more and more updates are performed. The update cannot catch up with the growing amount of time in the timeBucket.

Besides these drawbacks, I agree, that for deterministic physics, a constant time step is the best solution.