delta time alternative

Started by hosch, August 02, 2021, 13:01:11

Previous topic - Next topic

hosch

Hello,

I recently published an App on Android made in AGK (which is not specifically causing the problem). I tied the movement of the enemies to delta time and it works consistent and like a charm across all devices. However I got some reports by players that the movement of the main player is perceived as jittery and unpleasant. This happened with the newest update where I tied the player movement to delta time as well (before that I just incremented it gradually, which was dumb  :))).

I looked into it a little further and even a short lasting difference of 2-3 FPS (which can easily happen on mobile while a particle effect plays etc.) indeed makes the player movement look completly off. I think it has something to do with the player going in circles around a planet and the slightest stutter is very noticable.

Anyways, this is the current code for the player movement
function RotatePlayer()
local x as float
local y as float

local angle as float

angle = GetSpriteAngle(spr_player) + ((playerSpeed * playerDir) * deltaFrame)
SetSpriteAngle(spr_player,angle)

  x = resX / 2.0 + ((levelRadius) * cos(angle))
  y = resY / 2.0 + ((levelRadius) * sin(angle))
  SetSpritePositionByOffset(spr_player,x,y)
endfunction


Nothing fancy here. If I ditch the deltaFrame portion, the movement looks silky smooth again. Are there other methods of controlling the movement more consistently and frame independent I didn't think of? Thanks so much for your input.

Steve Elliott

You didn't supply the code for deltaFrame (which could be the problem).
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

hosch

#2
Of course, sorry. This gets called every frame

function GetDeltaTime()
    thisFrame = Timer()
    deltaFrame = thisFrame - lastFrame
    lastFrame = thisFrame
endfunction


And lastFrame is set on the game round start to the current milliseconds. I am fairly certain that it sticks out that much, because the slightest FPS difference breaks the illusion of a smooth circle. That's why it is not noticable at all for the enemies, they simply move towards the player in a straight line.

Steve Elliott

You can't just use a timer and not dampen any spikes in frame time - that will be jerky.

This is my code for delta-time:


// init delta time

Type Delta

prev_time   As Float
time_frame  As Float
update_time As Float
req_fps     As Float
dt          As Float
prev_dt     As Float
min         As Float
max         As Float

EndType

Global delta As Delta
ResetTimer()

delta.req_fps      = 60.0
delta.update_time  = 1000.0 / delta.req_fps
delta.min          = 0.005
delta.max          = 5.0
delta.prev_time    = Timer()
delta.time_frame   = 1.0 / delta.update_time
delta.dt           = 1.0 / delta.update_time
delta.prev_dt      = 1.0 / delta.update_time

// delta time function

Function get_delta()

delta.time_frame = ( Timer() - delta.prev_time ) * 1000.0
delta.prev_time  = Timer()

delta.prev_dt    = delta.dt
delta.dt         = delta.time_frame / delta.update_time

// limit increase from frame to frame

If( delta.dt > (delta.prev_dt * 2.0) )

delta.dt = delta.prev_dt * 2.0

ElseIf( delta.dt < (delta.prev_dt / 2.0) )

delta.dt = delta.prev_dt / 2.0

Endif

// keep frameskip sensible and avoid delta becoming too small

if( delta.dt > delta.max )

delta.dt = delta.max

Elseif( delta.dt < delta.min )

delta.dt = delta.min

Endif

EndFunction delta.dt



// update with delta eg)

Function update_enemy( dt As Float )

        enemy.speed = 2.0
enemy.x = enemy.x + ( enemy.speed * dt )

...

Endfunction


// main loop

dt As Float

Repeat
dt = get_delta()

        update_enemy( dt )

Until game_mode = QUIT
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

hosch

Ooph, yeah I've implemented your code and it is very smooth again  :o Am I allowed to use this for my project?

May I ask what you are doing in the ResetTimer() function? How did you come up with the value of 2.0?

Steve Elliott

Quote
Ooph, yeah I've implemented your code and it is very smooth again  :o Am I allowed to use this for my project?

Great to hear  :D  Yes, use it in your project.

Quote
May I ask what you are doing in the ResetTimer() function? How did you come up with the value of 2.0?

It's always best to reset the timer before you begin your timing - just think of it as an initialization step.  I'm not allowing the current delta to be any more than twice the size of the previous delta - or half the speed (divide by 2.0).  Letting the previous frame and current frame time jump or fall dramatically is how you get jerky movement - this smooths transitions from frame to frame.  2.0 seemed like a good choice, I found any less and you get slowdown, to high a figure and that would allow too much variation (and so would be jerky).
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

hosch

#6
Thank you for providing your example and for letting me use it! And for your explanation how you determined the smoothing value.

Unfortunately it was not the right solution for lower end devices the problem still exists there. I have tested it on 3 phones, 2 worked flawlessly, but the one on the lower end still had jittery movement (still a lot smoother with your delta time code than mine). I guess we humans are too good in spotting differences when we expect a smooth circle. It was really prominent and stuck out like a sore thumb. I have no clue how to solve it for those phones.

I did not expect to run into those troubles for a simple game  :))

Could the solution be something like a stopwatch? It takes x milliseconds for a full circle at base speed. It takes less milliseconds with higher speed. Advance by that amount each x milliseconds. Or am I just describing delta time here?  ???

Steve Elliott

#7
You're welcome.   :)

Quote
I have tested it on 3 phones, 2 worked flawlessly, but the one on the lower end still had jittery movement (still a lot smoother with your delta time code than mine).

2 out of 3 ain't bad!  ;D

Well if the hardware is incapable of running your code you will need to either optimize it or set a 'minimum hardware spec'.  Remember AGK is not particular fast on low-end hardware such as phones because it's not compiled to machine code.  My code will smooth the frame time out but it will begin to frameskip (to keep the speed of movement the same on all systems, which will mean less smoothness).  Without frameskip and delta time your game will play completely differently on every system.
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

hosch

#8
I understand. The only problem is that it became very noticable once I went for delta time, again I contribute this to human behaviour. It became unsatisfying to look at and I agree. The lower end phone runs this at about 55-56 FPS, which is good, but even if there is a slightest FPS drop it becomes instantly noticable. So I need to cheat somehow. When I just incremented the angle by the player speed each frame (which of course makes the game run slightly faster or slower, depending on the device) it looked silky smooth. That's why I want and need to find a more stable way of controlling this. A millisecond is a millisecond (well, roughly at least  :))) and not as instable as FPS.

I honestly see no way to improve the framerate (my code won't be perfect that's for sure). The mx and my values for the enemies are precalculated so I don't do this every frame. There are only 8 enemies or so on the screen at any given time. I disabled collision, same framerate. Other than that there is not much going on. It is a simple game. At the end of the day, I think improving the delta time with your code might be the only thing possible, though.

Steve Elliott

#9
What you could try is to lower the required framerate in my code:


So instead of requesting 60 FPS with:

delta.req_fps      = 60.0

You could try:

delta.req_fps      = 30.0

You would need to double your game objects speed then.
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

hosch

#10
I tried it and it helped a bit. With everything going a bit slower I might have found some room for improvement. I noticed there was a slight lag when I started scaling up the enemies (they have a scale of 0 and pop up to a scale of 1). I will remove this line to see if I get some performance out of it. Might be the scaling that is too taxing for slower devices.

I am super grateful for your help!

EDIT:
Yes, it seems to be the scaling part takes up a lot of performance. I might need to convert it into a sprite animation to gain some frames.

Steve Elliott

Yes things will now run half the speed (so you will need to up the speed you give game objects).  But you will give slower devices a chance to make the 30FPS target.

Experiment, it's always a compromise if you really want your game to run smoother on low end devices.

You're welcome.
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

Derron

If you want it "smooth" you can do something ... more complex: delta time PLUS tweening.

Assume you split physics and rendering (so movement / logic is done in a different function than rendering).
When rendering you can always exactly know how many time has left since LAST render.
This time can vary here and there. This "timeSinceLastRender" can be divided by a "desiredTimePerRender". You get a value which expresses how far you progressed already between last render and "regular next render".
0.5 means you are rendering a bit earlier than expected (half way)
1.5 means you had a spike and it took a bit longer than expected.

Then for eg your ship you store "lastX, lastY, x, y". And instead of rendering things right at "x,y" you render them at a vector from "lastX, lastY" to "x,y" (so render at "lastX + tweenValue*(x-lastX)" and similar for y).

Tweening > 1.0 means you need to extrapolate (eg you could draw the ship a bit after the "target" ... it is up to you if this is desired).



Next to tweening you can always do a "catch up" approach. you store the actual ship position, but also a "lastRenderX, lastRenderY". If on "regular render intervals" the movement would be 30 px/s you would move a bit more - like as if the ship was flying "full speed" but deaccelerating a bit (until it reaches the normal 30px/s again). The amount of initial acceleration is up to you. It will in worst case look like the ship was attached to a rubber rope which was extended to its maximum and then "let loose".
The lower the render/fps spike was, the smoother it will look. The higher the more apparent this "rubber-snap" effect will be.

Instead of this deaccelerating effect you can of course also "lineary" process your speedups. The idea is the same: your physics still update constantly - and the renderer "smooths" stuff out.  It catches up - moving a bit faster for a while, or moving while the ship already ("physically" - in the updates()) reached it.
Similar stuff is done for online games - which is why there is no "instant hit" firespell but always a bit of an animation played first (to hide the lag / confirmation delay). In older RTS games games which did run in "lockstep mode" often had these rapdily moving elements then too - after a lag.


Cannot give you exact code - just see it as an idea to think about.


bye
Ron

Ashmoor

Why is it important that the game runs at the same speed on all devices? As  Derron says you should decouple logic from drawing and add tweening and allow your engine to work at different speeds.  This article explains it best: https://gafferongames.com/post/fix_your_timestep/

The main idea is that you set a desired logic rate (UPS - updates per second) and a draw rate (FPS). You update as much as you need, do spike suppression and when you draw you apply a linear interpolation between last pos and current pos (current pos is usually ahead of the drawing pos on the timeline and that is why you can lerp). In this way you can drop both your UPS and FPS on slower devices and it should still feel smooth. In some extreme cases you may drop UPS to ~20-24 and keep your FPS higher. It will look smooth but with a little delay in responsiveness.


Steve Elliott

#14
Quote
Why is it important that the game runs at the same speed on all devices?

I said "without frameskip and delta time your game will play completely differently on every system."  So you will get a different experience depending on your device capabilities.  This is what was requested - a better (smoother) version of delta time or similar solution.

Quite frankly hosch is struggling to understand delta time specifics (ie some explained code) and in my experience Tweening can be problematic to get right for all situations (such as minus figures and big position changes if the game object goes right off-screen and slides back in from the left).  Making vague and confusing statements from others doesn't really help.
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb