前一阵子写了一个播放序列帧的小脚本,本以为只是临时用一用,但是后来发现这个脚本能频繁的用在项目的其他地方,所以决定优化一下。
这个脚本参考了NGUI的 UI2DSpriteAnimation 脚本,里面有很多不错的思想,这里只是做了一些修修补补的工作。

只需要将这个脚本挂载到任意游戏物体上,animImage变量为要播放序列帧的UGUI Image
OK,上脚本

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 播放2D序列帧
/// </summary>

public class SpriteAnimation : MonoBehaviour
{
	/// <summary>
	/// 播放序列帧的UI Image
	/// </summary>
	[SerializeField]
	private Image animImage;

	/// <summary>
	/// 当前播放的帧在数组中的索引。
	/// 正向播放且不循环时 数字不可大于等于序列帧数组索引。
	/// 倒序播放且不循环时 数字不能小于等于0
	/// </summary>
	public int frameIndex = 0;

	/// <summary>
	/// 每秒播放多少多少帧(不可为0) 
	/// 数字大于0为正向播放,数字小于0为倒序播放
	/// </summary>
	[SerializeField]
	private int framerate = 20;

	/// <summary>
	/// 是否受TimeScale的影响
	/// </summary>
	public bool ignoreTimeScale = true;

	/// <summary>
	/// 是否循环播放
	/// </summary>
	public bool isLoop = true;

	/// <summary>
	/// 存放所有序列帧sprite的数组
	/// </summary>
	public Sprite[] frames;

	/// <summary>
	/// 序列帧更新时间
	/// </summary>
	private float mUpdate = 0f;

	/// <summary>
	/// Continue playing the animation. If the animation has reached the end, it will restart from beginning
	/// </summary>
	public void Play()
	{
		if (!enabled && !isLoop)
		{
			//判断是正向播放还是倒序播放
			int newIndex = framerate > 0 ? frameIndex + 1 : frameIndex - 1;

			//
			if (newIndex < 0 || newIndex >= frames.Length)
			{
				frameIndex = framerate < 0 ? frames.Length - 1 : 0;
			}
		}

		enabled = true;
		UpdateSprite();
	}

	/// <summary>
	/// 暂停播放
	/// </summary>
	public void Pause()
	{
		enabled = false;
	}

	/// <summary>
	/// 回到最初的序列帧,等待播放
	/// </summary>
	public void ResetToBeginning()
	{
		frameIndex = framerate < 0 ? frames.Length - 1 : 0;
		UpdateSprite();
	}

	void Update()
	{
		//获取当前的游戏内时间,用于和序列帧更新时间做比较
		float time = ignoreTimeScale ? Time.unscaledTime : Time.time;

		//这里将 当前游戏时间(time) 和 上次更新的序列帧时间(mUpdate) 进行比对
		//如果 mUpdate<time,说明:更新序列帧之前的时间+序列帧两张间隔时间 小于 游戏内时间,则需要将两个时间线统一,然后再更新序列帧图片
		//如果 mUpdate>=time,说明:更新序列帧之前的时间+序列帧两张间隔时间 大于等于 游戏内时间,需要等待游戏内时间继续增长,不用更新序列帧图片
		//这样做法可以最大程序上保证两张序列帧之间的间隔约等于 1/每秒播放帧数
		if (mUpdate < time)
		{
			mUpdate = time;

			//判断是正向播放还是倒序播放
			int newIndex = framerate > 0 ? frameIndex + 1 : frameIndex - 1;

			//判断 如果在不循环播放时
			//倒序播放 序列帧当前索引小于0,正向播放 序列帧索引大于序列帧总长度
			//则停止播放
			if (!isLoop && (newIndex < 0 || newIndex >= frames.Length))
			{
				enabled = false;
				return;
			}

			//判断序列帧索引是否越界
			frameIndex = RepeatIndex(newIndex, frames.Length);
			UpdateSprite();
		}
	}

	/// <summary>
	/// 更新序列帧图片
	/// </summary>
	void UpdateSprite()
	{
		float time = ignoreTimeScale ? Time.unscaledTime : Time.time;

		//每次更新序列帧图片时,将序列帧时间进行更新
		//新的序列帧时间为:当前游戏内时间+每两帧图片之间的间隔时间(间隔时间就是1秒/序列帧数量)
		mUpdate = time + Mathf.Abs(1f / framerate);

		//更新序列帧图片
		animImage.sprite = frames[frameIndex];
	}

	//用于判断序列帧索引是否越界
	public int RepeatIndex(int val, int max)
	{
		if (max < 1) return 0;
		while (val < 0) val += max;
		while (val >= max) val -= max;
		return val;
	}
}