最近的项目用SpriteRenderer+Animation的方式播放序列帧,unity编辑器里是可以正常播放没问题的,但打包出去后,发现一闪一闪的,什么玩意啊。。。然后搜索了一下文章,看到个大佬写的,换成image播放序列帧就可以了。
原文链接:https://blog.csdn.net/SerenaHaven/article/details/79273114
好了问题解决是解决了。然后我发现挂上脚本后,因为脚本上有个精灵体数组,要自己一个一个添加,比较麻烦。于是我改了下动态加载,代码如下:
using UnityEngine;
using UnityEngine.UI;
using System;
/// <summary>
/// 序列帧动画播放器
/// 支持UGUI的Image和Unity2D的SpriteRenderer
/// </summary>
public class FrameAnimator : MonoBehaviour
{
/// <summary>
/// 序列帧数
/// </summary>
public int _framesCount;
/// <summary>
/// 序列帧名字前半部分 例如: image_1.jpg中的 image_为前半部分
/// </summary>
public string _framesName;
/// <summary>
/// resource文件夹下的文件夹路径 例如:Resources/Test/Sprites 则直接赋值 Test/Sprites 序列帧放在Sprites文件夹下就好
/// </summary>
public string _folderPath;
/// <summary>
/// 序列帧
/// </summary>
private Sprite[] frames = null;
/// <summary>
/// 帧率,为正时正向播放,为负时反向播放
/// </summary>
public float Framerate { get { return framerate; } set { framerate = value; } }
[SerializeField] private float framerate = 20.0f;
/// <summary>
/// 是否忽略timeScale
/// </summary>
public bool IgnoreTimeScale { get { return ignoreTimeScale; } set { ignoreTimeScale = value; } }
[SerializeField] private bool ignoreTimeScale = true;
/// <summary>
/// 是否循环
/// </summary>
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 event Action FinishEvent;
//目标Image组件
private Image image;
//目标SpriteRenderer组件
private SpriteRenderer spriteRenderer;
//当前帧索引
private int currentFrameIndex = 0;
//下一次更新时间
private float timer = 0.0f;
//当前帧率,通过曲线计算而来
private float currentFramerate = 20.0f;
/// <summary>
/// 重设动画
/// </summary>
public void Reset()
{
currentFrameIndex = framerate < 0 ? frames.Length - 1 : 0;
}
/// <summary>
/// 从停止的位置播放动画
/// </summary>
public void Play()
{
this.enabled = true;
}
/// <summary>
/// 暂停动画
/// </summary>
public void Pause()
{
this.enabled = false;
}
/// <summary>
/// 停止动画,将位置设为初始位置
/// </summary>
public void Stop()
{
Pause();
Reset();
}
//自动开启动画
void Start()
{
image = this.GetComponent<Image>();
spriteRenderer = this.GetComponent<SpriteRenderer>();
//初始化精灵体数组
frames = new Sprite[_framesCount];
for (int i = 0; i < _framesCount; i++)
frames[i] = Resources.Load<Sprite>(_folderPath + "/" + _framesName + i) as Sprite;
#if UNITY_EDITOR
if (image == null && spriteRenderer == null)
{
Debug.LogWarning("No available component found. 'Image' or 'SpriteRenderer' required.", this.gameObject);
}
#endif
}
void Update()
{
//帧数据无效,禁用脚本
if (frames == null || frames.Length == 0)
{
this.enabled = false;
}
else
{
//从曲线值计算当前帧率
float curveValue = curve.Evaluate((float)currentFrameIndex / frames.Length);
float curvedFramerate = curveValue * framerate;
//帧率有效
if (curvedFramerate != 0)
{
//获取当前时间
float time = ignoreTimeScale ? Time.unscaledTime : Time.time;
//计算帧间隔时间
float interval = Mathf.Abs(1.0f / curvedFramerate);
//满足更新条件,执行更新操作
if (time - timer > interval)
{
//执行更新操作
DoUpdate();
}
}
#if UNITY_EDITOR
else
{
Debug.LogWarning("Framerate got '0' value, animation stopped.");
}
#endif
}
}
//具体更新操作
private void DoUpdate()
{
//计算新的索引
int nextIndex = currentFrameIndex + (int)Mathf.Sign(currentFramerate);
//索引越界,表示已经到结束帧
if (nextIndex < 0 || nextIndex >= frames.Length)
{
//广播事件
if (FinishEvent != null)
{
FinishEvent();
}
//非循环模式,禁用脚本
if (loop == false)
{
currentFrameIndex = Mathf.Clamp(currentFrameIndex, 0, frames.Length - 1);
this.enabled = false;
return;
}
}
//钳制索引
currentFrameIndex = nextIndex % frames.Length;
//更新图片
if (image != null)
{
image.sprite = frames[currentFrameIndex];
}
else if (spriteRenderer != null)
{
spriteRenderer.sprite = frames[currentFrameIndex];
}
//设置计时器为当前时间
timer = ignoreTimeScale ? Time.unscaledTime : Time.time;
}
}
我想要的效果是每次显示都要从第一帧开始,但现在的问题是:如果我隐藏掉也就是setactive设为false,那么下次再激活的时候是从你刚刚隐藏的那一帧开始的。如果想要每次初始化,只需在生命周期函数OnEnable中调用一次重置的方法即可:
private void OnEnable()
{
Reset();
}
后面又更新了一版RawImage,为什么呢,图片一多了,一起拖到Resources文件夹里还是比较慢的,然后还有手动转一下精灵体,又要等好久。。。然后又改了一版。需要的自己选择!
using UnityEngine;
using UnityEngine.UI;
using System;
/// <summary>
/// 序列帧动画播放器
/// 支持UGUI的Image和Unity2D的SpriteRenderer
/// </summary>
public class FrameAnimator : MonoBehaviour
{
/// <summary>
/// 序列帧数
/// </summary>
public int _framesCount;
/// <summary>
/// 序列帧名字前半部分 例如: image_1.jpg中的 image_为前半部分
/// </summary>
public string _framesName;
/// <summary>
/// resource文件夹下的文件夹路径 例如:Resources/Test/Texture 则直接赋值 Test/Texture 序列帧放在Texture文件夹下就好
/// </summary>
public string _folderPath;
/// <summary>
/// 序列帧
/// </summary>
private Texture[] frames = null;
/// <summary>
/// 帧率,为正时正向播放,为负时反向播放
/// </summary>
public float Framerate { get { return framerate; } set { framerate = value; } }
[SerializeField] private float framerate = 20.0f;
/// <summary>
/// 是否忽略timeScale
/// </summary>
public bool IgnoreTimeScale { get { return ignoreTimeScale; } set { ignoreTimeScale = value; } }
[SerializeField] private bool ignoreTimeScale = true;
/// <summary>
/// 是否循环
/// </summary>
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 bool _isPlayFrameAnimator { get { return IsPlayFrameAnimator; } set { IsPlayFrameAnimator = value; } }
[SerializeField] private bool IsPlayFrameAnimator = false;
/// <summary>
/// 结束事件
/// 在每次播放完一个周期时触发
/// 在循环模式下触发此事件时,当前帧不一定为结束帧
/// </summary>
public event Action FinishEvent;
//目标Image组件
private RawImage image;
//当前帧索引
private int currentFrameIndex = 0;
//下一次更新时间
private float timer = 0.0f;
//当前帧率,通过曲线计算而来
private float currentFramerate = 20.0f;
/// <summary>
/// 重设动画
/// </summary>
public void Reset()
{
currentFrameIndex = framerate < 0 ? frames.Length - 1 : 0;
}
/// <summary>
/// 从停止的位置播放动画
/// </summary>
public void Play()
{
this.enabled = true;
}
/// <summary>
/// 暂停动画
/// </summary>
public void Pause()
{
this.enabled = false;
}
/// <summary>
/// 停止动画,将位置设为初始位置
/// </summary>
public void Stop()
{
Pause();
Reset();
}
//每次显示时,初始化当前帧数
private void OnEnable()
{
Reset();
}
void Start()
{
image = GetComponent<RawImage>();
//初始化精灵体数组
frames = new Texture[_framesCount];
for (int i = 0; i < _framesCount; i++)
frames[i] = Resources.Load<Texture>(_folderPath + "/" + _framesName + i);
#if UNITY_EDITOR
if (image == null)
{
Debug.LogWarning("No available component found. 'RawImage' required.", this.gameObject);
}
#endif
}
void Update()
{
//帧数据无效,禁用脚本
if (frames == null || frames.Length == 0)
{
this.enabled = false;
}
else
{
//是否允许播放
if (!PlayFrameAnimator()) return;
//从曲线值计算当前帧率
float curveValue = curve.Evaluate((float)currentFrameIndex / frames.Length);
float curvedFramerate = curveValue * framerate;
//帧率有效
if (curvedFramerate != 0)
{
//获取当前时间
float time = ignoreTimeScale ? Time.unscaledTime : Time.time;
//计算帧间隔时间
float interval = Mathf.Abs(1.0f / curvedFramerate);
//满足更新条件,执行更新操作
if (time - timer > interval)
{
//执行更新操作
DoUpdate();
}
}
#if UNITY_EDITOR
else
{
Debug.LogWarning("Framerate got '0' value, animation stopped.");
}
#endif
}
}
/// <summary>
/// 判断是否允许播放
/// </summary>
/// <returns>是否允许播放</returns>
public bool PlayFrameAnimator()
{
return _isPlayFrameAnimator;
}
//具体更新操作
private void DoUpdate()
{
//计算新的索引
int nextIndex = currentFrameIndex + (int)Mathf.Sign(currentFramerate);
//索引越界,表示已经到结束帧
if (nextIndex < 0 || nextIndex >= frames.Length)
{
//广播事件
if (FinishEvent != null)
{
FinishEvent();
}
//非循环模式,禁用脚本
if (loop == false)
{
currentFrameIndex = Mathf.Clamp(currentFrameIndex, 0, frames.Length - 1);
this.enabled = false;
return;
}
}
//钳制索引
currentFrameIndex = nextIndex % frames.Length;
//更新图片
if (image != null)
{
image.texture = frames[currentFrameIndex];
}
//设置计时器为当前时间
timer = ignoreTimeScale ? Time.unscaledTime : Time.time;
}
}
最近有空,之前的脚本其实倒放(原版的也不行)有问题,我一直不知道,现在重新码一遍吧~把一些不常用的功能取消了,并优化了下,要是用到什么动画曲线来调帧率的话,还是选择上面的脚本好一些
using UnityEngine;
using UnityEngine.UI;
/*
*
* Writer:June(改)
*
* Date: 2020.6.6
*
* Function:序列帧动画播放器
*
* Remarks:支持Image和SpriteRenderer
*
*/
public class FrameAnimatorPlayScript : MonoBehaviour
{
/// <summary>
/// 序列帧帧数
/// </summary>
private int _framesCount;
/// <summary>
/// 序列帧名字前半部分 例如: image_1.jpg中的 image_为前半部分
/// </summary>
public string _framesName;
/// <summary>
/// resource文件夹下的文件夹路径 例如:Resources/Test/Sprites 则直接赋值 Test/Sprites 序列帧放在Sprites文件夹下就好
/// </summary>
public string _folderPath;
/// <summary>
/// 序列帧精灵体数组
/// </summary>
private Sprite[] frames = null;
/// <summary>
/// 帧率
/// </summary>
public float Framerate { get { return framerate; } set { framerate = value; } }
[Range(20, 60)]
[SerializeField] private float framerate = 20.0f;
/// <summary>
/// 是否忽略timeScale
/// </summary>
public bool IgnoreTimeScale { get { return ignoreTimeScale; } set { ignoreTimeScale = value; } }
[SerializeField] private bool ignoreTimeScale = true;
/// <summary>
/// 是否循环
/// </summary>
[SerializeField] private bool loop = true;
public bool Loop { get { return loop; } set { loop = value; } }
//目标Image组件
private Image image;
//目标SpriteRenderer组件
private SpriteRenderer spriteRenderer;
//当前帧索引
private int FrameIndex = 0;
//下一次更新时间
private float timer = 0.0f;
/// <summary>
/// 是否倒放
/// </summary>
public bool IsRewind { get; private set; }
/// <summary>
/// 播放状态
/// </summary>
public bool IsPlay { get; private set; }
/// <summary>
/// 倒放
/// </summary>
public void PlayBackward()
{
//循环状态下先把循环关掉,倒放不能循环
if (loop) loop = false;
IsRewind = true;
IsPlay = true;
}
/// <summary>
/// 重设动画
/// </summary>
public void Reset()
{
FrameIndex = IsRewind ? _framesCount - 1 : 0;
}
/// <summary>
/// 从停止的位置播放动画
/// </summary>
public void PlayForward()
{
IsRewind = false;
IsPlay = true;
}
/// <summary>
/// 暂停动画
/// </summary>
public void Pause() { IsPlay = false; }
/// <summary>
/// 停止动画,将位置设为初始位置
/// </summary>
public void Stop()
{
Pause();
Reset();
}
//获取序列帧
void Start()
{
image = GetComponent<Image>();
spriteRenderer = GetComponent<SpriteRenderer>();
if (image == null && spriteRenderer == null)
{
Debug.LogError("没找到组件", gameObject);
}
//读序列帧
ReadFrames();
}
/// <summary>
/// 动态读取序列帧
/// </summary>
private void ReadFrames()
{
//文件夹中的图片数
_framesCount = GetCountInFolder(Application.dataPath + "/Resources/" + _folderPath);
//初始化精灵体数组
frames = new Sprite[_framesCount];
for (int i = 0; i < frames.Length; i++)
frames[i] = Resources.Load<Sprite>(_folderPath + "/" + _framesName + i) as Sprite;
}
/// <summary>
/// 获取文件夹内文件数(不包括meta文件)
/// </summary>
/// <param name="_path">目标路径</param>
/// <returns>文件数</returns>
private int GetCountInFolder(string _path)
{
int _folderNumberSum = 0;
string[] fileList = System.IO.Directory.GetFileSystemEntries(_path);
foreach (string item in fileList)
{
if (!item.Contains(".meta"))
_folderNumberSum++;
}
return _folderNumberSum;
}
void Update()
{
//帧数据无效,禁用脚本
if (frames == null || frames.Length == 0 || !IsPlay) return;
//帧率有效 控制帧率
if (framerate != 0)
{
//获取当前时间
float time = ignoreTimeScale ? Time.unscaledTime : Time.time;
//计算帧间隔时间
float interval = Mathf.Abs(1.0f / framerate);
//满足更新条件,执行更新操作
if (time - timer > interval)
{
//执行更新操作
DoUpdate();
}
}
else Debug.LogError("帧率必须大于0");
}
//具体更新操作
private void DoUpdate()
{
//是否打开循环
if (!loop)
{
FrameIndex = Mathf.Clamp(FrameIndex, 0, frames.Length - 1);
//正向播放判断||倒放判断
if ((!IsRewind && FrameIndex == (frames.Length - 1)) || (IsRewind && FrameIndex == 0))
{
IsPlay = false;
return;
}
}
//帧数赋值,更新图片
if (image != null) image.sprite = frames[FrameIndex];
if (spriteRenderer != null) spriteRenderer.sprite = frames[FrameIndex];
if (!IsRewind) FrameIndex++;//正向播放索引自增
else
{
FrameIndex--;//倒放,索引自减
if (FrameIndex < 0) FrameIndex = 0;
}
FrameIndex = FrameIndex % frames.Length;
//设置计时器为当前时间
timer = ignoreTimeScale ? Time.unscaledTime : Time.time;
}
}