From 6b4a2c980147a4ea272ece395aeebd9bac9fd98b Mon Sep 17 00:00:00 2001 From: skyace65 Date: Sun, 1 Dec 2024 09:30:44 -0500 Subject: [PATCH] Port physics interpolation docs from 3.6 --- tutorials/physics/index.rst | 1 + .../2d_and_3d_physics_interpolation.rst | 69 ++++++ .../advanced_physics_interpolation.rst | 174 +++++++++++++ .../img/fti_camera_worldspace.webp | Bin 0 -> 3890 bytes .../img/fti_graph_fixed_ticks.webp | Bin 0 -> 2696 bytes .../img/fti_graph_interpolated.webp | Bin 0 -> 9856 bytes .../img/physics_interpolation_mode.webp | Bin 0 -> 6242 bytes tutorials/physics/interpolation/index.rst | 14 ++ .../physics_interpolation_introduction.rst | 232 ++++++++++++++++++ ...hysics_interpolation_quick_start_guide.rst | 14 ++ .../using_physics_interpolation.rst | 154 ++++++++++++ 11 files changed, 658 insertions(+) create mode 100644 tutorials/physics/interpolation/2d_and_3d_physics_interpolation.rst create mode 100644 tutorials/physics/interpolation/advanced_physics_interpolation.rst create mode 100644 tutorials/physics/interpolation/img/fti_camera_worldspace.webp create mode 100644 tutorials/physics/interpolation/img/fti_graph_fixed_ticks.webp create mode 100644 tutorials/physics/interpolation/img/fti_graph_interpolated.webp create mode 100644 tutorials/physics/interpolation/img/physics_interpolation_mode.webp create mode 100644 tutorials/physics/interpolation/index.rst create mode 100644 tutorials/physics/interpolation/physics_interpolation_introduction.rst create mode 100644 tutorials/physics/interpolation/physics_interpolation_quick_start_guide.rst create mode 100644 tutorials/physics/interpolation/using_physics_interpolation.rst diff --git a/tutorials/physics/index.rst b/tutorials/physics/index.rst index d220491164b..56d8c8433be 100644 --- a/tutorials/physics/index.rst +++ b/tutorials/physics/index.rst @@ -19,3 +19,4 @@ Physics collision_shapes_3d large_world_coordinates troubleshooting_physics_issues + interpolation/index diff --git a/tutorials/physics/interpolation/2d_and_3d_physics_interpolation.rst b/tutorials/physics/interpolation/2d_and_3d_physics_interpolation.rst new file mode 100644 index 00000000000..143cc3d3651 --- /dev/null +++ b/tutorials/physics/interpolation/2d_and_3d_physics_interpolation.rst @@ -0,0 +1,69 @@ +.. _doc_2d_and_3d_physics_interpolation: + +2D and 3D physics interpolation +=============================== + +Generally 2D and 3D physics interpolation work in very similar ways. However, there +are a few differences, which will be described here. + +Global versus local interpolation +--------------------------------- + +- In 3D, physics interpolation is performed *independently* on the **global + transform** of each 3D instance. +- In 2D by contrast, physics interpolation is performed on the **local transform** + of each 2D instance. + +This has some implications: + +- In 3D, it is easy to turn interpolation on and off at the level of each ``Node``, + via the ``physics_interpolation_mode`` property in the Inspector, which can be + set to ``On``, ``Off``, or ``Inherited``. + +.. figure:: img/physics_interpolation_mode.webp + :align: center + +- However this means that in 3D, pivots that occur in the ``SceneTree`` (due to + parent child relationships) can only be interpolated **approximately** over the + physics tick. In most cases this will not matter, but in some situations the + interpolation can look slightly wrong. +- In 2D, interpolated local transforms are passed down to children during + rendering. This means that if a parent is set to ``physics_interpolation_mode`` + ``On``, but the child is set to ``Off``, the child will still be interpolated if + the parent is moving. *Only the child's local transform is uninterpolated.* + Controlling the on / off behavior of 2D nodes therefore requires a little more + thought and planning. +- On the positive side, pivot behavior in the scene tree is perfectly preserved + during interpolation in 2D, which gives super smooth behaviour. + +Resetting physics interpolation +------------------------------- + +Whenever objects are moved to a completely new position, and interpolation is not +desired (so as to prevent a "streaking" artefact), it is the responsibility of the +user to call ``reset_physics_interpolation()``. + +The good news is that in 2D, this is automatically done for you when nodes first +enter the tree. This reduces boiler plate, and reduces the effort required to get +an existing project working. + +.. note:: If you move objects *after* adding to the scene tree, you will still need + to call ``reset_physics_interpolation()`` as with 3D. + +2D Particles +------------ + +Currently only ``CPUParticles2D`` are supported for physics interpolation in 2D. It +is recommended to use a physics tick rate of at least 20-30 ticks per second to +keep particles looking fluid. + +``Particles2D`` (GPU particles) are not yet interpolated, so for now it is +recommended to convert to ``CPUParticles2D`` (but keep a backup of your +``Particles2D`` in case we get these working). + +Other +----- + +- ``get_global_transform_interpolated()`` - this is currently only available for 3D. +- ``MultiMeshes`` - these should be supported in both 2D and 3D. + diff --git a/tutorials/physics/interpolation/advanced_physics_interpolation.rst b/tutorials/physics/interpolation/advanced_physics_interpolation.rst new file mode 100644 index 00000000000..28140f00c46 --- /dev/null +++ b/tutorials/physics/interpolation/advanced_physics_interpolation.rst @@ -0,0 +1,174 @@ +.. _doc_advanced_physics_interpolation: + +Advanced physics interpolation +============================== + +Although the previous instructions will give satisfactory results in a lot of games, +in some cases you will want to go a stage further to get the best possible results +and the smoothest possible experience. + +Exceptions to automatic physics interpolation +--------------------------------------------- + +Even with physics interpolation active, there may be some local situations where +you would benefit from disabling automatic interpolation for a +:ref:`Node` (or branch of the :ref:`SceneTree`), and +have the finer control of performing interpolation manually. + +This is possible using the :ref:`Node.physics_interpolation_mode` +property which is present in all Nodes. If you for example, turn off interpolation +for a Node, the children will recursively also be affected (as they default to +inheriting the parent setting). This means you can easily disable interpolation for +an entire subscene. + +The most common situation where you may want to perform your own interpolation is +Cameras. + +Cameras +~~~~~~~ + +In many cases, a :ref:`Camera3D` can use automatic interpolation +just like any other node. However, for best results, especially at low physics tick +rates, it is recommended that you take a manual approach to camera interpolation. + +This is because viewers are very sensitive to camera movement. For instance, a +Camera3D that realigns slightly every 1/10th of a second (at 10tps tick rate) will +often be noticeable. You can get a much smoother result by moving the camera each +frame in ``_process``, and following an interpolated target manually. + +Manual camera interpolation +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Ensure the camera is using global coordinate space +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The very first step when performing manual camera interpolation is to make sure the +Camera3D transform is specified in *global space* rather than inheriting the +transform of a moving parent. This is because feedback can occur between the +movement of a parent node of a Camera3D and the movement of the camera Node itself, +which can mess up the interpolation. + +There are two ways of doing this: + +1) Move the Camera3D so it is independent on its own branch, rather than being a child of a moving object. + +.. image:: img/fti_camera_worldspace.webp + +2) Call :ref:`Node3D.top_level` and set this to ``true``, which will make the Camera ignore the transform of its parent. + +Typical example +^^^^^^^^^^^^^^^ + +A typical example of a custom approach is to use the ``look_at`` function in the +Camera3D every frame in ``_process()`` to look at a target node (such as the player). + +But there is a problem. If we use the traditional ``get_global_transform()`` on a +Camera3D "target" node, this transform will only focus the Camera3D on the target *at +the current physics tick*. This is *not* what we want, as the camera will jump +about on each physics tick as the target moves. Even though the camera may be +updated each frame, this does not help give smooth motion if the *target* is only +changing each physics tick. + +get_global_transform_interpolated() +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +What we really want to focus the camera on, is not the position of the target on +the physics tick, but the *interpolated* position, i.e. the position at which the +target will be rendered. + +We can do this using the :ref:`Spatial.get_global_transform_interpolated` +function. This acts exactly like getting :ref:`Spatial.global_transform` +but it gives you the *interpolated* transform (during a ``_process()`` call). + +.. important:: ``get_global_transform_interpolated()`` should only be used once or + twice for special cases such as cameras. It should **not** be used + all over the place in your code (both for performance reasons, and + to give correct gameplay). + +.. note:: Aside from exceptions like the camera, in most cases, your game logic + should be in ``_physics_process()``. In game logic you should be calling + ``get_global_transform()`` or ``get_transform()``, which will give the + current physics transform (in global or local space respectively), which + is usually what you will want for gameplay code. + +Example manual camera script +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Here is an example of a simple fixed camera which follows an interpolated target: + +.. code-block:: gdscript + + extends Camera3D + + # Node that the camera will follow + var _target + + # We will smoothly lerp to follow the target + # rather than follow exactly + var _target_pos : Vector3 = Vector3() + + func _ready() -> void: + # Find the target node + _target = get_node("../Player") + + # Turn off automatic physics interpolation for the Camera3D, + # we will be doing this manually + set_physics_interpolation_mode(Node.PHYSICS_INTERPOLATION_MODE_OFF) + + func _process(delta: float) -> void: + # Find the current interpolated transform of the target + var tr : Transform = _target.get_global_transform_interpolated() + + # Provide some delayed smoothed lerping towards the target position + _target_pos = lerp(_target_pos, tr.origin, min(delta, 1.0)) + + # Fixed camera position, but it will follow the target + look_at(_target_pos, Vector3(0, 1, 0)) + +Mouse look +^^^^^^^^^^ + +Mouse look is a very common way of controlling cameras. But there is a problem. +Unlike keyboard input which can be sampled periodically on the physics tick, mouse +move events can come in continuously. The camera will be expected to react and +follow these mouse movements on the next frame, rather than waiting until the next +physics tick. + +In this situation, it can be better to disable physics interpolation for the camera +node (using :ref:`Node.physics_interpolation_mode`) +and directly apply the mouse input to the camera rotation, rather than apply it in +``_physics_process``. + +Sometimes, especially with cameras, you will want to use a combination of +interpolation and non-interpolation: + +* A first person camera may position the camera at a player location (perhaps using :ref:`Spatial.get_global_transform_interpolated`), but control the Camera rotation from mouse look *without* interpolation. +* A third person camera may similarly determine the look at (target location) of the camera using :ref:`Spatial.get_global_transform_interpolated`, but position the camera using mouse look *without* interpolation. + +There are many permutations and variations of camera types, but it should be clear +that in many cases, disabling automatic physics interpolation and handling this +yourself can give a better result. + +Disabling interpolation on other nodes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Although cameras are the most common example, there are a number of cases when you +may wish other nodes to control their own interpolation, or be non-interpolated. +Consider for example, a player in a top view game whose rotation is controlled by +mouse look. Disabling physics rotation allows the player rotation to match the +mouse in real-time. + + +MultiMeshes +~~~~~~~~~~~ + +Although most visual Nodes follow the single Node single visual instance paradigm, +MultiMeshes can control several instances from the same Node. Therefore, they have +some extra functions for controlling interpolation functionality on a +*per-instance* basis. You should explore these functions if you are using +interpolated MultiMeshes. + +- :ref:`MultiMesh.reset_instance_physics_interpolation` +- :ref:`MultiMesh.set_buffer_interpolated` + +Full details are in the :ref:`MultiMesh` documentation. diff --git a/tutorials/physics/interpolation/img/fti_camera_worldspace.webp b/tutorials/physics/interpolation/img/fti_camera_worldspace.webp new file mode 100644 index 0000000000000000000000000000000000000000..b99ff8eeb80d4a7fb69b7ecfd9c3b08528cf7eac GIT binary patch literal 3890 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 WL?_pa4BTHAFPh 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 literal 0 HcmV?d00001 diff --git a/tutorials/physics/interpolation/img/physics_interpolation_mode.webp b/tutorials/physics/interpolation/img/physics_interpolation_mode.webp new file mode 100644 index 0000000000000000000000000000000000000000..e66bbfbccda50165cc6db6783041d706a93cf12a GIT binary patch literal 6242 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 literal 0 HcmV?d00001 diff --git a/tutorials/physics/interpolation/index.rst b/tutorials/physics/interpolation/index.rst new file mode 100644 index 00000000000..92ce6c24484 --- /dev/null +++ b/tutorials/physics/interpolation/index.rst @@ -0,0 +1,14 @@ +.. _doc_physics_interpolation: + +Physics Interpolation +===================== + +.. toctree:: + :maxdepth: 1 + :name: toc-physics-interpolation + + physics_interpolation_quick_start_guide + physics_interpolation_introduction + using_physics_interpolation + advanced_physics_interpolation + 2d_and_3d_physics_interpolation diff --git a/tutorials/physics/interpolation/physics_interpolation_introduction.rst b/tutorials/physics/interpolation/physics_interpolation_introduction.rst new file mode 100644 index 00000000000..6969d4bb648 --- /dev/null +++ b/tutorials/physics/interpolation/physics_interpolation_introduction.rst @@ -0,0 +1,232 @@ +.. _doc_physics_interpolation_introduction: + +Introduction +============ + +Physics ticks and rendered frames +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +One key concept to understand in Godot is the distinction between physics ticks +(sometimes referred to as iterations or physics frames), and rendered frames. The +physics proceeds at a fixed tick rate (set in :ref:`Project Settings > Physics > Common > Physics Tick per Second`), +which defaults to 60 ticks per second. + +However, the engine does not necessarily **render** at the same rate. Although many +monitors refresh at 60 Hz (cycles per second), many refresh at completely different +frequencies (e.g. 75 Hz, 144 Hz, 240 Hz or more). Even though a monitor may be able +to show a new frame e.g. 60 times a second, there is no guarantee that the CPU and +GPU will be able to *supply* frames at this rate. For instance, when running with +V-Sync, the computer may be too slow for 60 and only reach the deadlines for 30 +FPS, in which case the frames you see will change at 30 FPS (resulting in +stuttering). + +But there is a problem here. What happens if the physics ticks do not coincide with +frames? What happens if the physics tick rate is out of phase with the frame rate? +Or worse, what happens if the physics tick rate is *lower* than the rendered frame +rate? + +This problem is easier to understand if we consider an extreme scenario. If you set +the physics tick rate to 10 ticks per second, in a simple game with a rendered +frame rate of 60 FPS. If we plot a graph of the positions of an object against the +rendered frames, you can see that the positions will appear to "jump" every 1/10th +of a second, rather than giving a smooth motion. When the physics calculates a new +position for a new object, it is not rendered in this position for just one frame, +but for 6 frames. + +.. image:: img/fti_graph_fixed_ticks.webp + +This jump can be seen in other combinations of tick / frame rate as glitches, or +jitter, caused by this staircasing effect due to the discrepancy between physics +tick time and rendered frame time. + +What can we do about frames and ticks being out of sync? +-------------------------------------------------------- + +Lock the tick / frame rate together? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The most obvious solution is to get rid of the problem, by ensuring there is a +physics tick that coincides with every frame. This used to be the approach on old +consoles and fixed hardware computers. If you know that every player will be using +the same hardware, you can ensure it is fast enough to calculate ticks and frames +at e.g. 50 FPS, and you will be sure it will work great for everybody. + +However, modern games are often no longer made for fixed hardware. You will often +be planning to release on desktop computers, mobiles, and more. All of which have +huge variations in performance, as well as different monitor refresh rates. We need +to come up with a better way of dealing with the problem. + +Adapt the tick rate? +^^^^^^^^^^^^^^^^^^^^ + +Instead of designing the game at a fixed physics tick rate, we could allow the tick +rate to scale according to the end users hardware. We could for example use a fixed +tick rate that works for that hardware, or even vary the duration of each physics +tick to match a particular frame duration. + +This works, but there is a problem. Physics (*and game logic*, which is often also +run in the ``_physics_process``) work best and most consistently when run at a +**fixed**, predetermined tick rate. If you attempt to run a racing game physics +that has been designed for 60 TPS (ticks per second) at e.g. 10 TPS, the physics +will behave completely differently. Controls may be less responsive, collisions / +trajectories can be completely different. You may test your game thoroughly at 60 +TPS, then find it breaks on end users machines when it runs at a different tick +rate. + +This can make quality assurance difficult with hard to reproduce bugs, especially +in AAA games where problems of this sort can be very costly. This can also be +problematic for multiplayer games for competitive integrity, as running the game at +certain tick rates may be more advantageous than others. + +Lock the tick rate, but use interpolation to smooth frames in between physics ticks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This has become one of the most popular approaches to deal with the problem, +although it is optional and disabled by default. + +We have established that the most desirable physics/game logic arrangement for +consistency and predictability is a physics tick rate that is fixed at design-time. +The problem is the discrepancy between the physics position recorded, and where we +"want" a physics object to be shown on a frame to give smooth motion. + +The answer turns out to be simple, but can be a little hard to get your head around +at first. + +Instead of keeping track of just the current position of a physics object in the +engine, we keep track of *both the current position of the object, and the previous +position* on the previous physics tick. + +Why do we need the previous position *(in fact the entire transform, including +rotation and scaling)*? By using a little math magic, we can use **interpolation** +to calculate what the transform of the object would be between those two points, in +our ideal world of smooth continuous movement. + +.. image:: img/fti_graph_interpolated.webp + +Linear interpolation +^^^^^^^^^^^^^^^^^^^^ + +The simplest way to achieve this is linear interpolation, or lerping, which you may +have used before. + +Let us consider only the position, and a situation where we know that the previous +physics tick X coordinate was 10 units, and the current physics tick X coordinate +is 30 units. + +.. note:: Although the maths is explained here, you do not have to worry about the + details, as this step will be performed for you. Under the hood, Godot + may use more complex forms of interpolation, but linear interpolation is + the easiest in terms of explanation. + +The physics interpolation fraction +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If our physics ticks are happening 10 times per second (for this example), what +happens if our rendered frame takes place at time 0.12 seconds? We can do some math +to figure out where the object would be to obtain a smooth motion between the two +ticks. + +First of all, we have to calculate how far through the physics tick we want the +object to be. If the last physics tick took place at 0.1 seconds, we are 0.02 +seconds *(0.12 - 0.1)* through a tick that we know will take 0.1 seconds (10 ticks +per second). The fraction through the tick is thus: + +.. code-block:: gdscript + + fraction = 0.02 / 0.10 + fraction = 0.2 + +This is called the **physics interpolation fraction**, and is handily calculated +for you by Godot. It can be retrieved on any frame by calling :ref:`Engine.get_physics_interpolation_fraction`. + +Calculating the interpolated position +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Once we have the interpolation fraction, we can insert it into a standard linear +interpolation equation. The X coordinate would thus be: + +.. code-block:: gdscript + + x_interpolated = x_prev + ((x_curr - x_prev) * 0.2) + +So substituting our ``x_prev`` as 10, and ``x_curr`` as 30: + +.. code-block:: gdscript + + x_interpolated = 10 + ((30 - 10) * 0.2) + x_interpolated = 10 + 4 + x_interpolated = 14 + +Let's break that down: + +- We know the X starts from the coordinate on the previous tick (``x_prev``) which + is 10 units. +- We know that after the full tick, the difference between the current tick and the + previous tick will have been added (``x_curr - x_prev``) (which is 20 units). +- The only thing we need to vary is the proportion of this difference we add, + according to how far we are through the physics tick. + +.. note:: Although this example interpolates the position, the same thing can be + done with the rotation and scale of objects. It is not necessary to know + the details as Godot will do all this for you. + +Smoothed transformations between physics ticks? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Putting all this together shows that it should be possible to have a nice smooth +estimation of the transform of objects between the current and previous physics +tick. + +But wait, you may have noticed something. If we are interpolating between the +current and previous ticks, we are not estimating the position of the object *now*, +we are estimating the position of the object in the past. To be exact, we are +estimating the position of the object *between 1 and 2 ticks* into the past. + +In the past +^^^^^^^^^^^ + +What does this mean? This scheme does work, but it does mean we are effectively +introducing a delay between what we see on the screen, and where the objects +*should* be. + +In practice, most people won't notice this delay, or rather, it is typically not +*objectionable*. There are already significant delays involved in games, we just +don't typically notice them. The most significant effect is there can be a slight +delay to input, which can be a factor in fast twitch games. In some of these fast +input situations, you may wish to turn off physics interpolation and use a +different scheme, or use a high tick rate, which mitigates these delays. + +Why look into the past? Why not predict the future? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There is an alternative to this scheme, which is: instead of interpolating between +the previous and current tick, we use maths to *extrapolate* into the future. We +try to predict where the object *will be*, rather than show it where it was. This +can be done and may be offered as an option in future, but there are some +significant downsides: + +- The prediction may not be correct, especially when an object collides with + another object during the physics tick. +- Where a prediction was incorrect, the object may extrapolate into an "impossible" + position, like inside a wall. +- Providing the movement speed is slow, these incorrect predictions may not be too + much of a problem. +- When a prediction was incorrect, the object may have to jump or snap back onto + the corrected path. This can be visually jarring. + +Fixed timestep interpolation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In Godot this whole system is referred to as physics interpolation, but you may +also hear it referred to as **"fixed timestep interpolation"**, as it is +interpolating between objects moved with a fixed timestep (physics ticks per +second). In some ways the second term is more accurate, because it can also be used +to interpolate objects that are not driven by physics. + +.. tip:: Although physics interpolation is usually a good choice, there are + exceptions where you may choose not to use Godot's built-in physics + interpolation (or use it in a limited fashion). An example category is + internet multiplayer games. Multiplayer games often receive tick or timing + based information from other players or a server and these may not + coincide with local physics ticks, so a custom interpolation technique can + often be a better fit. diff --git a/tutorials/physics/interpolation/physics_interpolation_quick_start_guide.rst b/tutorials/physics/interpolation/physics_interpolation_quick_start_guide.rst new file mode 100644 index 00000000000..0ba9fca7867 --- /dev/null +++ b/tutorials/physics/interpolation/physics_interpolation_quick_start_guide.rst @@ -0,0 +1,14 @@ +.. _doc_physics_interpolation_quick_start_guide: + +Quick start guide +================= + +- Turn on physics interpolation: :ref:`Project Settings > Physics > Common > Physics Interpolation` +- Make sure you move objects and run your game logic in ``_physics_process()`` + rather than ``_process()``. This includes moving objects directly *and + indirectly* (by e.g. moving a parent, or using another mechanism to automatically + move nodes). +- Be sure to call :ref:`Node.reset_physics_interpolation` + on nodes *after* you first position or teleport them, to prevent "streaking". +- Temporarily try setting :ref:`Project Settings > Physics > Common > Physics Tick per Second` + to 10 to see the difference with and without interpolation. diff --git a/tutorials/physics/interpolation/using_physics_interpolation.rst b/tutorials/physics/interpolation/using_physics_interpolation.rst new file mode 100644 index 00000000000..a97bed44fbf --- /dev/null +++ b/tutorials/physics/interpolation/using_physics_interpolation.rst @@ -0,0 +1,154 @@ +.. _doc_using_physics_interpolation: + +Using physics interpolation +=========================== + +How do we incorporate physics interpolation into a Godot game? Are there any +caveats? + +We have tried to make the system as easy to use as possible, and many existing +games will work with few changes. That said there are some situations which require +special treatment, and these will be described. + +Turn on the physics interpolation setting +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The first step is to turn on physics interpolation in :ref:`ProjectSettings.physics/common/physics_interpolation`. +You can now run your game. + +It is likely that nothing looks hugely different, particularly if you are running +physics at 60 TPS or a multiple of it. However, quite a bit more is happening +behind the scenes. + +.. tip:: + + To convert an existing game to use interpolation, it is highly recommended that + you temporarily set :ref:`ProjectSettings.physics/common/physics_ticks_per_second` + to a low value such as 10, which will make interpolation problems more obvious. + +Move (almost) all game logic from _process to _physics_process +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The most fundamental requirement for physics interpolation (which you may be doing +already) is that you should be moving and performing game logic on your objects +within ``_physics_process`` (which runs at a physics tick) rather than ``_process`` +(which runs on a rendered frame). This means your scripts should typically be doing +the bulk of their processing within ``_physics_process``, including responding to +input and AI. + +Setting the transform of objects only within physics ticks allows the automatic +interpolation to deal with transforms *between* physics ticks, and ensures the game +will run the same whatever machine it is run on. As a bonus, this also reduces CPU +usage if the game is rendering at high FPS, since AI logic (for example) will no +longer run on every rendered frame. + +.. note:: If you attempt to set the transform of interpolated objects *outside* the + physics tick, the calculations for the interpolated position will be + incorrect, and you will get jitter. This jitter may not be visible on + your machine, but it *will* occur for some players. For this reason, + setting the transform of interpolated objects should be avoided outside + of the physics tick. Godot will attempt to produce warnings in the editor + if this case is detected. + +.. tip:: This is only a *soft rule*. There are some occasions where you might want + to teleport objects outside of the physics tick (for instance when + starting a level, or respawning objects). Still, in general, you should be + applying transforms from the physics tick. + + +Ensure that all indirect movement happens during physics ticks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Consider that in Godot, Nodes can be moved not just directly in your own scripts, +but also by automatic methods such as tweening, animation, and navigation. All +these methods should also have their timing set to operate on the physics tick +rather than each frame ("idle"), **if** you are using them to move objects (*these +methods can also be used to control properties that are not interpolated*). + +.. note:: Also consider that nodes can be moved not just by moving themselves, but + also by moving parent nodes in the :ref:`SceneTree`. The + movement of parents should therefore also only occur during physics ticks. + +Choose a physics tick rate +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When using physics interpolation, the rendering is decoupled from physics, and you +can choose any value that makes sense for your game. You are no longer limited to +values that are multiples of the user's monitor refresh rate (for stutter-free +gameplay if the target FPS is reached). + +As a rough guide: + +.. csv-table:: + :header: "Low tick rates (10-30)", "Medium tick rates (30-60)", "High tick rates (60+)" + :widths: 20, 20, 20 + + "Better CPU performance","Good physics behavior in complex scenes","Good with fast physics" + "Add some delay to input","Good for first person games","Good for racing games" + "Simple physics behaviour" + +.. note:: You can always change the tick rate as you develop, it is as simple as + changing the project setting. + +Call ``reset_physics_interpolation()`` when teleporting objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Most of the time, interpolation is what you want between two physics ticks. +However, there is one situation in which it may *not* be what you want. That is +when you are initially placing objects, or moving them to a new location. Here, you +don't want a smooth motion between where the object was (e.g. the origin) and the +initial position - you want an instantaneous move. + +The solution to this is to call the :ref:`Node.reset_physics_interpolation` +function. What this function does under the hood is set the internally stored +*previous transform* of the object to be equal to the *current transform*. This +ensures that when interpolating between these two equal transforms, there will be +no movement. + +Even if you forget to call this, it will usually not be a problem in most +situations (especially at high tick rates). This is something you can easily leave +to the polishing phase of your game. The worst that will happen is seeing a +streaking motion for a frame or so when you move them - you will know when you need +it! + +There are actually two ways to use ``reset_physics_interpolation()``: + +*Standing start (e.g. player)* + +1) Set the initial transform +2) Call ``reset_physics_interpolation()`` + +The previous and current transforms will be identical, resulting in no initial +movement. + +*Moving start (e.g. bullet)* + +1) Set the initial transform +2) Call ``reset_physics_interpolation()`` +3) Immediately set the transform expected after the first tick of motion + +The previous transform will be the starting position, and the current transform +will act as though a tick of simulation has already taken place. This will +immediately start moving the object, instead of having a tick delay standing still. + +.. important:: Make sure you set the transform and call + ``reset_physics_interpolation()`` in the correct order as shown + above, otherwise you will see unwanted "streaking". + +Testing and debugging tips +-------------------------- + +Even if you intend to run physics at 60 TPS, in order to thoroughly test your +interpolation and get the smoothest gameplay, it is highly recommended to +temporarily set the physics tick rate to a low value such as 10 TPS. + +The gameplay may not work perfectly, but it should enable you to more easily see +cases where you should be calling :ref:`Node.reset_physics_interpolation`, +or where you should be using your own custom interpolation on e.g. a +:ref:`Camera3D`. Once you have these cases fixed, you can set the +physics tick rate back to the desired setting. + +The other great advantage to testing at a low tick rate is you can often notice +other game systems that are synchronized to the physics tick and creating glitches +which you may want to work around. Typical examples include setting animation blend +values, which you may decide to set in ``_process()`` and interpolate manually.