Dynamic Animation Part 2

In the first part of this series, I introduced easing equations and created a control which wrapped a lot of typical animation related functionality into a reusable control. In this post, we will create some extensions and a convenience class so that implementation of common animation tasks can be done with a single line of code.

Create a new class called “Tweener.cs”. This class is an abstract base class of other tweeners which handle concrete purposes. The base class will handle general issues like creating an easing control, hooking up events, and cleanup when the animation is complete. Concrete subclasses will be created for specific purposes like moving a transform’s position.

public abstract class Tweener : MonoBehaviour
{
	#region Properties
	public static float DefaultDuration = 1f;
	public static Func<float, float, float, float> DefaultEquation = EasingEquations.EaseInOutQuad;

	public EasingControl easingControl;
	public bool destroyOnComplete = true;
	#endregion

	#region MonoBehaviour
	protected virtual void Awake ()
	{
		easingControl = gameObject.AddComponent<EasingControl>();
	}

	protected virtual void OnEnable ()
	{
		easingControl.updateEvent += OnUpdate;
		easingControl.completedEvent += OnComplete;
	}

	protected virtual void OnDisable ()
	{
		easingControl.updateEvent -= OnUpdate;
		easingControl.completedEvent -= OnComplete;
	}

	protected virtual void OnDestroy ()
	{
		if (easingControl != null)
			Destroy(easingControl);
	}
	#endregion

	#region Event Handlers
	protected abstract void OnUpdate (object sender, EventArgs e);

	protected virtual void OnComplete (object sender, EventArgs e)
	{
		if (destroyOnComplete)
			Destroy(this);
	}
	#endregion
}

The concrete example we are working toward is the ability to modify the position of a transform. This requires a Vector3 to be the value we are interpolating instead of a float, which is what the easing control uses natively. So let’s create another class to handle that, called “Vector3Tweener.cs”

public abstract class Vector3Tweener : Tweener
{
	public Vector3 startValue;
	public Vector3 endValue;
	public Vector3 currentValue { get; private set; }

	protected override void OnUpdate (object sender, System.EventArgs e)
	{
		currentValue = (endValue - startValue) * easingControl.currentValue + startValue;
	}
}

Now its time to create the concrete subclasses which will actually be used. Here are several which can modify various properties of a transform:

public class TransformPositionTweener : Vector3Tweener 
{
	protected override void OnUpdate (object sender, System.EventArgs e)
	{
		base.OnUpdate (sender, e);
		transform.position = currentValue;
	}
}
public class TransformLocalPositionTweener : Vector3Tweener 
{
	protected override void OnUpdate (object sender, System.EventArgs e)
	{
		base.OnUpdate (sender, e);
		transform.localPosition = currentValue;
	}
}
public class TransformScaleTweener : Vector3Tweener 
{
	protected override void OnUpdate (object sender, System.EventArgs e)
	{
		base.OnUpdate (sender, e);
		transform.localScale = currentValue;
	}
}

Based on those examples it should be trivial to create several other tweeners, such as one to handle the EulerAngle rotation of a transform, etc. I decided to leave those as an exercise. Of course if you get stuck feel free to leave a comment.

Next I will create several extension methods to make the animation of this system super easy and clean to use.

public static class TransformExtensions
{
	public static Tweener MoveTo (this Transform t, Vector3 position)
	{
		return MoveTo (t, position, Tweener.DefaultDuration);
	}
	
	public static Tweener MoveTo (this Transform t, Vector3 position, float duration)
	{
		return MoveTo (t, position, duration, Tweener.DefaultEquation);
	}
	
	public static Tweener MoveTo (this Transform t, Vector3 position, float duration, Func<float, float, float, float> equation)
	{
		TransformPositionTweener tweener = t.gameObject.AddComponent<TransformPositionTweener> ();
		tweener.startValue = t.position;
		tweener.endValue = position;
		tweener.easingControl.duration = duration;
		tweener.easingControl.equation = equation;
		tweener.easingControl.Play ();
		return tweener;
	}
	
	public static Tweener MoveToLocal (this Transform t, Vector3 position)
	{
		return MoveToLocal (t, position, Tweener.DefaultDuration);
	}
	
	public static Tweener MoveToLocal (this Transform t, Vector3 position, float duration)
	{
		return MoveToLocal (t, position, duration, Tweener.DefaultEquation);
	}
	
	public static Tweener MoveToLocal (this Transform t, Vector3 position, float duration, Func<float, float, float, float> equation)
	{
		TransformLocalPositionTweener tweener = t.gameObject.AddComponent<TransformLocalPositionTweener> ();
		tweener.startValue = t.localPosition;
		tweener.endValue = position;
		tweener.easingControl.duration = duration;
		tweener.easingControl.equation = equation;
		tweener.easingControl.Play ();
		return tweener;
	}

	public static Tweener ScaleTo (this Transform t, Vector3 scale)
	{
		return ScaleTo (t, scale, Tweener.DefaultDuration);
	}
	
	public static Tweener ScaleTo (this Transform t, Vector3 scale, float duration)
	{
		return ScaleTo (t, scale, duration, Tweener.DefaultEquation);
	}
	
	public static Tweener ScaleTo (this Transform t, Vector3 scale, float duration, Func<float, float, float, float> equation)
	{
		TransformScaleTweener tweener = t.gameObject.AddComponent<TransformScaleTweener> ();
		tweener.startValue = t.localScale;
		tweener.endValue = scale;
		tweener.easingControl.duration = duration;
		tweener.easingControl.equation = equation;
		tweener.easingControl.Play ();
		return tweener;
	}
}

With all of that in place, lets go back to the demo script we had originally, which we used to move the cube across the screen. Look how easy it is to animate an object now!

public class Temp : MonoBehaviour
{
	void Start ()
	{
		transform.MoveTo( new Vector3(5, 0, 0), 3f, EasingEquations.EaseInOutQuad );
	}
}

Note that because the MoveTo method was overloaded, I didn’t have to specify an easing equation, or even a duration, and in those instances, the “defaults” specified in the Tweener class would be used instead.

Occasionally you still may want to get access to the tweener, or its easing control in order to access some of the other options. Here is another sample- still nice and short, but very powerful:

public class Temp : MonoBehaviour
{
	void Start ()
	{
		Tweener tweener = transform.MoveTo( new Vector3(5, 0, 0), 3f, EasingEquations.EaseInOutQuad );
		tweener.easingControl.loopCount = -1;
		tweener.easingControl.loopType = EasingControl.LoopType.PingPong;
	}
}

6 thoughts on “Dynamic Animation Part 2

  1. Hey, you may want to fix the code showing the TransformExtensions; the ScaleTo functions with fewer parameters are referencing the MoveTo function rather than the complete ScaleTo function.

  2. I’ve been all over the internet looking for something like this. Fantastic work. Any plans to extend this to projectile? (Like LaunchTo(taegetVector, LaunchAngle, time).

    1. Actually, I did something exactly like that in an old project, but I didn’t understand the math very well. It could be worth taking another look at to see if I can learn it well enough to teach it.

  3. This can be useful to somebody I guess. I’ve been trying to make a extension to change the color of my text with this, and i wanna share the results.

    I’m using TMPro for this

    — Class Vector4Tweener.cs —
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class Vector4Tweener : Tweener
    {
    public Vector4 startTweenValue;
    public Vector4 endTweenValue;
    public Vector4 currentTweenValue { get; private set; }

    protected override void OnUpdate ()
    {
    currentTweenValue = (endTweenValue – startTweenValue) * currentValue + startTweenValue;
    base.OnUpdate ();
    }
    }

    — class TextMeshProAnimationExtension.cs —

    using System.Collections;
    using System;
    using UnityEngine;
    using TMPro;

    public static class TextMeshProAnimationExtension
    {
    public static Tweener ChangeColor (this TextMeshProUGUI t, Color32 change)
    {
    return ChangeColor(t, change, Tweener.DefaultDuration);
    }

    public static Tweener ChangeColor (this TextMeshProUGUI t, Color32 change, float duration)
    {
    return ChangeColor(t, change, duration, Tweener.DefaultEquation);
    }

    public static Tweener ChangeColor (this TextMeshProUGUI t, Color32 change, float duration, Func equation)
    {
    ColorTweener tweener = t.gameObject.AddComponent ();
    //Vector 4 vs Color necesito cambiarlo
    Color coloraux;

    tweener.startTweenValue = t.color;
    coloraux = change;
    tweener.endTweenValue = coloraux;
    tweener.duration = duration;
    tweener.equation = equation;
    tweener.Play ();
    return tweener;
    }
    }

    — class ColorTweener.cs —
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using TMPro;

    public class ColorTweener : Vector4Tweener
    {
    TextMeshProUGUI textmeshPro;

    void Awake ()
    {
    textmeshPro = gameObject.GetComponent();
    }

    protected override void OnUpdate ()
    {
    base.OnUpdate();
    textmeshPro.color = currentTweenValue;
    }
    }

    I don’t know if it’s the most elegant thing, but I really think that could be useful to modify the color or opacity of your texts as simple as the Vector3 movement. Thank you very much for this tutorials.

Leave a Reply

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