Today we return to Unity basics. We take another look at Unity’s *SmoothDamp *function (read previous post about it: SmoothDamp – dynamic smoothing!). Our goal is to implement *SmoothDamp *step by step, propose similar function with different dynamics – *InertialDamp* and get familiar with basics of numerical integration of differential equations. It may seem that we reinvent the wheel but we hope that you will find something useful and interesting in our elaboration. Caution! This post contains more math than usual, but we hope that this will not scare you!

# SmoothDamp introduction – maths inside!

Let’s start with a pinch of math. For now we know that *SmoothDamp* is an dynamic system implementation, we assume that it’s a second order system. Dynamic system can be described by a set of differential equations. To get them from second order inertia we can use it’s transfer function given as:

\(K(s)=\frac{Y(s)}{U(s)}=\frac{k}{(1+sT_1)(1+sT_2)},\)

where: \(Y(s)\) – Laplace transform of system output, \(U(s)\) – input transform, \(k\) – magnitude, \(T_1, T_2\) – time constants. After a few calculations we can get:

\((1+sT_1)(1+sT_2)Y(s)=kU(s),\)

\(T_1T_2s^2Y(s) + (T_1 + T_2)sY(s)+Y(s)=kU(s),\)

and after using inverse Laplace transform:

\(T_1T_2\ddot{y}(t)+(T_1+T_2)\dot{y}(t)+y(t)=ku(t).\)

Finally we get differential equation of second order inertia:

\(\ddot{y}(t)=-\frac{T_1+T_2}{T_1T_2}\dot{y}(t)-\frac{1}{T_1T_2}y(t)+\frac{k}{T_1T_2}u(t).\)

How does it relate to *SmoothDamp*? We can implement dynamic system given as differential equations by numerical integration algorithms. The most popular are Runge-Kutta methods. Maybe we will discuss this topic in our next post. For now we will use the simplest method: Forward Euler Method. This method is used to numeric solving first order differential equations (calculating next \(y_i\) values):

\(y_{i+1}=y_i+hf(y_i,t_i),\)

where: \(y_{i+1}\) – next value (solution in next step), \(y_i\) – previous solution, \(h\) – discrete integration step, \(f(y_i,t_i)\) – function of previous \(y\) and \(t\) value which is given directly from differential equation:

\(\dot{y}(t)=f(y(t),t).\)

It’s important to mention that we also need the starting value \(y_0\) of above algorithm which is an initial condition of differential equation \(y(0)\). As you can see, we can simply integrate first order differential equation so we need to split our second order inertia equation into 2 equations. We can do this with simple substitution:

\(x_1=y(t), \quad \dot{x}_1= \dot{y}(t), \quad x_2=\dot{y}(t), \quad \dot{x}_2=\ddot{y}(t),\)

Finally we get:

\(\begin{cases} \dot{x}_1= x_2 \\ \dot{x}_2=-\frac{T_1+T_2}{T_1T_2}x_2-\frac{1}{T_1T_2}x_1+\frac{k}{T_1T_2}u(t)\end{cases}\)

We can independently integrate these equations with mentioned Euler’s method:

\(\begin{cases} x_{1,i+1}=x_{1,i}+ hx_{2,i} \\ x_{2,i+1}=x_{2,i}+h(-\frac{T_1+T_2}{T_1T_2}x_{2,i}-\frac{1}{T_1T_2}x_{1,i}+\frac{k}{T_1T_2}u_i) \end{cases}\)

We can do the same steps for first order inertia:

\(K(s)=\frac{Y(s)}{U(s)}=\frac{k}{1+sT},\)

\(\dot{y}(t)=-\frac{1}{T}y(t)+ku(t)\)

and finally:

\(y_{i+1}=y_i+h(-\frac{1}{T}y_i+ku_i).\)

*Smoothdamp *allows you to transform some values to target values. How we can use mentioned calculations to imitate its behaviour? We need two additional assumptions. We know that above systems are stable so their free response components aim dynamically to 0. We also know that we don’t have any inputs so equations can be reduced with assumption that \(u(t)=0\). No matter what are values of initial conditions, in steady state systems’ outputs will be equal 0. We just need to add target value to system’s output and properly simulate it (we don’t want to integrate nothing but the difference between current value and its target) to get desired results.

# C# implementation – *InertialDamp*

We prepared two functions which implements all above calculations (*InertialDamp *and *SmoothDamp*):

1 2 3 4 5 6 |
public float InertialDamp(float previousValue, float targetValue, float smoothTime, float h) { float x = previousValue - targetValue; float newValue = x + h * (-1f / smoothTime * x); return targetValue + newValue; } |

1 2 3 4 5 6 7 8 9 10 |
public float SmoothDamp(float previousValue, float targetValue, ref float speed, float smoothTime, float h) { float T1 = 0.36f * smoothTime; float T2 = 0.64f * smoothTime; float x = previousValue - targetValue; float newSpeed = speed + h * (-1f / (T1 * T2) * x - (T1 + T2) / (T1 * T2) * speed); float newValue = x + h * speed; speed = newSpeed; return targetValue + newValue; } |

As we mentioned in our previous post, we estimate second order system’s time constants to \(T_1=0.36, T_2=0.64\) to get similar response of original *SmoothDamp *with *smoothTime = *1. To calculate approximate values of these constants for any *smoothTime* we can use formulas:

\(T_1+T_2=smoothTime,\)

\(\frac{T_1}{T_2}=\frac{0.36}{0.64}\rightarrow T_1=\frac{9}{16}T_2,\)

\(\frac{25}{16}T_2=smoothTime,\)

\(T_1=\frac{9}{25}smoothTime, \quad T_2=\frac{16}{25}smoothTime\)

Above calculations are slightly rounded but if we compare our implementation of *SmoothDamp* with Unity’s one we get results that are shown below:

Our solution has a little faster response (see speed graphs).

Both functions *SmoothDamp *and *InertialDamp* do the same thing. They transform some values from their current values toward targets with defined dynamics (without overshoot). To prove that this code works we prepared simple WinForms application which simulates these functions. You can see how it works on screenshot below:

Of course, you can download this application from here: Download

# Unity example

Now it’s time to see how our code works in Unity. To test it we used modified example from SmoothDamp – dynamic smoothing! post. We use *SmoothDamp *and *InertialDamp* to create smoothed camera movement. Final result is shown below:

There are few thing that should be mentioned. Main scene contains two cameras. We can split screen into two parts so each camera renders its own part of world. To achieve this effect just set proper *Viewport Rect* values for both cameras:

We also added background grid with transparency to make differences more noticeable. *InertialDamp* acts more robust at the beginning but it reaches its target in slightly lazy way.

Full camera follower script is shown below:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
using UnityEngine; using System.Collections; public class FollowCamera : MonoBehaviour { public Character character; public bool inertial = false; public float smoothTime = 0.4f; private float smoothXspeed = 0f; private float smoothYspeed = 0f; private float zOffset; void Awake () { zOffset = transform.position.z; } void Update () { Vector3 newPos; if (inertial) newPos = InertialFollow (character.transform.position.x, character.transform.position.y); else newPos = SmoothFollow (character.transform.position.x, character.transform.position.y); transform.position = newPos; } private Vector3 InertialFollow (float targetX, float targetY) { float x = InertialDamp (transform.position.x, targetX, smoothTime); float y = InertialDamp (transform.position.y, targetY, smoothTime); return new Vector3 (x, y, zOffset); } private Vector3 SmoothFollow (float targetX, float targetY) { float x = SmoothDamp (transform.position.x, targetX, ref smoothXspeed, smoothTime); float y = SmoothDamp (transform.position.y, targetY, ref smoothYspeed, smoothTime); return new Vector3 (x, y, zOffset); } private float InertialDamp(float previousValue, float targetValue, float smoothTime) { float x = previousValue - targetValue; float newValue = x + Time.deltaTime * (-1f / smoothTime * x); return targetValue + newValue; } private float SmoothDamp(float previousValue, float targetValue, ref float speed, float smoothTime) { float T1 = 0.36f * smoothTime; float T2 = 0.64f * smoothTime; float x = previousValue - targetValue; float newSpeed = speed + Time.deltaTime * (-1f / (T1 * T2) * x - (T1 + T2) / (T1 * T2) * speed); float newValue = x + Time.deltaTime * speed; speed = newSpeed; return targetValue + newValue; } } |

As homework, you can extend this code to implement your own dynamics (for example with oscillations) and create *SmoothDamp* which transforms vectors instead of floats.

To download Unity project click here: Download

Do you use SmoothDamp in your games? Leave a comment below 🙂

Cheers,

Aliasing Games Team