Tomus Blogus Noteworthy coding techniques and discussion

24Apr/11Off

Defining real-time animations with fluent expressions

Fluent expressions and LINQ

.NET 3.0 introduced Language Integrated Queries (LINQ) which allow us to query, filter and otherwise manipulate data using a syntax a little bit resemblant of traditional SQL. The SQL-like syntax is cool, but importantly everything that can be expressed in LINQ can also be expressed as a standard C# expression built from method calls (often using "extension methods") and lambdas. The expressions exhibit a left-to-right flow that mirrors the order of operations applied to the data, making them easier to read and write - which is why the style is commonly referred too as "fluent". This is the structure we will emulate for defining animated sequences in code.

The animation loop problem

I needed to add 2D animated sequences to our XNA Xbox game. Our artist would pass me a number of sprite-sheets and a Shockwave Flash animation as an example of how everything should look. The game needed to cycle through frames, move different "sprites" around the screen and coordinate all the movements to start and finish at roughly the right times. And it had to fit into the XNA framework, which gives you a basic runtime loop and the amount of elapsed time between the previous frame so you can advance the gameplay accordingly. The difficulty is that the XNA game loop pushes you into a state machine type of configuration whereas a sequential style would have fit more naturally. So typically the code would look something like:

switch (state) {
case 0:
	// ... Move/animate sprite here.
	timer -= elapsedTime;
	if (timer < 0) {
		state = 1;
		timer = 0.5f;
	}
case 1:
	// ...

Which can be a little difficult to read and maintain, especially when we start branching into more complicated sequences that coordinate multiple sprites.

 

A fluent animation sequence

So what if we could instead write something like the following?

var realtimeSequence = mySprite
	.MoveTo(320, 240, 1.0f)
	.Then(Seq.Wait(2.5f))
	.Then(Seq.AtSameTime(
			mySprite.MoveTo(480, 240, 1.0f),
			otherSprite.MoveTo(500, 100, 0.5f)
	));
Animator.Instance.Add(realtimeSequence);

This effectively reads as: "Move mySprite to position (320,240) taking 1 second, then wait 2.5 seconds, then at the same time move mySprite to position (480,240) taking 1 second, and otherSprite to position (500, 100) taking 0.5 seconds."
'Fluent' right? The expression evaluates to an object that we can pass to an "animator" which handles advancing the animated sequence over the subsequent game frames until it has completed.

 

The groundwork

So to start with we need an interface to specify what an animated sequence is and how it can be interacted with to drive the animation.

public interface IAnimSeq
{
	bool Advance(float timestep);
}

Not too complicated. Our animator just needs to call "Advance(...)" on each registered animated sequence until it returns false, which indicates it has completed. This also straight-forward to implement:

public class Animator
{
    private readonly List<IAnimSeq> sequences = new List<IAnimSeq>();

    private Animator() {}
    public static readonly Animator Instance = new Animator();

    public void Add(IAnimSeq seq) { sequences.Add(seq); }

    public void Advance(float timestep)
    {
        var copy = sequences.ToList();
        foreach (var seq in copy)
        {
            if (!seq.Advance(timestep))
            {
                sequences.Remove(seq);
            }
        }
    }
}

Here we have a basic singleton implementation and an Add() method for adding new sequences. Advance() can be called every game update in order to advance all active animation sequences along.
The reason we take a copy of the original list is so we can iterate it while removing elements from the original (the List class doesn't like to be iterated and modified at the same time). We could probably optimize this to avoid copying a whole list every game update, but in my experience it performs well enough.

 

Basic building blocks

We continue by building some basic animated sequences to start as building blocks. We can start with a basic "Wait", "WaitUntil" and "WaitWhile". These simply wait a predefined length of time, or until some expression returns true ("WaitUntil") or false ("WaitWhile"). To keep things concise we'll wrap up the constructors in a static "Seq" class.

public class WaitSeq : IAnimSeq
{
    private float duration;

    public WaitSeq(float duration) { this.duration = duration; }

    public bool Advance(float timestep)
    {
        duration -= timestep;
        return duration > 0;
    }
}

public class WaitWhileSeq : IAnimSeq
{
    private Func<bool> condition;

    public WaitWhileSeq(Func<bool> condition) { this.condition = condition; }

    public bool Advance(float timestep)
    {
        return condition();
    }
}

 

public static class Seq
{
    public static IAnimSeq Wait(float duration) { return new WaitSeq(duration); }
    public static IAnimSeq WaitWhile(Func<bool> condition) { return new WaitWhileSeq(condition); }
    public static IAnimSeq WaitUntil(Func<bool> condition) { return new WaitWhileSeq(() => !condition()); }

    // We will add more later...
}

So now we can construct pauses with Seq.Wait(1.0f) (for example), or wait for a condition to elapse with Seq.Wait(() => State.enemyCount == 0) (using a lambda expression to define the completion criteria).
For brevity we re-used the WaitWhileSeq class for the WaitUntil(...) method by negating the criteria.

 

Moving sprites around

Waiting by itself isn't very interesting. Its real purpose is to help sequence and coordinate more interesting actions, like moving objects around the screen.
In our XNA game a "sprite" is basically an object that stores screen coordinates of an image to draw on the screen each frame. I won't go into the full implementation, but consider an object with X and Y pixel coordinate properties that we wish to update in order to move an image around the screen.
We would like to use a fluent syntax like "mySprite.MoveTo(x, y, duration)". And to avoid polluting our sprite class with methods that are related to our animated sequence system, we choose to use the .NET language feature "extension methods".

public class SpriteMoveToSeq : IAnimSeq
{
    private Sprite sprite;
    private float duration;
    private float elapsed;
    private float startX, startY;
    private float endX, endY;

    public SpriteMoveToSeq(Sprite sprite, float x, float y, float duration)
    {
        this.sprite = sprite;
        this.endX = x;
        this.endY = y;
        this.duration = duration;
    }

    public bool Advance(float timestep)
    {
        if (elapsed == 0)
        {
            startX = sprite.X;
            startY = sprite.Y;
        }

        elapsed += timestep;
        float frac = elapsed / duration;
        if (frac > 1.0f) frac = 1.0f;
        sprite.X = (1.0f - frac) * startX + frac * endX;
        sprite.Y = (1.0f - frac) * startY + frac * endY;

        return elapsed < duration;
    }
}

This slides the X and Y properties each from a start value to a finish value over time.
It's important that we set the start position when the animation actually starts executing and not in the constructor, as quite often the sprite can have moved between the two events.

public static class SpriteExtensions
{
    public static IAnimSeq MoveTo(this Sprite sprite, float x, float y, float duration)
    {
        return new SpriteMoveToSeq(sprite, x, y, duration);
    }

    // Will add more later...
}

The "this" keyword in our MoveTo method denotes it as an extension method extending the Sprite class. It means that we can write:

mySprite.MoveTo(20, 300, 2.0f);

and the compiler will quietly substitute it with:

SpriteExtensions.MoveTo(mySprite, 20, 300, 2.0f);

This is really not much more than a short-hand macro, but it allows us to achieve the 'fluent' left-to-right flow as we will see soon.

 

Combining sequences

In order to make this useful we need a way to combine sequences together.
We need a way to build sequences that perform one action after another (we are calling them animated sequences after all). While we're at it we'll add support for executing actions at the same time in parallel.

public class SequentialSeq : IAnimSeq
{
    private readonly Queue<IAnimSeq> children = new Queue<IAnimSeq>();

    public SequentialSeq(params IAnimSeq[] elements)
    {
        foreach (var elem in elements)
        {
            children.Enqueue(elem);
        }
    }

    public bool Advance(float timestep)
    {
        if (children.Any())
        {
            if (!children.Peek().Advance(timestep))
            {
                children.Dequeue();
            }
        }

        return children.Any();
    }
}

public class ParallelSeq : IAnimSeq
{
    private readonly List<IAnimSeq> children = new List<IAnimSeq>();

    public ParallelSeq(params IAnimSeq[] elements)
    {
        children.AddRange(elements);
    }

    public bool Advance(float timestep)
    {
        var copy = children.ToList();
        foreach (var child in copy)
        {
            if (!child.Advance(timestep))
            {
                children.Remove(child);
            }
        }

        return children.Any();
    }
}

 

public static class Seq
{
    // Previous methods...

    public static IAnimSeq AtSameTime(params IAnimSeq[] elements) { return new ParallelSeq(elements); }
}

We've defined a couple of objects for storing and executing sequential and parallel child sequences. We can use Seq.AtSameTime() to execute a number of sequences in parallel.
To execute them sequentially we will create an extension method on the IAnimSeq interface itself. This will allow us to take any existing sequences and append a ".Then(...)" to the end to add another sequence to the end of our animation, achieving the desired left-to-right flow or "fluent"ness.

public static class SeqExtensions
{
    public static IAnimSeq Then(this IAnimSeq first, IAnimSeq second)
    {
        return new SequentialSeq(first, second);
    }
}

Which is all we need to create a .Then() method we can append to any IAnimSeq object.

 

Wrapping up

So now we have the basic building blocks of a fluent expression based animation system, and enough to compile and execute the fluent expression that we proposed earlier.

The building-block nature of the system makes it straight-forward to extend. We just create more classes implementing IAnimSeq and add the appropriate factory methods or extension methods to construct them in a 'fluent', readable way.
A logical progression is to add support for repeating a sequence (which is a little bit complicated by the need to restart already executing sequences - see the example code for such an implementation). Also adding the ability to execute arbitrary code at a point in the sequence (hint: lambda expression).
With a little effort we can create a very flexible, expressive system that can be applied to a wide range of situations. In our game I used it for background animations, particle effects, cut-scenes and even for driving and coordinating screen transitions. The advantages in flexibility and readability allowed me to try more combinations and tweak and adjust more parameters in a space of time than I normally would be able to, leading to more interesting and better fine-tuned animated sequences.

I hope you find this useful if you happen to be working on something similar in the .NET universe.
You can grab working source code of a simple example here.