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;
	}
}
Advertisements

2 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.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s