Godot: Add ability to iterate physics from script or lock physics to the rendered frame rate

Created on 4 Jan 2019  路  13Comments  路  Source: godotengine/godot

To write the smoothest and most responsive games you need to:

  • Update positions of objects every frame for the smoothest movements.
  • Respond to inputs the same frame they are received.
  • Respond to collisions and area overlaps the same frame they occur.

Currently Godot does not meet these requirements where your options are:

  • Move objects in _physics_process which may or may not occur every rendered frame. This is more of an issue now that it's not a given that the player will have a 60Hz refresh rate. This would essentially lock a player with 144Hz monitor to 60Hz, making the game look very choppy since 144 is not divisible by 60.
  • Move objects in _process which would give the smoothest animations possible. However, all collisions and area overlaps would be delayed by at least one frame. On very high refresh rate monitors such as 240Hz with the default physics frame rate of 60 this would result in up to 4 frames before a collision or area overlap is recognized. This would result in a visible lag time or possibly objects moving through each other.

Currently I don't see many functions in the API that can help with this and that's the lower level and cumbersome Shape2D.collide or the even lower level Physics2DDirectSpaceState.collide_shape. However, it's burdensome to call these manually on every object that responds to collisions.

There are two solutions to this:

  • Allow the physics engines to be iterated from scripts. Calling the physics engine via call_deferred from _process on one script, ideally a singleton, would ensure that it's called after everything has moved and report overlaps on the same frame. This would require you to set the physics FPS to 0 since we only want to call it manually.
  • Allow the physics frame rate to be locked to to real frame rate. This mode would iterate the physics engine once per frame regardless of time passed and pass a variable delta to _physics_process, rather than a fixed delta. If everything moves in _fixed_process then it will move smoothly, input could be processed more reliably in _fixed_process and collisions and overlaps would occur on the same frame.

Either of these will negatively impact determinism or outright break it, but in games that only use physics for mainly responding to collisions and overlaps then this is hardly a problem.

archived discussion core

Most helpful comment

I'm actually currently working on fixing this. See my PR #30226 and these issues:

30068

10388

https://www.gamedev.net/blogs/entry/2265460-fixing-your-timestep-and-evaluating-godot/

There can be multiple sources of jitter which act on top of each other, which can make identifying 'one cause' of jitter difficult. Some sources:

  1. the need for movement code / physics to be synchronized with the frame times
  2. variation in the time within a frame that the OS allows the game thread to run
  3. timing information from the OS is only for the submitted frame, whereas the actual time needed is the render time, which as yet is not possible to get

In this issue you are mainly talking about (1). My personal preferred method of dealing with this (and is used in many AAA games now) is fixing your timestep. This involves using the physics / movement at a fixed tick rate (as is done now within Godot). However in order for this to work you also need to perform interpolation at render time. This is already possible to an extent from gdscript, however PR #30226 will allow this to be done more accurately.

To save users the bother of writing interpolation themselves, I have also written a smoothing node:
https://www.youtube.com/watch?v=SFLwCR2KEJ8
However I am currently seeing whether it can be integrated within the spatial node so any derived nodes can use this functionality with minimal user intervention.

As well as fixed timestep interpolation, the two methods in this thread are common alternatives:

  1. stepping physics by frame time
  2. semi-fixed timestep

(1) was probably the first method used to try to deal with varying frame durations. It kind of works, but physics and gameplay can give wildly different results when you drive it at varying deltas.

(2) is a simple way of trying to combine the benefits of fixed timesteps and accurate positions during frames which don't match up. You step the timestep at regular intervals, then add a 'mini step' to make up the difference to get to the exact frame time.

And then there is fixed timestep with interpolation. This is slightly more complex to implement, but provides the best visual quality with completely stable physics / gameplay, which is why it is in many cases now the de facto choice.

There are other trade offs too. Consider that the timing of the timesteps can also affect things like input. So some would argue the use of semi-fixed over fixed in the case of fast twitch games.

Another thing to bear in mind is multiplayer, where you might have one server and several clients. Here fixed timestep makes total sense, with the server running fixed timestep and the clients interpolating. Usually multiplayer interpolation has to be more advanced because it has to take account of things like lost packets, variation in timing of packet arrival etc.

Anyway hopefully that should help.. As I say I am currently working to get fixed timestep working. I can also have a look afterwards and see if it is possible to switch stepping mode for Godot, maybe have a selection between fixed timestep, semi fixed and frame based stepping. As far as the physics stepping in the Godot timing code I've examined so far I'm hoping this will be straightforward, although there may be unforeseen interdependencies (for example if there are multithreading issues).

_BTW, @viktor-ferenczi if you want to try adding semi-fixed go for it, it should (hopefully) be quite simple, however you will need to maintain the old method and have the method switchable in project settings, with fixed timestep as default so as not to break existing games. You may need to examine / replace the jitter fix. Actually I think once these other methods are working, there is a good argument for removing the jitter fix (if it does what I think it does). If not as I say I will get to it soon._

All 13 comments

i remember seeing a suggestion about changing the physics frame rate to the monitor's refresh rate. if the developer multiplies everything by a delta, they should be fine on all monitor rates, right?

however, is that how it should be done? i mean, the physics loop will be 144 times per second compared to just 60. does it effect collisions and create noticable delay that much to be worth it?

cause i'm on a 144hz monitor, and use move_and_slide in _process for player movement.
projectile collisions and stuff all run at the default physics rate of 60/s and it seems to work fine. no delay with collisions at all. from what i've experienced anyway...

Setting the physics FPS to the refresh rate could work, there's just no easy way to do it right now. #21486 makes a good point, if we could get the refresh rate and vsync is enabled, we could just set the physics FPS to match refresh rate. Right now we can do Engine.iterations_per_second = Engine.get_frames_per_second() but this depends on what the game is doing at the moment. You'd have to do it a few seconds into the title screen, that's the earliest time you can get an accurate reading from get_frames_per_second. But that's error prone, if the player alt tabs or moves the window or something you'll get a very wrong value.

Using this script I am seeing a very small number of "bad frames" popping up. Occasionally you'll get a delta that's just barely too short (0.15 instead of 0.1666), there are the expected multiple calls on frames with a higher delta, and confusingly there was a negative delta (I'm not even sure how a negative delta can happen). However, the frames with 0 physics frames because the delta is barely too short are the problem, setting the physics FPS to the refresh rate doesn't necessarily guarantee that there will be a physics frame every rendered frame. However, it almost guarantees that overlaps are processed at most one frame later.

So as the engine is today, I think the best way would be to set physics FPS to refresh rate, move in process and just deal with the 1 frame delay. At least then it's almost always 1 frame and rarely more. Or set it to something higher as long as the overhead is not too much.

extends Node

var physics_frames_this_frame = 0
var physics_fps_set = false

func _physics_process(delta):
    physics_frames_this_frame += 1

func _process(delta):
    if physics_fps_set and physics_frames_this_frame != 1:
        print("bad frame " + str(physics_frames_this_frame) + " " + str(delta))
    physics_frames_this_frame = 0

func _on_Timer_timeout():
    Engine.iterations_per_second = Engine.get_frames_per_second()
    print(Engine.get_frames_per_second())
    physics_fps_set = true

AFAIK physics processing happens _at least_ once per frame. Correct me if I'm wrong.

The number of physics iterations is based on the amount of time since last frame. If only 1/144th of a second has passed since the last physics iteration and the physics FPS is on 60 then there will be no physics frames that frame.

@timoschwarzer from what i found, each frame for physics is default at 1/60 (0.0167). you can confirm this by using movement code in _physics_process and set your monitor to 144hz or 75hz, or whatever. there will be extreme jitter (not updating fast enough to correspond to the monitor refresh rate). but once movement code goes in _process, it's very smooth.

this is why it's very important not to use movement code inside physics_process. i don't know how many times i've downloaded a game from facebook, itch, etc and the screen tearing is so overbearing it makes me want to stop playing. the dev is basically limiting their game to 60hz monitors. which IMO is not a good idea. even though 60hz monitors are still popular, there are tons of players who use 75hz and above.

these findings are for 2d only, not sure about 3d

I wonder if physics/common/physics_jitter_fix is just broken/not working. Because it should prevent what you are experiencing.

Fix to improve physics jitter, specially on monitors where refresh rate is different than physics FPS

Same jitter problem with Godot 3.1.1 while running a very simple 2D game prototype with a fast moving rigid body character.

Problem happens due to slow hardware rendering the game at 43-45 fps (varying) while Physics is set to fixed 60Hz. I tried to increase physics rate, which reduces the jitter a bit, but it is still very real.

I tried to change physics_jitter_fix to 1.0 from the default 0.5, but it did not help either. Is that fix suitable only for the case when rendering fps is higher than physics rate?

Is there a way to force exactly one physics evaluation per frame rendered, using the same delta for both?

@girng

this is why it's very important not to use movement code inside physics_process.

I also have my movement code in physics_process, because it calls apply_impulse to add a user controlled thrust vector. Moving user input handling into process does not eliminate the jitter caused by the varying number of physics frames evaluated per rendered frame. Reason is that the character's position is changed by physics due to its velocity.

Only solution seems to be running a fixed number of physics steps per frame rendered. For example if delta < 1.0/60, then run 1 physics frame with the same delta. If delta is higher, then split the delta to be less than 1.0/60 and run multiple steps, so physics can remain precise even if the rendering frame rate temporarily drops.

Is there a way to override the scheduling of physics processing in GDScript, like manually invoking physics processing with specific delta?

Integrated the deltas of both the physics and render process functions:

extends RigidBody2D

var physics_time := 0.0
var render_time := 0.0

func _physics_process(delta):
    physics_time += delta
    add_torque((PI - angular_velocity) * 50)

func _process(delta):
    render_time += delta
    var time_diff := render_time - physics_time
    print("time difference %8.3fms" % (time_diff * 1000))

The script is on a RigidBody with a Line2D visual, the added torque force spins it up and stabilizies at a near constant rotation speed. The test runs at 60fps with VSync ON on a 60Hz display. Physics rate is left at the default 60Hz.

Test project ZIP

Screen recordings:

Notice when no physics step is happening between two rendered frames. That appears as jitter/judder when the rod stops momentarily, then catches up (appears to jump back and forth).

When the line is rotating smoothly the printout shows a constant difference between the physics and render timestamps:

time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
time difference   11.451ms
Game FPS: 60

When the rotating line has a visible jitter, then the timestamps slip and physics jumps over frame rendering, causing the visible artifact:

time difference   12.947ms
time difference   12.947ms
time difference   12.947ms
time difference   12.947ms
time difference   12.947ms
time difference   12.947ms
time difference   12.947ms
time difference   12.947ms
time difference   12.947ms
time difference   12.947ms
time difference   12.947ms
time difference   12.947ms
time difference    0.000ms
time difference    1.389ms
time difference    2.778ms
time difference    4.167ms
time difference    5.556ms
time difference    6.944ms
time difference    7.750ms
time difference    7.750ms
time difference    7.750ms
time difference    7.884ms
time difference    9.273ms
time difference    9.273ms
time difference    9.273ms
time difference    9.273ms
time difference    9.273ms
time difference    9.273ms
time difference    9.273ms
time difference    9.273ms
time difference    9.273ms
time difference    9.273ms
time difference    9.273ms
time difference    9.273ms
time difference    9.273ms
time difference    9.376ms
time difference   10.271ms
time difference   10.271ms
time difference   10.271ms
time difference   11.312ms
time difference   11.312ms
time difference   11.312ms
time difference   11.312ms
time difference   11.312ms
time difference   11.312ms
time difference   11.312ms
time difference   11.312ms
time difference   11.312ms
time difference   11.312ms
time difference   11.312ms
time difference   11.312ms
time difference   11.312ms
time difference   12.701ms
time difference   12.701ms
time difference   12.701ms
time difference   12.701ms
time difference   12.701ms
time difference   12.701ms
time difference   12.701ms
Game FPS: 59

Currently it is impossible to make smooth physics based movement even with both the rendering and physics rates fixed and having a very basic (fast to render) scene.

I suggest refactoring the Main::iteration method in main/main.cpp, so it can be maintained.

In order to fix the visible jitter/judder issue physics needs to be invoked one or more times with appropriate deltas before rendering of each frame. Physics timestamps needs to catch up exactly to the rendering timestamp in order to avoid jitter.

Doing so would guarantee that there is no drift between physics and rendering regardless of the current rendering frame rate or intermittent slowdowns due to other concurrent processing, OS background tasks, CPU throttling, etc.

# On starting scene
physics_time := 0.0
render_time := 0.0

# Before calling _process for each frame rendered
physics_period := 1.0 / physics_fps
while physics_time < render_time:
    drift := render_time - physics_time
    delta := min(physics_period, drift)
    invoke_physics_processing(delta)
    physics_time += delta

# After rendering frame
render_time += render_delta
# (it could just assing current monotonic timestamps, e.g. wall clock time)

I'm actually currently working on fixing this. See my PR #30226 and these issues:

30068

10388

https://www.gamedev.net/blogs/entry/2265460-fixing-your-timestep-and-evaluating-godot/

There can be multiple sources of jitter which act on top of each other, which can make identifying 'one cause' of jitter difficult. Some sources:

  1. the need for movement code / physics to be synchronized with the frame times
  2. variation in the time within a frame that the OS allows the game thread to run
  3. timing information from the OS is only for the submitted frame, whereas the actual time needed is the render time, which as yet is not possible to get

In this issue you are mainly talking about (1). My personal preferred method of dealing with this (and is used in many AAA games now) is fixing your timestep. This involves using the physics / movement at a fixed tick rate (as is done now within Godot). However in order for this to work you also need to perform interpolation at render time. This is already possible to an extent from gdscript, however PR #30226 will allow this to be done more accurately.

To save users the bother of writing interpolation themselves, I have also written a smoothing node:
https://www.youtube.com/watch?v=SFLwCR2KEJ8
However I am currently seeing whether it can be integrated within the spatial node so any derived nodes can use this functionality with minimal user intervention.

As well as fixed timestep interpolation, the two methods in this thread are common alternatives:

  1. stepping physics by frame time
  2. semi-fixed timestep

(1) was probably the first method used to try to deal with varying frame durations. It kind of works, but physics and gameplay can give wildly different results when you drive it at varying deltas.

(2) is a simple way of trying to combine the benefits of fixed timesteps and accurate positions during frames which don't match up. You step the timestep at regular intervals, then add a 'mini step' to make up the difference to get to the exact frame time.

And then there is fixed timestep with interpolation. This is slightly more complex to implement, but provides the best visual quality with completely stable physics / gameplay, which is why it is in many cases now the de facto choice.

There are other trade offs too. Consider that the timing of the timesteps can also affect things like input. So some would argue the use of semi-fixed over fixed in the case of fast twitch games.

Another thing to bear in mind is multiplayer, where you might have one server and several clients. Here fixed timestep makes total sense, with the server running fixed timestep and the clients interpolating. Usually multiplayer interpolation has to be more advanced because it has to take account of things like lost packets, variation in timing of packet arrival etc.

Anyway hopefully that should help.. As I say I am currently working to get fixed timestep working. I can also have a look afterwards and see if it is possible to switch stepping mode for Godot, maybe have a selection between fixed timestep, semi fixed and frame based stepping. As far as the physics stepping in the Godot timing code I've examined so far I'm hoping this will be straightforward, although there may be unforeseen interdependencies (for example if there are multithreading issues).

_BTW, @viktor-ferenczi if you want to try adding semi-fixed go for it, it should (hopefully) be quite simple, however you will need to maintain the old method and have the method switchable in project settings, with fixed timestep as default so as not to break existing games. You may need to examine / replace the jitter fix. Actually I think once these other methods are working, there is a good argument for removing the jitter fix (if it does what I think it does). If not as I say I will get to it soon._

@lawnjelly Thank you for your detailed post and all the hard work. Currently I'm working on a game prototype, which shows severe jitter on my notebook, while works perfectly on stronger desktop PC. That's how I noticed the problem. I'm going to test your branch with the prototype. (Since I've just started to dig into the relevant part of the Godot code base I cannot promise to add semi-fixed quickly.)

Just a heads up on this (and to @viktor-ferenczi ), I've refactored the main_timer_sync to make it a bit more sensible, easier to understand and expandable. I've now added a choice of timestep for physics:

  1. Fixed timestep
  2. Semifixed
  3. Frame based delta
  4. Fixed timestep with the old jitter fix

I've also added a rudimentary delta smoothing system to deal with timing variations from the OS. The fixed timestep will work best in conjunction with 2 new smoothing (interpolation) nodes, one for 3d and one for 2d. I was intending to have these available for testing already as a module, but this is waiting on the interpolation fraction PR getting merged.

It will probably take me another week or two to iron out any kinks before submitting the timestep stuff as a PR. It shouldn't break anything existing, just will add the new methods in the godot settings pages, defaulting to the old fixed timestep with jitter fix.

Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine.

The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker.

Thanks in advance!

Was this page helpful?
0 / 5 - 0 ratings