Unity——用代码实现序列帧动画(动态加载)

最近的项目用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;
    }
}

 

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值