Decoupling Frame rate from movement speed issues

Hi,

In order to ensure that players with a higher framerate don’t actually move faster I want to incorporate deltatime into my main game loop. However, several issues have arisen when trying to implement this in babylon.The following examples are based on the answers given in: fps-affects-game-speed-framerate-independence (on this forum).

First, when I just update the position with engine.getDeltaTime() * speed, there is a very noticeable jitter:
https://playground.babylonjs.com/#J6U7V4
This is actually to be expected because the deltatime varies quite a bit, leading to erradic movement.

So, I’m trying to use the basic method described in the blog ‘detailed-explanation-javascript-game-loops-and-timing by Isaac Sukin’ to solve this erradic movement but here I am running into a roadblock:
https://playground.babylonjs.com/#YAPHY6

The movement is still jittery on a 60 hz monitor and extremely jittery on a 144 hz monitor. I understand that this might be because there is still a little bit of time left over in the delta variable after updating the player’s position. The article describes a solution for this:

Passing the percent of a timestep that hasn’t been simulated yet to the draw() function allows it to interpolate between frames. This visual smoothing helps reduce stuttering even at high frame rates.

But I don’t believe there is support for this in babylon.

Extra backstory in short:
It’s for a basic multiplayer game. I’m trying to ensure that all players move at the same speed + ensure that the client simulates movement at the same speed as the server which is currently doing it at 60 updates per second.

Edit: I had to reference some links by title and author because I’m only allowed to post 2 links.

Thank alot in advance!

Hello, and welcome!!!

the tool you should use for your step is scene.getAnimationRatio() which takes the FPS in account and is here to compensate. It will return 1 is you are rendering at 60fps and will adapt if not

1 Like

Hi, thanks for the warm welcome!

I googled and searched around a bit to make sure I understand scene.getAnimationRatio(). It appears to be defined as: engine.getDeltaTime() / (1000/60) which is the same value that I am using in my examples.

Just to be sure, here’s a playground using getAnimationRatio:
https://playground.babylonjs.com/#7SJJ0K

The movement still appears to be very jittery. When I lower the speed, the jitter is less (obviously) but if I lower the size of the sphere to compensate it is still as noticeable as before.

ok so I misread your question :slight_smile: The animation sync will make sure that the distance covered during 1s will be the same no matter what the fps is

But if you are looking for smoothing the rendering, then you need to increase the FPS unfortunately. (Unless I misunderstood again?)

You’re correct in that the distance covered in 1s is the same for players with different fps now.

The issue is that engine.getDeltaTime() and by extension scene.getAnimationRatio() vary quite a bit from frame to frame, which causes the sphere to jitter around a lot (erradic movement). The scene is running at 144 fps without any issues so I don’t think increasing the fps any further would help.

Edit:
Here’s a snippet with the value of getAnimationRatio for 5 frames at 60 fps:

1.256699999794364
0.8348999940790236
0.8229000004939735
1.251000005286187
0.8351999963633716

You can see that increasing position using this value causes some jitter. Which was what lead me to the blog by Isaac Sukin to even out this value. But his solution still contains jitter in babylonjs as can be seen in the second link on my initial post.

Maybe you can use a average animation ratio? Something smoothed over a second? So really less erratic.

something like that Babylon.js/performanceMonitor.ts at master · BabylonJS/Babylon.js · GitHub

2 Likes

That actually seems to work quite well, thanks alot!

For people who stumble upon this thread, here’s a playground without jitter by using the RollingAverage class:
https://playground.babylonjs.com/#9W2NDV

2 Likes

Just wanted to say I’m stealing this :wink: This is a great idea for lessening the impact of frame drops. In our game we sometimes have unavoidable one-off frame drops due to network delays (client side prediction is an option we are keeping open from a tech standpoint, but it is not very suitable for a number of reasons), and this is perfect for smoothing things over.

Actually in the 2D world this was a commonplace thing back when I started out doing stuff in SDL and C++ around 2009. But somehow we all forgot about it…

I want to add that I’m not sure if this solution wont cause problems when the physics updates get more complex. A lot of resources indicate that most physics engines become unreliable if they don’t get constant timestep updates.

By using rollingAverage were smoothing out the local variance in deltatime but were not quite at the fixed deltatime for physics updates. What happens when the user tabs out/freezes for a while and then comes back with a large value for deltatime. RollingAverage wont smooth that out and the physics engine now has to deal with that large value aswell.

The default solution used by most frameworks (blog) is to divide large deltatimes into multiple updates:

After freezing for one second (deltatime = 1000), before drawing the new frame the engine first does 60 updates with a deltatime of 1000/60.

Currently with rollingAverage:

After freezing for one second (deltatime = 1000), before drawing the new frame the engine does 1 update with a deltatime of avg([16, 17, 15, 14, 16, 1000]).

The issue I’m having with Babylonjs and the default solution is that it will jitter, most likely because if the deltatime is 1015 ms, it will do 60 updates to similate the 1000 ms but it wont simulate the remaining 15 ms until the next frame update. In the default usecase (deltatime ~16 ms):

20.9449999966 ms - (1000/60) ms = 4.27833332993 ms
13.9149999013 ms - (1000/60) ms = -2.75166676537 ms
13.7150000082 ms - (1000/60) ms = -2.95166665847 ms
20.8500000881 ms - (1000/60) ms = 4.18333342143 ms
13.9199999394 ms - (1000/60) ms = -2.74666672727 ms

You can see that it is now the variance in the remaining deltatime that is causing the jittery movement. Because sometimes you move the player for 16 ms when 21 ms have passed, then you move him for 16 ms while 23 ms have passed, until you’ve gathered enough remainder to move the player for 2*16 ms while 20 ms have passed since the previous frame.