What is Movement / Time

Movement over Time is a framework for accomplishing movement cleanly and efficiently in Unity. In order to use the framework you define a few fields which tell Movement over Time what to do at key points in time. Once you have the parameters of the effect defined the way you want them you can start it running by calling Movement.Run.

The Movement over Time framework has been highly polished to minimize memory allocations, maximize speed, and allow you to control every aspect of the movement effect so that you can create any custom sequence that you can imagine!

Movement over Time is designed to make it easy to add juiciness to your game or app. Juiciness is the opposite of sterile. Juiciness is when a button expands as you hover over it, when elements nudge over to the side to make room for a new button on the screen, or when random objects in your world have eyes which watch you as you pass. It’s when the environment seems to be semi-intelligent and responding to the user.

Movement over Time makes it easy and fun to add juiciness to your app. It is designed to integrate with your existing coding structure. Movement over Time will be useful whether you are just starting a project or are two weeks from release.

How It Works

If you’ve ever used Unity’s coroutines before then the following block might look familiar:

public Transform Item;
public Vector3 TargetPosition;

void Start()
{
     StartCoroutine(MoveItem(Item, TargetPosition, 5f));
}
 
IEnumerator MoveItem(Transform item, Vector3 target, float timeframe)
{
     float startTime = Time.time;
     Vector3 startPos = item.position;
 
     while(startTime + timeframe <= Time.time)
     {
          item.position = Vector3.Lerp(startPos, target, (startTime - Time.time) / timeframe);
 
          yield return null;
     }
}

 
The code above runs a coroutine that will update the position of the item every frame so that it gets closer and closer to the position of targetuntil, after 5 seconds, it finally reaches target. The key processing happens inside the while loop where start.position gets set to the result of Vector3.Lerp. It’s the line with the Vector3.Lerp command that really does all the interesting work, but in order to accomplish that one simple movement we have to surround it with approximately 15 lines of boilerplate code!

Movement over Time does the same basic thing: It also runs a loop which calculates a new position every frame to create a smooth transition between the starting position and the ending position. We’ve improved the basic loop above in several ways that you probably never considered (like making it work even when time is running backwards) and we’ve optimized each and every line.

With Movement over Time the syntax is a little different: Instead of writing out a function full of boilerplate code, you just define the key actions which will be executed at the key points in the loop. These actions aren’t variables, they are typically anonymous functions which do one simple thing and do it well.

Effect<Controller, float> goUp = new Effect<Controller, float>
{
     Duration = 5f,
     RetrieveEnd = (me) => me.TargetPosition,
     OnUpdate = (me, value) => me.Item.position = value
};
 
Sequence<Controller, float> rise = new Sequence<Controller, float>
{
     Reference = this,
     RetrieveSequenceStart = me => me.Item.position
};
 
rise.Add(goUp);
 
Movement.Run(rise);

 
The above code is an example of how you start a sequence in Movement over Time.

  1. Define an effect, which holds a few key methods and the duration.
  2. Define a sequence which holds the rest of the methods we need in order to perform this particular transition.
  3. Add the effect to the sequence.
  4. Start the sequence running.

Using lambda expressions

Many of the fields you specify on the movement effect are actually actions. For example, one of the most important actions is the RetrieveEnd field, which retrieves the location that the sequence should move towards and end up at.

Every action, including the RetrieveEnd action, receives a reference as input. This reference is usually the object or script which holds all the information necessary to do the work you need. Many times the easiest thing to do is to set Reference to the “this” pointer.

In order to define RetrieveEnd one option is to create a function in your script that accepts a reference variable and returns the value, like so:

float RetrieveEndFunction(Controller reference)
{
      return 5f;
}

 
You can then set RetrieveEnd to point to the function you just created:

var moveOver = new Effect<Controller, float>();
moveOver.RetrieveEnd = RetrieveEndFunction;

 
However, it can get quite cumbersome to define a new function for every action that you want to define. Fortunately the C# compiler accepts lambda expressions, which are a quick way to define a function without even giving that function a name. A pointer to that nameless function is then stored in the RetrieveEnd field. Movement over Time can then use that function to retrieve the end value whenever it needs it. You know it’s a lambda expression when you see the => sign:

var moveOver = new Effect<Controller, float>();
moveOver.RetrieveEnd = reference => 5f;

 
If the function accepts more than one variable then you need parenthesis, and if you want the function to run more than one instruction then you need brackets, but in this case you need neither.

Templated types

Effects and Sequences are declared like this:

Effect<ReferenceType, ValueType> myEffect = new Effect<RefrenceType, ValueType>();
Sequence<ReferenceType, ValueType> mySequence = new Sequence<RefrenceType, ValueType>();

 
This section is about the ReferenceType and the ValueType. These are both called template type arguments.

ReferenceType is the type of the Sequence.Reference variable. It can be the type of any class, struct, or variable. The type just has to match the type of the reference variable. It can even be a built in type, like int, but if you make it a built in type then you’re doing it wrong. If you’re leaving the Reference variable as null then make this type object. However, if you leave Reference as null then you’re also doing it wrong.

ValueType is the type of value that you are acting on. It can be float, double, Vector2, Vector3, Vector4, Rect, Color, or Quaternion. Whenever you see anything in these documents that says “function x passes a value” then that value is of type ValueType.

You can’t put effects in a sequence unless their template type arguments match. If you want to form a sequence that switches types you’ll have to make two separate sequences and chain them together using OnComplete.

The 6 Essential Fields

There are 6 essential fields that really define a sequence. 4 of them are attached to an effect object and the other 2 are attached to a sequence object. Once you master these 6 fields you’ll be able to define any sequence, all of the other fields are just ways to control the specifics of how the effect plays out.

The 6 fields are:

Effect.Duration
Effect.RetrieveStart
Effect.RetrieveEnd
Effect.OnUpdate
Sequence.Reference
Sequence.RetrieveSequenceStart
  1. Effect.Duration is simple enough, it’s a float. It holds the number of seconds that the sequence should take to get from the start value to the end value.
  2. Effect.RetrieveStart is an action. It receives the Reference (like all actions do) and it also receives a value that I always label “lastEndValue” (or lastEnd if I’m in a hurry.) lastEnd is the value that was returned by the last call to Effect.RetrieveEnd. Usually, if you’re running a sequence with several effects in it, you’ll want to start the current effect at the point where the last effect left off. In fact, if you leave this field as null then the start value will be set to the last effect’s end value by default.
  3. Effect.RetireveEnd receives the Reference and returns the end value (or target) of the effect.
  4. Effect.OnUpdate is the heart of the whole affair. This function receives a reference and the current computed value. This function should be as short as possible, since it will be called every frame. It’s one task is to set whichever aspect of your environment that you are performing the effect on to the value that is passed in.
  5. Sequence.Reference should be assigned to whichever object holds the data that will need to be referenced in order to do the effect. This can be any object of any type. When you define the Effect or Sequence variable you specify the type of the Reference as the first templated argument (i.e. new Effect<"Reference Type", "Variable type you are acting on">). Often the Reference can be set to type of the current script that you are using, and then you can set Sequence.Reference to “this”. When you do that your reference can access any public or private variables in the current script. If you set Reference to the this pointer a good convention is to name it “me” when it’s passed in to any of the other actions.
  6. Sequence.RetrieveSequenceStart is a little different then the Effect.RetrieveStart function. Remember that Effect.RetrieveStart receives the lastEndValue each time it is called, which begs the question “what value is supplied to the first effect in the sequence?” This field has the answer: it’s the value that is returned by Sequence.RetrieveSequenceStart.. or a zero value if you leave this field null. You don’t have to use this field, but you’ll find that it comes in very handy if you end up re-ordering your list of effects, using smoothing, or reversing time.

Closure is not a Good Thing

Before we move on we need to take a quick break to have a little talk about closures.

Take a look at the following function:

void foo()
{
     Vector3 Offset = new Vector3(0f, 1f, 2f);
     
     Effect<Controller, Vector3> moveEffect = new Effect<Controller, Vector3>();
     moveEffect.Duration = 3f;
     moveEffect.RetrieveEnd = me => me.TargetLocation;
     moveEffect.OnUpdate = 
        (me, value) => me.MovingObject.transform.position = value + Offset;

     Movement.Run(this, moveEffect);
}

 

There are actually two problems with this block. The first is that we aren’t setting the RetrieveSequenceStart or RetrieveStart for the effect, so this effect will default to starting at (0, 0, 0) regardless of where the object happens to be at the moment, but that can be easily fixed by adding one of those two actions. The second problem is far more tricky.

The second problem is that we are using the variable Offset inside the action that we are storing in OnUpdate. Offset is a local variable that is scoped to the function foo, and by the time the OnUpdate method is called foo has already finished executing and quit. See the problem? How do we reference a variable every frame for three seconds when that variable has a different scope than our function?

However, this function will compile and run. So what is actually happening here?

What happens is the compiler recognizes that you are referencing a variable here that exists in a different context, so it takes a snapshot of that variable and bundles it along side the lambda expression in memory. Making a single copy of the variable wouldn’t be so bad, but the compiler can’t actually be sure you didn’t modify the variable while accessing it, so it ends up making another copy of Offset for every single time OnUpdate is run. This will eat up your memory fast.

Closures can be created for other reasons as well. A common mistake would be to use the line MovingObject.transform.position = value rather than me.MovingObject.transform.position = value. If you don’t put the me. in front of the line you’re breaking that bubble and accessing outside variables.

This is an important point. So important, in fact, that the Movement over Time framework checks for closures in the OnUpdate function before running every sequence. If you accidentally create a closure you’ll start getting a bunch of warnings in your console. Don’t ignore them, they’re important!

Closures are ok in functions that are executed once per effect, like RetrieveEnd. They only really hurt you in Effect.OnUpdate, Effect.RunEffectUntilTime, Effect.RunEffectUntilValue, and Effect.CalculatePercent.

Conceivably, you could run into a situation where you just have to create a closure. If you understand the performance implications but want to do it anyway then you can set Sequence.SupressClosureWarnings to true so that the warnings won’t pop up in your inspector. Use that field at your own risk.

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.