前一阵子写了一个播放序列帧的小脚本,本以为只是临时用一用,但是后来发现这个脚本能频繁的用在项目的其他地方,所以决定优化一下。
这个脚本参考了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;
}
}