A custom tweening library for Unity.
A game without anticipation is like playing tennis in the dark.
Games require anticipation, and anticipation requires animation.
Games are dynamic systems, and so they require dynamic animations. In order to do dynamic animations, we have to write code that can start, wait, stop and sequence actions.
There are many ways to do this, from manually checking a timestamp every frame to using a tweening library.
For a while, I’ve been using Unity’s coroutines. They’re great because they present the logic in sequential order. But they are somewhat cumbersome to write, even for small things.
I wanted to write a timing library that would take care of all the boilerplate code and allow me to make dynamic animations as easily as possible.
Example
Here’s an example of a time counter. The count increases and the graphics scale up and down every second.
Without Momentum
// Keep track of variables
int count = 0;
float currentTime = 0f;
float time = 1f;
Text text;
// In update loop
void Update()
{
// Add current time...
currentTime += Time.deltaTime;
// Did we pass 1 second?
if (currentTime >= time)
{
// Reduce 1 second and increase the loop count
currentTime -= time;
count++;
}
// Get the unit progress of the current animation
float progress = currentTime / time;
// Set text to current loop number
text.text = count.ToString();
// Scale the counter text up and down
text.transform.localScale = Vector3.one * (Ease.InOutSine(progress * 2f) + 1));
}
With Momentum
void Start()
{
// Create and run a new task
Task.Run()
// every 1 second
.Duration(1f)
// Repeat forever
.Loop()
// Set text to 0
.OnStart(task => text.text = task.CurrentLoop.ToString())
// Scale the counter text up and down
.OnUpdate(task => text.transform.localScale = Vector3.one * (Ease.InOutSine(task.Progress * 2f) + 1))
// Set text to current loop number
.OnLoop(task => text.text = task.CurrentLoop.ToString());
}
Complexity
The basic system was fairly simple to write. You queue in tasks and execute them all at once. When a task completes you check if it has a next task defined, which you’d queue in to be started in the next update.
I was a little surprised at how much complexity followed:
- Ordering tasks: sometimes one task needs to run before another.
- Destroying tasks: some tasks need to be linked to a game object otherwise you’ll get null reference exceptions.
- Testing: all kinds of issues like cyclic tasks.
- Performance: pooling tasks instead of constantly creating and destroying new ones.
- Writing Modules: we often animate the same things over and over again (Transformations, Colors, Audio).
Uses
The library was used in Turbo Balls and Swords and Forks. And while I love it, I don’t plan on using it in the future. There are far better tweening libraries out there like DOTween.
Code
https://github.com/IggyZuk/momentum
Takeaways
- Harder than you think – just use DOTween or another tweening library.