Inertia and Elasticity

The scenario is this: You have an object. It might be a button on the screen, it might be a rock in your environment, it might be the camera.. lets just call it “your object.” You want to move your object between 10 different waypoints. So your object is going to change direction a lot, but you don’t want it to jerk from one point to the next. What you need to do is smooth out the movement.

Movement over Time has two very different ways to accomplish smoothing. In this post we are going to go over smoothing by effecting the value that is returned, which is done by setting Inertia and Elasticity. These values are properties of the Sequence object and together they give you a lot of power. Both of them should be set to a percentage which is represented by a float value between 0 and 1. 0 means 0% and 1 means 100%. Lets go over the difference between Inertia and Elasticity real quickly:

Inertia can be thought of as “a bias towards being inert.” If you set it to 1 then your sequence will not move. Inertia will always produce a value that lags behind the value that your effect would hold if Inertia was set to 0. Inertia will never overshoot. If your object was a boat then the path drawn by inertia would be the path of a water-skier who was being pulled behind that boat.

Elasticity is a spring function. Elasticity can bounce around, overshoot, and in some situations it can become unstable and start jumping around on the screen. Despite all that, it can be used to make some really nice effects. For instance, when your object reaches the edge of the screen you can use elasticity to make it bounce before settling down. You can also use Elasticity to make your object follow a course exactly until there’s a rapid change in direction and then it will swing around and match course with the new direction.

There is also a boolean that you can set: Sequence.StartWithVelocity. This boolean only makes a difference if Elasticity is greater than 0. When this is true the first effect will start out moving, and when it’s false the first effect will start out stopped, and then rush to catch up.

Elasticity and Inertia can be combined. In fact, if you are using Elasticity it’s usually a good idea to tamper it with a bit of Inertia so that sudden movements don’t make things fly out of control. If you set Inertia to a value that’s greater than Elasticity then your effect can become unstable, especially when the frame rate starts to drop.

There are a couple of things to keep in mind when using Inertia and/or Elasticity:

  1. Since both of these types of smoothing change the value that is passed to OnUpdate, they can both end up leaving your object in a different place than the value returned by RetrieveEnd. In the case of Inertia your object will always end up somewhere behind the end value when time runs out on the effect. If you want the effect to run a little longer so that your object ends up at the end value then you can define the Effect.RunEffectUntilValue action.
  2. When you’re using smoothing and going from one effect to another inside a sequence it’s important to remember that you don’t generally want to start the next effect at the current value of your object. Instead, you want to start the next effect at the last end value. If you are running an effect with smoothing and you notice your object “hopping back” or “appearing to hit a wall and then going on” then this is probably the cause. Use lastEndValue rather than the current position of your object.

Easing Functions

Inertia and Elasticity are one way to do smoothing in Movement / Time, the other way is by applying an easing function to your effect. The two approaches each have their own strengths and weaknesses.

Inertia and Elasticity work by adding a “lag” to the position before it is returned. This “lag” allows a sequence to gracefully handle changes in direction and speed. I&E is well suited for complex sequences on objects in your world. Easing functions are a very different approach to smoothing. Normally the “PercentDone” variable moves in a straight line from 0% done to 100% done. Easing functions simply make that line progress in more complicated fashion (a non-straight line). Easing functions work well when you are moving UI elements around, like menus or buttons. The advantage of easing functions is that they automatically scale with the size of the transition and they will always leave your effect at the end point. The biggest disadvantage of the easing functions is that it’s really hard to make them handle changes in direction, so they are often badly suited for moving around objects in your environment.

In order to use Easing functions you have to first include the proper namespace: “using MovementEffects.Extensions;” Then you just need to assign the CalculatePercentDone function in your effect to one of the easing functions. If you need some kind of exotic easing like SinInPow3Out, then you can just extend the Easing class in MoT Extensions.cs (they’re all quite simple.)

Here is a graph of the current easing functions:Easing Graphs

Continue, Hold, and Complete

Effect.ContinueIf can be a very important action. This allows you to test for some condition and stop processing if that condition is ever false.

A common thing to test for is “someGameObject == true” because if a gameObject has been disposed then a test of true or false on that gameObject will return false. If there’s even a tiny chance of this happening then you should check for it. If you skip the check for dead objects and one of your objects dies then your app will end up throwing a lot of exceptions in that rare case that it does happen. If you set your objects to null when they should be dead then you can check for null.

Some other ways you might use Effect.ContinueIf is to check to make sure an object is visible and/or enabled before you try to move it. OnDone (which is covered later in this post) will fire if the effect was stopped in the middle of doing it’s thing, and OnComplete will always fire. So you can use those actions to do the final thing (like enable the menu) but skip the transition period when the object is not visible. Some types of objects will actually throw errors if you try to move them when they aren’t visible, and it’s usually a better user experience for the user when they don’t see the tail end of movements that they never saw the beginning of, so checking ContinueIf and using OnDone or OnComplete is a good practice to get into.

Effect.HoldEffectUntil is an action that takes the reference and a float. The float represents the time the effect has been held so far, so that you can time out the hold action if that number gets too large. If you set HoldEffectUntil then the effect will poll this delegate every frame before the effect actually starts running until the action returns true.

HoldEffectUntil has two common uses:

  1. It can be used to set up an effect now, but hold off actually running that effect until some condition is met. For instance in Demo #3 we set up all the effects to return to home, but hold them until the home base receives an OnCollisionEnter event.
  2. The other common use is as a stop and go mechanism. For example, lets say that you have a character who is walking along using the sequence “put right foot forward, put left foot forward, repeat.” You could have logic in your script that set a boolean (lets call it beingLookedAt) to true when the camera can see the player. If you define HoldEffectUntil on those two effects to hold until beingLookedAt was false then the characters would walk only when the player wasn’t watching them (kind of like that cat in that video).

There are two actions which can by surprisingly handy in certain situations: Effect.OnDone and Sequence.OnComplete.

Both of these actions pass in the reference and don’t return any values. As the name suggests, they are executed when the Effect or the Sequence completes. They have several common uses:

  1. There are quite a few instances in which an Effect is really just a prelude to doing some action. For instance, if you are deleting an element on the screen you might want to move that element over to a trash can icon and once it’s done moving then call Destroy. Or, when opening a menu, you might want to slide the menu out and then, once that has finished, enable the buttons on it. In these cases you want to assign one of these two fields to your ending action.
  2. You can use these actions to chain different types of effects together. For example, you may have noticed that the type of variable that an effect manipulates can’t be changed between one effect and another inside a sequence. So if you wanted to move an item 3 units in the x axis and then rotate it 20 degrees you would have to define two separate sequences and then chain them together using OnDone or OnComplete.
  3. You can also use one of these functions if you want one effect to turn into more than one effect. For instance, if you were using an effect to control billiard balls on a pool table and the billiard hit two other pool balls at the same instant you could start a sequence for both of the new balls using OnDone or OnComplete.
  4. You could use OnComplete to repeat the sequence if it should only repeat when some complicated condition is met. If it’s a simple condition then it will be far more efficient and easier to use the Loop field (which will be covered in a future section.)

If you’re not sure whether to use OnDone or OnComplete in a particular instance then you should probably use OnComplete. OnComplete will always execute (unless you set SequenceInstance.SupressOnCompleteCallbacks to true)

Run Until

There are two fields available which control when the effect considers itself done. They are Effect.RunEffectUntilTime and Effect.RunEffectUntilValue.

Effect.RunEffectUntilTime takes two floats. Float #1 holds the current time, which starts at zero and counts up (unless time is running backwards, in that case it will start at the end and run to zero). Float #2 holds the time that the effect should end at (which will be the same as Effect.Duration unless you mess with the timescale). The function should return true if we should keep running or false if it’s time to stop.

Effect.RunEffectUntilValue takes three values. They are CurrentValue, StartValue, and EndValue (in that order). StartValue and EndValue are the values that were returned by Effect.RetrieveStart and Effect.RetrieveEnd. CurrentValue is the calculated value after all smoothing effects. It is the same value that was just passed into OnUpdate. This function also returns true to keep running and false to stop.

The majority of your movement effects will probably leave both of these actions as their default value of null. If you leave them null then the effect will run until time runs out. RunEffectUntilTime can be defined to make the effect quit early, or late, or according to any other time based logic you can think of. RunEffectUntilValue is usually used when you have smoothing applied and you want the effect to keep running until the end value is actually reached. If you define both of these at the same time.. well, that’s cute, but only RunEffectUntilValue will be used.

You generally want to define RunEffectUntilValue something like this:

moveEffect.RunEffectUntilValue = 
    (curVal, startVal, endVal) => (endVal - curVal).sqrMagnitude > 16f;

The above block calculates how far we are from our destination at this moment and quits once we are within 4 units (the square root of 16 is 4). We use square magnitude because it avoids calculating the square root, so it runs faster.

Using Update, FixedUpdate, or LateUpdate

There is an enum attached to the Sequence named SequenceTiming. This enum controls which update loop the sequence will be run in.

* Note: SequenceTiming does not change the rate of time for the sequence. That is done by setting SequenceInstance.SequenceTimescale, which will be covered in the advanced section.

The Sequence.SequenceTiming enum has 4 states: Default, Update, FixedUpdate, and LateUpdate. Default will use Movement.DefaultSequenceTiming, which will be set to Update unless you change it. You will want to run most of your effects in the Update loop, but there are a few instances where you might want to use the FixedUpdate or LateUpdate loops instead.

FixedUpdate is for physics. If your effect is using AddForce, AddTorque, or any of their variants then you’ll want to use this timing.

LateUpdate is almost Update, but it’s run after all the regular Update effects have finished. LateUpdate is mostly used when you are controlling the camera, but it might also be used in situations like when a turret is tracking a target. The idea is that any time you are tracking or following a moving target you want to always be using it’s current position that frame in your calculations. You can’t always guarantee that whichever code is updating your target’s current position will execute before the current code block which is tracking your target’s position, unless you run the tracking code in LateUpdate.

It may not seem like a huge deal that you might be using the position from the last frame rather than the current frame, but keep in mind that if the processor gets tied up then the last frame could be 1/3 of a second old (or even longer if Time.maxDeltaTime has been changed in your project). If this was a turret then the user could cause the turret to shoot at the spot they were standing 1/3 of a second ago just by causing a framerate drop! If the camera is following an object on a slow computer 1/3 of a second could be long enough for the object to have left the screen entirely!

The Movement and Timing Objects

Some assets require a lengthy setup process in order to run properly in your scene, Movement over Time is not one of those assets. All that’s required in order to run an effect is to include the MovementEffects namespace, build a sequence, and call Movement.Run. However, you can achieve an additional level of control by knowing how the Movement and Timing objects work.

If you change nothing than the first time you call Movement.Run, the Movement object will either add itself to your EventSystem gameObject (if you have one in your scene) or to a new gameObject called “Movement Effects”. Movement is like a singleton, except that a singleton guarantees that there is only a single instance, whereas the Movement object guarantees that there is at least one instance.

If you like you can add the Movement and Timing objects to any gameObject in your scene and Movement over Time will use that instance rather than creating a new one. If the Movement object or the gameObject that it is attached to are disabled or deleted then all movement effects will immediately stop, so you probably want to keep the Movement object on a gameObject where that will never happen.

As you can see in the image on the right, the movement object does have two settings that you can change: Default Sequence Timing and Pool Instance Variables. If you change the default sequence timing then all new sequences that are initialized with their timing set to Default will use the value you select here. If you uncheck Pool Instance Variables then no new objects will be added to the instance pools. The rest of these values are for reference so you can see how the instance pools are being used. If you try to change them they will just change back again next tick.

The Trouble with For Loops

There is an unpatched bug in the mono compiler that Unity runs on. It only shows itself when you try to reference the variable that you are iterating through in a for loop or a foreach loop, and when you are creating a closure in a lambda expression.

Lets see an example. Neither the for or the foreach blocks will execute like you expect them to:

public Transform[] Waypoints;

Effect<Controller, Vector3> effect = new Effect<Controller, Vector3>();
[ ... ] // populate the fields of the effect.
Sequence<Controller, Vector3> sequence = new Sequence<Controller, Vector3>();

sequence = effect * Waypoints.Length;

for (int i = 0; i < sequence.Effects.Length; i++)
    sequence.Effects[i].RetrieveEnd = system => Waypoints[i].position;

int i = 0;
foreach(Transform waypoint in Waypoints)
    sequence.Effects[i++].RetrieveEnd = system => waypoint.position;

Both loops are creating a closure. In the for loop “Waypoints[i]” creates a closure, and in the foreach loop “waypoint” creates a closure. A closure is ok to use in the RetrieveEnd field, since that is only executed once per effect. The trouble is that in both of these cases the item that ends up getting used is not the current item that you passed in, but instead every item will be set to the position of the last item in the list.

Fortunately, this bug only affects variables that are declared at the top of a for or foreach loop, so if you create the variable in any other way it will work fine. Here is one way that you can work around it:

for (int i = 0; i < sequence.Effects.Length; i++)
    int localI = i;
    sequence.Effects[i].RetrieveEnd = system => Waypoints[localI].position;

foreach(Transform waypoint in Waypoints)
    Transform localCopy = waypoint;
    sequence.Effects[i++].RetrieveEnd = system => localCopy .position;

Another way to avoid the bug is to convert your for loop into a while loop, like this:

int i = 0;
while(i < sequence.Effects.Length)
    sequence.Effects[i].RetrieveEnd = system => Waypoints[i].position;