In this "tutorial" I will explain the differences between the functions
_physics_process() in Godot Engine and when to use each.
Godot will do its best to run
_physics_process() at a fixed interval within its main loop. In other words, the engine will do whatever it can in order to keep the
delta argument always be the same. More specifically, the pace in which this function will be called is determined by the Physics FPS setting, which can be found at Project Settings → Physics/Common. This setting tells that the physics process should be called that amount of times per second. This means that the
delta should be equal to
1 / PhysicsFPS seconds and, as mentioned, Godot will do its best to ensure this fact. By default the physics FPS is set to 60, which means
_physics_process() will be called each 1/60 = 16.6666... milliseconds.
On the other hand the
_process() function will be called at a varying interval, meaning that its
delta argument will most likely have different values each time the function is called.
One very important thing to keep in mind is that
_physics_process() might get called multiple times in a single loop iteration. The
_process() function is meant to be called once per frame. This means that the
_process() function will be called as fast as the main loop is running.
The first question that someone might do is why have two functions that will be called during the main loop in order to update the game simulation? To help explain things lets go back in time a little bit. In the past, when the amount of processors were very limited, the pace in which they were capable of running operations were also very known by programmers. Game loops were tweaked with that knowledge in order to keep the game running at the expected speed.
As faster processors started to become available, those games also started to run faster. The proposed solution is to use the delta time between loop iterations in order to determine how much the objects should be updated. In theory this brings a time independent game update, meaning that varying processor speeds should yield the same results.
Well, problem is, the way computers store floating point numbers brings some errors. What happens is that we can't represent every single real number. When we need one of those values that can't be represented, what we get will be an approximation. Those errors accumulate and the result is that the game will most likely not behave exactly the same between different runs. And when on different hardware, it's almost certain there will be differences in the simulation.
The next solution is to run the game at a fixed time step. That is, no matter the speed of the processor the simulation update should always occur at the same interval. However this brings a problem. Suppose we determined that the game should be updated at 30 frames per second to reduce its target hardware requirements. The typical computer screen will be updated 60 times per second. In other words, the screen's refresh rate is faster than the simulation pace. In here object motion will look very jittery. It's easy to simulate this scenario in Godot by ensuring VSync is enabled and setting the Physics FPS value to something well bellow the refresh rate of the computer screen.
Ok, so, running the game as fast as possible but using a delta time to determine how much things are updated result in smooth motion but unreliable simulation. And if we run the game at a fixed time step we get reliable simulation but potential jittery motion. What to choose, smooth animation or reliable simulation? Luckily we don't have to choose one or the other. We can have both! This is accomplished by decoupling the game simulation from the rendering.
In Godot we sort of get this decoupling by having the
_physics_process() (fixed time step) and the
_process() (as fast as possible) functions.
Interesting, but if the game is updated at a fixed time step, what difference would it make by rendering the state as fast as possible? Well, if we render the actual state of the game we will obviously not have any benefit at all. However, we can calculate an interpolated state and render that instead. Basically when the game is updated, the rendering will take how it was in the previous physics frame and interpolate what it should render based on the current frame.
Wait, if we take previous game state and interpolate a visual state between previous and the current one, aren't we rendering something a little behind the actual game state? Indeed we are. And the "most behind" the visual state will be is the exact interval between the physics updates. In a 30 physics per second simulation, the biggest delay we would have is 33.3333... milliseconds. In the typical 60 physics per second this visual delay goes down to at most 16.6666... milliseconds. What this means is that if we increase the pace in which the game is simulated, the visual delay is reduced at the cost of increasing hardware requirements.
If you think this delay is unacceptable, then consider the fact that many games have been doing this for years! The fact is, this delay is unnoticeable. It might become problematic when added to the lag caused by networked multiplayer. However, this interpolation is actually almost necessary in order to obtain a reasonably acceptable animation when performing this kind of game synchronization. The point here is that the proposed technique used to obtain a reliable simulation with smooth animation for single players is almost ready to be adapted to also work with networked multiplayer games.
But, how exactly do we perform this kind of interpolation? Suppose we have a basic game object that extends the Kinematic Body class. It doesn't matter if 2D or 3D as the logic is the exact same (so I will not specify which class here). Our script can perform the simulation update normally:
func _physics_process(delta: float) -> void: ... process input ... calculate velocity move_and_collide(calculated_velocity * delta)
Excellent, we know the simulation is running at a fixed time step. But, as mentioned, it can lead to jittery motion, specially when the screen refresh rate is bigger than the physics frames per second setting. Because of that we have to take any visual node (sprite, mesh instance etc) within the game object scene hierarchy and make it smoothly follow the kinematic body. Basically we use the
_process() function to modify the position of the visual node, based on the interpolated position. Since we have to interpolate from previous position to current position, we have to store the previous position somewhere. That means we need to update the fixed time step function to store the entity position (and orientation) before updating to a new one:
func _physics_process(delta: float) -> void: # This should be angle in 2D and Quat in 3D previous_orientation: SOME_TYPE = orientation # This should actually be Vector2 OR Vector3 - Vector does not exist previous_position: Vector = position ... process input ... calculate velocity move_and_collide(calculated_velocity * delta)
Ok, now we are storing the previous position and orientation within the script scope. Note that I didn't specify if
Vector3 in the previous snippet. Again, the logic is the exact same for both 2D and 3D. With those values we can interpolate where the visual node has to be rendered. As a quick recap, interpolating requires 3 values, the originating "point", final "point" and a "percent", which is called alpha in many implementations. In Godot it's named
weight. Speaking of that, the interpolation function that we will use is
lerp(). The weight that we will use can be easily obtained by simply calling
get_physics_interpolation_fraction(), which is provided since Godot version 3.2. The calculation then becomes something like this:
func _process(delta: float) -> void: # Get the visual node - this becomes better if static typing to the actual type of the node, # like Sprite, MeshInstance etc. var visual: Node = $path/to/visual/node var alpha: float = get_physics_interpolation_fraction() visual.position = lerp(previous_position, position, alpha) visual.orientation = lerp(previous_orientation, orientation, alpha)
Attention: In the previous two snippets I have used "orientation" as if it were a property. It isn't. In 2D we most likely represent this as a simple angle and in 3D we can represent this as a quaternion. When using angle the interpolation will work best if using
lerp_angle()as it takes the "full circle" into account. In either case, the saved "previous" state must be properly obtained and then properly set within the visual node. By properly I mean the correct property holding the relevant orientation information.
Nice, the visual node is indeed being interpolated and bringing us a relatively smooth motion. But, if we attach a camera to a game object that moves like this, we will most likely see something very weird. What happens is that the camera will perform the non smooth motion of the kinematic body, while the visual node will be smoothed. Because the camera is "our window" to the game world, what we will see is a very jittery movement.
We might even think the character is not moving correctly, but if you take the camera to a stationary position you will notice that the visual node is indeed moving smoothly. What we have to do here is that if a camera must be attached into an entity with smooth visual node motion, the camera motion also needs to be smoothed through interpolation very much like the visual nodes.
As you have seen in the previous topic, for a simple player character which typically have a camera attached to the game object, we need to calculate the interpolated position for both the visual representation of the character itself and the camera. This obviously require additional code to ensure the interpolation is being performed. Yet we can somewhat "automate" this. We can create a node (it's necessary to have one for 2D and another for 3D) that will interpolate its own position and orientation. In a way, this node will follow its parent using the interpolation method shown. Any child of this node will then "automatically" have its motion in "interpolated mode".
Usage then becomes something like this:
I will not cover in details (or anything really) about said nodes because:
That said, if you want to perform those operations yourself, I believe there are some reference material to that. And if you still need help you can contact me!
Now that the differences of the two functions are known, and how to use them have been shown here, I would like to talk a little bit about when to use which. Unfortunately the name
_physics_process() can be a bit misleading in this aspect. I would say that anything that directly affects the gameplay should be updated within the fixed time step function while everything else can use the
Granted, the majority of elements that directly affect gameplay like that are indeed physics. Yet, consider AI calculation. It's not necessarily physics. Normally speaking it should generate input to the non player characters. Besides the result of the AI directly affecting the game, there is another factor here. Generally speaking the AI system will take the game state in order to calculate what the characters should do. There isn't much sense in running the AI in
_process() if the game state is being updated at the fixed time step, potentially at a lower pace.
Speaking of input. While we can gather player input as fast as possible, it should only affect the controlled character(s) during the actual simulation, which is in the fixed time step processing. So it also doesn't make much sense to gather input within the
The UI is something that rarely affect the simulation itself. In cases like this, those can be updated within the
_process() function, which will potentially make their animations very smooth.
Ok, whatever I will discuss in this section is entirely optional. I explained the why and how of the two processing functions as quickly and to the point as I could. Yet I want to got slightly deeper on the actual problem of simply relying on the delta time and updating the game as fast as possible.
As I have mentioned, floating point math is not precise. Not all numbers can be represented. As an example, the simple 0.1 number cannot and it will be approximated. Before I proceed, please note that Godot does something very weird with the print function. Indeed,
print("%.16f" % 0.1) will give 0.1000000000000000 as output. However if you perform
print(0.1 + 0.1 + 0.1 == 0.3) the output will be False. Internally the numbers are not representable and the result of the last print should "prove" that something is going on. Now consider the following snippet:
var interval: float = 0.1 var current: float = interval for i in 10: print("%.16f" % current) current += interval
The expected result should be the numbers in sequence, from 0.1 up to 1.0. Yet, the output is this:
Someone might say "but hey, that error is very small, why should we bother"? Well, consider this, that error occurred with a rather small number of iterations (10). If we set the game to run at 60 frames per second, with just 1/6 of a second we already have an error. And it will accumulate, becoming bigger and bigger. Now note something, as I mentioned, the
print() function is doing something and showing the expected value in the very first iterations of the loop, however 0.1 is not representable and is in fact holding a different thing than the one that has been output. So, yes, it took just a single loop iteration to get the error.
If we let the simulation run as fast as possible and use the delta time to determine how much things are updated, we are adding to the mix an additional source of imprecision. But it's one source that we can't exactly control, which will ultimately create unreliable simulations. With a fixed time step we eliminated a factor that we didn't have control. We still have an error but multiple runs of the simulation will most likely have the exact same one. In the end this should yield the same results.
Yet, maybe someone might ask "but what kind of simulation do we accumulate values like that?". Well, gravity is the easiest example. We often simulate its effect like this:
func _physics_update(dt) -> void: ... do some stuff # Integrate gravity velocity.y -= gravity ... do rest of stuff
In other words, instead of accumulating the 0.1 value we have used early, we use the gravity acceleration, which normally is 9.81. And so, just out of curiosity, if we substitute the
interval value of 0.1 by 9.81 in the loop iteration early, we get an output like this:
Someone might say that a better/different integrator should be used. Well, probably, yes. This is actually subject for a different post and I might cover it in the future.
Hopefully this short text helped you understand what roles the
_physics_process() functions have in a project made with Godot Engine. As a general "rule of thumb", anything that directly change the results of the simulation should go into the
_physics_process() and almost everything else into the
_process(). This helps us decouple gameplay code from rendering, which increases reliability of the simulation while also allowing the animations to run smoothly.