When and when NOT to use delta in _process and _physics_process functions

TLDR:

  • Use delta ONLY in _process, DO NOT use it in _physics_process.
  • There are exceptions, especially if you change the Physics Ticks per Second or do a lot of operations in the _physics_process.
  • Some built-in functions (like move_and_collide, move_and_slide) already factor in time delta and will misbehave if delta is factored in.

There was another question about this subject, opened by user paxer, which was sadly closed a bit too prematurely and the answer remains a incomplete. Sadly, due to the complexity of the topic there is no simple rule on whether or not to use delta that’s always correct.

Here’s how things actually go about in Godot. ALL value iterations (like movement processing) done in both _physics_process and _process functions are framerate dependent. The more frames per second, the more often that calculation will take place in a given length of time.
The _physics_process function is run on a separate thread on the CPU, apart from the other game code. This ensures that slow operations, like loading large amounts of assets, do not interfere with physics calculations. This separate process is run at a capped framerate which is set in the projects physics settings (Physics → Common → Physics Ticks per Second), at 60 FPS by default.
This means that in most use cases running code in the _physics_process will ensure execution at a constant rate.

However, there are some important caveats to this. For one, the execution is not guaranteed to be at a constant rate - if too many operations take place in a single physics step it will slow down, just like the regular _process function. Additionally, if for some reason the user changes the Physics Ticks per Second, for example to increase precision at high speeds by raising it, calculations that don’t factor in the time delta will be executed more often resulting in different results over time. Some built-in functions like move_and_collide(), move_and_slide() do already factor in time delta to alleviate this issue.

Factoring in (multiplying by) the time delta in both the _process and _physics_process functions will thus ensure constant rates of change over time, and is generally good practice. However, it is important to know that while this is technically also true in the _physics_process function, in most use cases factoring in delta is not necessary here and may, in fact, result in unexpected behavior when used in conjunction with some built in functions. Thus it is usually better not to use delta time in the _physics_process function.

5 Likes

Thanks for the explanations. Isn’t this a matter of what I’m changing in physics_process? As far as I understand move_and* are using the velocity, and if the built-in functions already factor in delta than for velocity delta should never be used. However, for changes to global_position I’m not seeing how the built in functions could factor in delta. So when changing the global_position it seems to me that using delta is always the right way.

All the move_and_slide/move_and_collide functions also update the global_position when they’re done. So factoring in delta on operations to the position would potentially introduce a doubling of delta lag compensation.
The more practical reason on why not to use delta when updating the global_position in the physics_process is, though, that _physics_process usually runs on a fixed timeframe. Factoring in delta here just adds an additional operation here, which always has a constant result.
Think of it this way, you can either say your speed is 50, or you can say your speed is (50*60)/60. The result stays the same, one of them is just more effort to calculate and is obscure to read.

Of course they update the position, but they cannot take into account the delta for changes to the position that happened outside of move_and_*.

I did a quick experiment which seems to confirm my assumption:

func _physics_process(delta):
    match delta_test_type:
        DELTA_TEST_TYPE.VELOCITY:
            velocity = Vector2(0, 60)
        DELTA_TEST_TYPE.VELOCITY_DELTA:
            velocity = Vector2(0, 3600 * delta)
        DELTA_TEST_TYPE.POSITION:
            global_position.y += 1
        DELTA_TEST_TYPE.POSITION_DELTA:
            global_position.y += 60 * delta
    move_and_slide()

Only the first and last case are stable to fluctuations in the physics tick rate. The other two show a different behavior when the tick rate is changed. So my suggestion would still be:

  • Adjusting velocity in physics_process and calling move_and*(): Don’t factor in delta.
  • Adjusting the global_position in _physics_process: Always do factor in delta.

In any case, from my experience in software development (not game development) it’s never a good idea relying on that something is “usually” a certain way. What seems practical and simpler at first will sooner or later get back to you, and then you have to spend time on debugging and figuring out all scripts that have to be fixed. It’s better to be exact from the start, especially if it is only one additional multiplication which will neither matter for performance nor readability.

1 Like

I may be able to shed some light on what “delta” means and why there are two separated “update” functions.

First of all, delta is short for “delta time” (Δt), which comes from the common expression seen in physics’ differential equations (used to describe changes in a physical system) and it represents the change in time. “Delta” is supposed to mean the same in both “update” functions, and it’s the time elapsed since it was last called.

Having two update functions is common practice in all frameworks that include a physics engine. In order to compute forces, movement, collisions, and so on, they require to solve a series of linear equations. The equation solvers are sensitive to the time elapsed between steps (updates), and can become unstable; if you’ve ever seen a game scene where things become jittery and, all of a sudden, explode, or something flies off in a random direction, that’s what happened. Something that helps a lot to keep the solvers stable is to maintain a constant update rate, hence the “physics update” function, a function dedicated to updates in the physics engine. The difference is that this function is called not once per frame, but in whatever tick interval it’s configured, so that “delta” is a constant value.

So, when do you use “delta”? Only in “process”? Only in “physics_process”? Both? Neither? It’s really pretty straightforward once you understand what it means. If you are doing a computation based on time (e.g.: update velocity based on acceleration, or update position based on velocity), you should use delta as the time passed since last update if you want to keep those updates consistent independently of the frame rate. Godot functions that take a “velocity” or an “acceleration”, will use the time delta internally, because an update based on something measured in “X per second” depends, unsurprisingly, on time.

I hope this helps. I know it’s more detail that people probably want to know, but I find that when things get confusing, understanding what’s going on is the best way to get rid of the confusion for good. And in this particular case, it’s really not that complicated.

3 Likes

Privacy & Terms