序列帧动画经常用到,最直接的方式就是用Animation录制。但某些情况下这种方式并不是太友好,需要靠代码的方式进行序列帧动画的实现。
代码实现序列帧动画,基本的思路是定义一个序列帧的数组/列表,根据时间的流逝来确定使用哪一帧并更新显示。
NGUI的UI2DSpriteAnimation已经实现了此功能,但是它支持的目标只有Native2D的SpriteRenderer组件或者NGUI自身的UI2DSprite组件,并不支持UGUI的Image组件。
当然可以通过改写源码的方式来添加对Image组件的支持,不过秉着学习的目的,我这里重新写了一个针对Image组件的序列帧动画播放器。
代码如下,注释写的很详细了,不再赘述。
using UnityEngine;
using UnityEngine.UI;
[RequireComponent (typeof(Image))]
public class UIFrameAnimator : MonoBehaviour
{
/// <summary>
/// 序列帧
/// </summary>
public Sprite[] Frames{ get { return frames; } set { frames = value; } }
[SerializeField]private Sprite[] frames = null;
/// <summary>
/// 帧率,为正时正向播放,为负时反向播放
/// </summary>
public int Framerate { get { return framerate; } set { framerate = value; } }
[SerializeField]private int framerate = 20;
/// <summary>
/// 是否忽略timeScale
/// </summary>
public bool IgnoreTimeScale{ get { return ignoreTimeScale; } set { ignoreTimeScale = value; } }
[SerializeField]private bool ignoreTimeScale = true;
//是否循环
public bool Loop{ get { return loop; } set { loop = value; } }
[SerializeField]private bool loop = true;
//动画曲线
[SerializeField]private AnimationCurve curve = new AnimationCurve (new Keyframe (0, 1, 0, 0), new Keyframe (1, 1, 0, 0));
/// <summary>
/// 当前帧索引
/// </summary>
public int CurrentFrameIndex{ get { return currentFrameIndex; } }
private int currentFrameIndex = 0;
//下一次更新时间
private float timer = 0.0f;
//目标Image组件
private Image image;
/// <summary>
/// 从停止的位置播放动画
/// </summary>
public void Play ()
{
//帧数据有效
if (frames != null && frames.Length > 0) {
//脚本被禁用并且是非循环模式
if (this.enabled == false && loop == false) {
//计算下一帧索引
int newIndex = framerate > 0 ? currentFrameIndex + 1 : currentFrameIndex - 1;
if (newIndex < 0 || newIndex >= frames.Length)
currentFrameIndex = framerate < 0 ? frames.Length - 1 : 0;
}
//启用脚本
this.enabled = true;
//执行更新操作
DoUpdate ();
}
}
/// <summary>
/// 暂停动画
/// </summary>
public void Pause ()
{
this.enabled = false;
}
/// <summary>
/// 重设动画
/// </summary>
public void ResetToBeginning ()
{
currentFrameIndex = framerate < 0 ? frames.Length - 1 : 0;
DoUpdate ();
}
//自动开启动画
void Start ()
{
Play ();
}
void Update ()
{
//帧数据无效,禁用脚本
if (frames == null || frames.Length == 0) {
this.enabled = false;
} else if (framerate != 0) {//帧率有效
//获取当前时间
float time = ignoreTimeScale ? Time.unscaledTime : Time.time;
//获取曲线值
float curveValue = curve.Evaluate ((float)currentFrameIndex / framerate);
curveValue = curveValue == 0 ? 0.01f : curveValue;
float curvedFramerate = curveValue * framerate;
//计算帧间隔时间
float interval = Mathf.Abs (1.0f / curvedFramerate);
//满足更新条件,执行更新操作
if (time - timer > interval) {
//执行更新操作
DoUpdate ();
}
}
}
//具体更新操作
private void DoUpdate ()
{
//计算新的索引
int nextIndex = currentFrameIndex + (int)Mathf.Sign (framerate);
//非循环模式并且索引越界,表示动画已播放完成,禁用脚本
if (loop == false && (nextIndex < 0 || nextIndex >= frames.Length)) {
enabled = false;
return;
}
//将新的索引定位在有效范围内
while (nextIndex < 0) {
nextIndex += frames.Length;
}
while (nextIndex >= frames.Length) {
nextIndex -= frames.Length;
}
//使用计算出的索引
currentFrameIndex = nextIndex;
//更新图片
image = image ?? this.GetComponent<Image> ();
image.sprite = frames [currentFrameIndex];
//设置计时器为当前时间
timer = ignoreTimeScale ? Time.unscaledTime : Time.time;
}
}