Smooth
April 17, 2020

This addon contains two nodes that can be added into your scene directly from the Create Node window:

Create Node

For this to work, this addon must be activated from the project settings, plugins tab. Nevertheless, as you can see, there is one node for 2D and another for 3D. As I have mentioned in the introduction, this addon is largely based on Lawnjelly’s Smoothing addon. There are a few key differences, though, which I will explain shortly.

The main objective of this addon is to create a system to automatically calculate interpolation states for visual representation of objects in the game world.

The differences between this addon and Lawnjelly’s:

  1. In Lawnjelly’s addon, when you add the smoothing node to the hierarchy, you have to manually specify the target that will be followed. This obviously brings a bit of freedom regarding node organization. In the case of my addon, the target is directly obtained by retrieving the parent node.
  2. Internally, my addon deals only with global transforms so it works regardless of the global node hierarchy. Lawnjelly’s addon offers means to work with global or relative transforms. Depending on the hierarchy, relative transforms will not work.

Now, why this interpolation system? Simply put, it allows for much smoother animations, specially when dealing with monitors with faster refresh rates than the physics updates per second. It’s also one of the ways to keep things running in a networked multiplayer game while the client is waiting for new server state.

Regarding the networked multiplayer game, I did talk about it in the network addon tutorial page. Regarding the smoother animations I want to talk a little more about this. We already know that physics simulations must run at fixed time steps. The frequency itself may change from project to project, but once the game is running it should remain the same. Generally speaking, the entire visual representation of the game is directly tied to it’s actual physics state. This leads to a problem when the physics frequency is lower than the refresh rate of the screen. The animation will be noticeable choppy.

One of the proposed solutions that “pop around” often is to dynamically change the physics frequency and keep it the same as the refresh rate of the screen where the game is running. This is not an ideal solution because it will lead to the game behaving differently depending on the screen. Basically, this falls back into the original problem of not having a fixed physics time step. It also brings another problem, which is the fact that generally speaking, input is gathered at the physics update. If the physics is running faster on certain screens, players with those conditions will have more input data gathered and processed than others, which obviously is not exactly fair.

Enter interpolation. The basic idea is to use interpolation to only change the visual representation of the game, while the physics state remains updated only at the designed physics frequency. The basic idea, problem discussion and solution are described by Glenn Fiedler in his Fix Your Timestep! article.

Interestingly, there isn’t much that is needed to use this addon. Normally you just attach the Smooth*D node between the physical object (rigid body or kinetic body) and the visual representation (mesh instance, sprite…) of the game object.

Two very common node hierarchies for characters are depicted bellow:

  • Kinetic Body or Rigid Body
    • Collision Shape
    • Visual (mesh, sprite...)
  • Kinetic Body or Rigid Body
    • Collision Shape
      • Visual (mesh, sprite...)

To smooth out the visual representation those hierarchies would then become like this:

  • Kinetic Body or Rigid Body
    • Collision Shape
    • Smooth*D
      • Visual (mesh, sprite...)
  • Kinetic Body or Rigid Body
    • Collision Shape
      • Smooth*D
        • Visual (mesh, sprite...)

With this the physical object remains tied to the physics tick while the visual representation of the game object smoothly follows it. Indeed the visual representation of the game will have an small delay when compared to the actual physical state. Some people may think this is unacceptable, however think about this, the amount of delay here is really small. Normally, a fraction of a single physics tick to be more exact. So if you configure your game to run at only 30 physics updates per second, then the interpolation will result in delay of at most 33.33 milliseconds. However, because the objects are moving between the physics ticks (ideally tied to the screen’s refresh rate - IE.: VSync enabled), there won’t be a sense of delay.

The interpolation can be disabled and the smooth node basically do nothing. If you set the Enabled property to false then the interpolation will not be used and the smooth node will snap into the target (the parent node), which will then result in the visual representation also snapping into the physical state.

Obviously you can manipulate this property from code:

# Disable the smoothing of the visual representation
$character_node/Smooth2D.enabled = false
# ... some code
# Enable the smoothing again
$character_node/Smooth2D.enabled = true

If desired you can teleport the smooth node into the specified transform, which can be very useful when an smoothed object is spawned and it’s initial position is not in the world’s origin:

# Teleport smooth node into the character's transform
$character_node/Smooth3D.teleport_to($character_node.global_transform)

There is a shortcut function within the smooth nodes that can be used when the desired teleportation transform corresponds to the smooth node parent:

# Teleport smooth node into the  character's transform
$character_node/Smooth3D.snap_to_target()

The main idea of the smooth node is that it will follow it’s parent node but using interpolation during the _process() function. Then, any node attached to the smooth one will have it’s transform behave in the same way.

In order to perform interpolation two states are necessary. A percent is then used to calculate an state that is in between the two reference ones. With that in mind, the Smooth2D and Smooth3D nodes internally keep track of the two states in the _from and _to variables, which are transforms. At each physics update, those two variables are updated, making _from hold the previous state while _to should then hold the target state. The target state is always gathered from the parent node of the Smooth*D.

During the _process() function the interpolation percent/fraction/alpha/factor is obtained by the Engine.get_physics_interpolation_fraction() function and then the global transform of the smooth node is set to the interpolated one, with _from.interpolate_with(_to, alpha).

One thing to note is that the _from/_to are not immediately cycled during the _physics_update(). Rather, a flag is set when physics get a tick and then in the _process() function, if this flag is true the states are updated. Otherwise the smooth node will seem a lot more behind than it should. The problem here is that the state of the smooth node depends on the correct state of the parent node, which, for some reason, does not report the most up to date transform. Deferring the call to perform the state updates also doesn’t help. Performing the update from within the _process() function based on the flag solved the problem.

There is also a method to allow the smooth node to be “teleported” to the specified transform. This is achieved by simply assigning the specified transform to both _from and _to.

One thing to mention is related to the get_physics_interpolation_fraction() function. One rather weird thing is that if we try to manually compute this value from GDScript, at some point we will diverge from the desired result and the visual representation of the object becomes rather unstable rather than smooth! Changing the jitter_fix project setting does not seem to do any difference. To be really honest here, I couldn’t figure out the reason it was simply not possible to get away from using this function and thus make this addon compatible with Godot 3.1.

Interestingly, the smooth node is that simple! There isn’t (I think) more that need explaining!

Introduction