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;
    i++;
}

Sequence Instance

So far we have covered the Sequence object and the Effect object, but we’ve left out the third object that you can use to control your movement effects; the SequenceInstance object. In this section we’re going to use SequenceInstance extensively, so here are some basic facts about it:

You can’t create a SequenceInstance object directly, but every time you call Movement.Run one will be created for that particular running instance and returned to you. The SequenceInstance is a very powerful variable since it allows you to modify many of the fields that the effect is using while the effect is running.

There are also a couple of options exposed through the SequenceInstance that are not available to be set in either the Effect or Sequence. These are SequenceInstance.Loop and SequenceInstance.Timescale.

In order to save memory allocations SequenceInstance variables use an object pooling system. This has a lot of advantages when it comes to performance, but it also has one downside: If you hold on to a SequenceInstance variable after the sequence that it points to has finished running, then that SequenceInstance will eventually be recycled and you could end up changing values on a completely random sequence.

There are two ways that you can address this issue:

  1. You can cache the value of SequenceInstance.RecycleCount, and stop using the reference if that value is ever incremented.
  2. Or you can set SequenceInstance.ExcludeFromPooling to true, which will keep that particular SequenceInstance from being recycled. As long as you release your reference at some point the memory manager will come and delete the SequenceInstance eventually.

Changing the Timescale

There are a couple of ways to control the rate of time on your movement effects.

  1. The first way is to change Unity’s Timescale in Edit/Project Settings/Time in the Unity Editor, or in code by changing Time.timeScale. Unity’s timescale is a float that defaults to 1. If you change it to 0.5 then your app will run at 50% speed. If it’s 0 then everything in your game will pause. If you set it to a negative number then your app will not run backwards.. it will just pause and not resume correctly, so avoid setting Unity’s timescale negative.
  2. Sequence.IgnoreUnityTimescale can be set on a sequence before you run it (it defaults to false.) When this is true, the sequence will calculate time independently of Unity’s timescale. This can be very handy for situations where you have a menu that you want to run movement effects on (like the window sliding in). You can set all of the effects that act on your menus to IgnoreUnityTimescale = true, and then you can pause time in the rest of the app by setting Unity’s timescale to 0.
  3. The last way is to change the timescale on a per-effect basis using SequenceInstance.Timescale. SequenceInstance.Timescale in that it’s a float that defaults to 1, 0.5 will run at 50% speed and 0 will pause the sequence. This value, however, can be set negative to run the sequence backwards through time. Unless Sequence.IgnoreUnityTimescale is true, this value will be multiplied with Unity’s Timescale. For example, if both Unity’s timescale and the sequence’s timescale are set to 0.5 then the sequence will be running at 1/4 speed (0.5 * 0.5).

Repeating Sequences

One very useful variable is SequenceInstance.Loop. So long as this boolean is set to true then the sequence will loop over to the first effect after reaching the last effect.

Demo #3 uses this line in order to create looping behavior:

Movement.Run(faceRandom).Loop = true;

 
Movement.Run returns the SequenceInstance. We don’t store the SequenceInstance in this case even though we could, we just change the Loop field to true. If nothing else was going on then this would create a sequence that would never end, but it doesn’t in this case because we’ve set Effect.ContinueIf so it can exit the sequence as soon as a variable is set to true. This is another way to run a sequence, it’s a bit like defining a while loop with while(true) and then putting a break statement in the loop somewhere.

Another way to use SequenceInstance.Loop would be to store the SequenceInstance object in some manager script and then set it to false from your manager script when something changed. This might be useful if, for example, you wanted to put some glittery sparkles around an important object in your scene. Once the user picked up the object you could simply set Loop to false. That way the sparkles would hang around for a little bit after you picked up the item rather than instantly going away.

Changing Key Values on the Fly

The SequenceInstance object has a few fields that can be set in either the Sequence object or the Effect object. These are SequenceInstance.Inertia, SequenceInstance.Elasticity, SequenceInstance.StartValue, and SequenceInstance.EndValue. It also have variables for SequenceInstance.Velocity and SequenceInstance.CurrentValue.

  1. In the Sequence object, Inertia and Elasticity set the initial values for these fields. However, by changing the values for Inertia and Elasticity in the SequenceInstance object you can change these values on the fly. There is a good demonstration of how that is done in Demo #1. In fact, if you were so inclined, you could pass one effect’s SequenceInstance as the reference variable of another effect and run an effect on the Inertia and Elasticity to make their values change over time. This could be used to create really interesting mechanics, like a car whose controls got more and more squishy the longer you tried to drive it.
  2. The Effect object has methods to RetrieveStartValue and RetireveEndValue. The result of those two actions are stored in the SequenceInstance under StartValue and EndValue. You can change the StartValue and EndValue while the sequence is running and the position will be updated as if those were always the StartValue and EndValue. As an example, lets take a simple Effect that runs a float from a StartValue of 1.0 to an EndValue of 2.0. If our effect happens to be 50% done and we change StartValue to -2.0 then the current value will jump from the 1.5 it is currently at to 0.0. Smoothing can smooth out that transition so that it moves over to the new position over several frames, but even with smoothing it’s a good idea to change StartValue or EndValue gradually and not make them jump around too much.
  3. SequenceInstance.CurrentValue is the current value after all smoothing effects. If no smoothing effects are enabled then changing this value won’t accomplish much, since it will just jump back into position instantly. However, if you have something like Elasticity enabled then changing CurrentValue can be somewhat like pulling on a rubber band.. as soon as you release it the effect will bounce back into place. This can be useful since it will have little or no effect on the eventual path of the object, but can give your app a good deal of juiciness.
  4. You won’t see a result from changing SequenceInstance.Velocity unless Elasticity is enabled. If you change it while Elasticity is enabled then you’ll see your object fly off in the direction you specified and then turn around and come back. Once again, as an example, if you had a zipline running along a track and something came by and hit the zipline from the side then you could transfer the intruding object’s velocity into the zipline and it would bounce back and forth on it’s track.