Creating 3D animation sequences with WaveEngine Game Actions

In this article we are going to see how to create a simple 3D animation sequence, it is very useful when you want to create initial tutorials or real time animations in your game.

Game Actions are the easier way to create an action stack . If you have not seen it before you can take a look to this Game Action article before continue reading.

First we are going to explain what we need. We will create a simple animation with translate, rotate and scale transformations. So we need to create an Animation unit to store the animation data between two points:

  
public struct AnimationSlot
{
    [Flags]
    public enum TransformationTypes
    {
        Position = 0x2,
        Rotation = 0x4,
        Scale = 0x8,
    };

    public TransformationTypes TransformationType { get; set; }
    public TimeSpan TotalTime { get; set; }
    public Vector3 StartPosition { get; set; }
    public Vector3 StartRotation { get; set; }
    public Vector3 StartScale { get; set; }
    public Vector3 EndPosition { get; set; }
    public Vector3 EndRotation { get; set; }
    public Vector3 EndScale { get; set; }
}

If you want to create an animated sequence then you will use something like this:

Next you need to create a behavior to animate 3D entities:

  
[DataContract]
public class Animation3DBehavior : Behavior
{
    public event EventHandler Completed;

    [RequiredComponent]
    private Transform3D transform3D = null;

    private TimeSpan time;
    private AnimationSlot animation;
    private bool completed;

    public AnimationSlot AnimationSlot
    {
        get
        {
            return this.animation;
        }

        private set
        {
            this.animation = value;
            
            if (this.animation.TransformationType.HasFlag(AnimationSlot.TransformationTypes.Position))
            {
                this.transform3D.LocalPosition = this.animation.StartPosition;
            }
            if (this.animation.TransformationType.HasFlag(AnimationSlot.TransformationTypes.Rotation))
            {
                this.transform3D.LocalRotation = this.animation.StartRotation;
            }
            if (this.animation.TransformationType.HasFlag(AnimationSlot.TransformationTypes.Scale))
            {
                this.transform3D.LocalScale = this.animation.StartScale;
            }
        }
    }

    protected override void DefaultValues()
    {
        base.DefaultValues();
        this.completed = true;
    }

    public void BeginAnimation(AnimationSlot animatinoSlot)
    {
        this.AnimationSlot = animatinoSlot;
        this.time = TimeSpan.Zero;
        this.completed = false;
    }

    protected override void Update(TimeSpan gameTime)
    {
        if (completed)
        {
            return;
        }

        this.time += gameTime;
        if (time <= this.animation.TotalTime)
        {
            float amount = (float)(this.time.TotalSeconds / this.animation.TotalTime.TotalSeconds);

            if (this.animation.TransformationType.HasFlag(AnimationSlot.TransformationTypes.Position))
            {
                Vector3 deltaPosition = Vector3.Lerp(this.animation.StartPosition, this.animation.EndPosition, amount);                    
                this.transform3D.LocalPosition = deltaPosition;
            }
            if (this.animation.TransformationType.HasFlag(AnimationSlot.TransformationTypes.Rotation))
            {
                Vector3 deltaRotation = Vector3.Lerp(this.animation.StartRotation, this.animation.EndRotation, amount);                    
                this.transform3D.LocalRotation = deltaRotation;                    
            }
            if (this.animation.TransformationType.HasFlag(AnimationSlot.TransformationTypes.Scale))
            {
                Vector3 deltaScale = Vector3.Lerp(this.animation.StartScale, this.animation.EndScale, amount);                    
                this.transform3D.LocalScale = deltaScale;                    
            }
        }
        else
        {                
            if (this.animation.TransformationType.HasFlag(AnimationSlot.TransformationTypes.Position))
            {
                this.transform3D.LocalPosition = this.animation.EndPosition;
            }
            if (this.animation.TransformationType.HasFlag(AnimationSlot.TransformationTypes.Rotation))
            {
                this.transform3D.LocalRotation = this.animation.EndRotation;
            }
            if (this.animation.TransformationType.HasFlag(AnimationSlot.TransformationTypes.Scale))
            {
                this.transform3D.LocalScale = this.animation.EndScale;
            }

            this.completed = true;
            if (this.Completed != null)
            {
                this.Completed(this, new EventArgs());
            }
        }
    }
}

You  need to create a custom game action too. This custom game action takes an AnimationSlot and set it to Animation3DBehavior.

  
public class Animation3DGameAction : GameAction
{
    private static int instances;

    private AnimationSlot animationSlot;

    private Animation3DBehavior animationBehavior;

    public Animation3DGameAction(AnimationSlot animationSlot, Animation3DBehavior animationBehavior, Scene scene = null)
        : base("Animation3DGameAction" + instances++, scene)
    {
        this.animationSlot = animationSlot;
        this.animationBehavior = animationBehavior;
    }

    protected override void PerformRun()
    {
        this.animationBehavior.Completed += this.OnAnimationCompleted;

        this.animationBehavior.BeginAnimation(this.animationSlot);
    }

    private void OnAnimationCompleted(object sender, EventArgs e)
    {
        this.PerformCompleted();
        this.animationBehavior.Completed -= this.OnAnimationCompleted;
    }
}

Now create an animation sequence on a scene, create a cube primitive, add an Animation3DBehavior component from Wave Visual Editor and get its Animation3DBehavior with the following code:

  
Animation3DBehavior cubeAnimationBehavior = this.EntityManager.Find("cube").FindComponent<Animation3DBehavior>();

Create two animationSlot:

  
AnimationSlot animationSlot1 = new AnimationSlot()
{
    TransformationType = AnimationSlot.TransformationTypes.Position,
    TotalTime = TimeSpan.FromSeconds(1.5f),
    StartPosition = new Vector3(0, 0, 0),
    EndPosition = new Vector3(0, 2, 0),
};

AnimationSlot animationSlot2 = new AnimationSlot()
{
    TransformationType = AnimationSlot.TransformationTypes.Rotation,
    TotalTime = TimeSpan.FromSeconds(1.5f),
    StartRotation = new Vector3(0, 0, 0),
    EndRotation = new Vector3(0, (float)Math.PI * 2, 0),
};

You must add Game Action namespace to scene class before you can use extended Game Action methods:

  
using WaveEngine.Components.GameActions;

Finally, create the animation sequence using the Animation3DGameAction that you have created before:

  
this.animationSequence = this.CreateGameAction(this.CreateGameAction(new Animation3DGameAction(animationSlot1, cubeAnimationBehavior, this)))
                                                    .ContinueWith(new Animation3DGameAction(animationSlot2, cubeAnimationBehavior, this));
                                                    
this.animationSequence.Run();                                                    

Build and run the project:

View post on imgur.com

Another sample project with multiple animations and more complex entities:

View post on imgur.com

All code can be download from Github.

I hope you find it useful when you want to create animation sequences in your game. It is a simple animation system that you can extend with relative and absolute transformations, more transformation types etc…

Published by

Jorge Canton

He is an engineer in computer science, #WaveEngine team member is a technology enthusiast determined to stay on the cutting edge of development. He is a very dynamic person who loves solving challenges, puzzles and riddles. He usually spends his free time with his family or playing videogames on his console or mobile phone and plays the piano to relax.

Leave a Reply

Your email address will not be published. Required fields are marked *