动画事件

近来想在PlayMaker上Action上加一个播放动画并等待完成的功能,于是乎落入了井底深坑。

虽然处处设坎,但是也还是有两个可行的解决方案(注意这里可行的意思是只能在PlayMaker的Action上进行编辑操作,而不是通过加组件这种增加操作复杂性来解决问题):

动态添加AnimationEvent做回调(同步事件)
模拟当前动画进度做回调(非同步事件)

同步回调 AnimationEvent

先来说说事件这个,实现相对简单,因为事件是侦对AnimationClip而添加,所以这里实现大概又分以下几层:

给AnimationClip添加与移除事件。
实现通过Animator的Play与CrossFade来取到当前AnimationClip。
通过Mono实现回调事件与动画事件的结合。
把以上方法封装成扩展方法(一句代码搞定的那种)。
先看看最终的使用形态:

//Play
animator.Play(stateName, layer, normalizedTime, Callback);
//CrossFade
animator.CrossFade(stateName, layer, transitionDuration, Callback);
//等待当前动画播放完成
animator.AddFinishEventToCurrentState(Callback, layer);public void Callback()
{Debug.Log("我播完了");
}

开始实现:

  1. 给AnimationClip添加与移除事件。
    static private Dictionary<AnimationClip, AnimationEvent> dictClipToEndEvent = new Dictionary<AnimationClip, AnimationEvent>();/// <summary>/// 因为Unity的Clip的共用性质,一旦加入了一个之后,所有动画都加入了/// 所以会使用到DontRequireReceiver/// </summary>/// <param name="clip"></param>/// <param name="functionName"></param>static public void AddFinishEventToAnimationClip(this AnimationClip clip, string functionName){if (clip != null){if (dictClipToEndEvent.ContainsKey(clip)){//不同名的方法先移除,后添加if (functionName != dictClipToEndEvent[clip].functionName)RemoveFinishEvent(clip);//同名方法无需重新设置else return;}AnimationEvent ev = new AnimationEvent();ev.time = clip.length;dictClipToEndEvent.Add(clip, ev);//在clip.AddEvent前必须设置好functionName否则不生效ev.functionName = functionName;//无需报错ev.messageOptions = SendMessageOptions.DontRequireReceiver;clip.AddEvent(ev);}}static public void RemoveFinishEvent(this AnimationClip clip){if (dictClipToEndEvent.ContainsKey(clip)){if (clip.events.Length == 1) clip.events = null;else{List<AnimationEvent> events = new List<AnimationEvent>(clip.events);var e = dictClipToEndEvent[clip];if (events.Contains(e)) events.Remove(e);clip.events = events.ToArray();}dictClipToEndEvent.Remove(clip);}}
  1. 实现通过Animator的Play与CrossFade来取到当前AnimationClip。
    /// <summary>/// 注意是Clip的名字不是 StateName/// 且此方法为运行中支持,不会影响到Clip的anim文件/// </summary>/// <param name="animator"></param>/// <param name="clipName"></param>/// <returns></returns>static public AnimationClip GetRuntimeClip(this Animator animator, string clipName){AnimationClip[] clips = animator.runtimeAnimatorController.animationClips;if (clips != null && clips.Length > 0)for (int i = 0; i < clips.Length; i++){if (string.Equals(clips[i].name, clipName))return clips[i];}Debug.LogError("Don't find animation name :" + clipName + "\n使用此方法之前,请保证参数为Animator里面的动画名而非状态名");return null;}static public AnimationClip GetRuntimeCurrentClip(this Animator animator, int layer = 0){var clipSource = GetCurrentClip(animator, layer);if (clipSource != null)return animator.GetRuntimeClip(clipSource.name);return null;}static public AnimationClip GetCurrentClip(this Animator animator, int layer = 0){var clipInfos = animator.GetCurrentAnimatorClipInfo(layer);if (clipInfos == null) return null;if (clipInfos.Length > 0){var clipSource = clipInfos[0].clip;return clipSource;}return null;}
  1. 通过Mono实现回调事件与动画事件的结合。
    /// <summary>/// 此脚本在Clip的最后一帧上添加AnimationEvent,以做到回调/// </summary>public class AnimatorClipEventListener : MonoBehaviour{private Action onFinishState;private Animator animator;private bool IsInit = false;private int layer = 0;private string stateName = "";private AnimationClip clip; //实际添加了Clip事件的片段private AnimatorStateInfo CurStateInfo => animator.GetCurrentAnimatorStateInfo(layer);// Start is called before the first frame updateprivate void Start(){}private void OnDestroy(){if (clip) clip.RemoveFinishEvent();}private bool IsInTransition() => animator.IsInTransition(layer);private void Init(Animator anim, Action act, int layer = 0, string stateName = ""){animator = anim;onFinishState = act;IsInit = true;this.layer = layer;this.stateName = stateName;}private void AddEvent(){clip = animator.GetRuntimeCurrentClip(layer);if (clip != null)clip.AddFinishEventToAnimationClip(nameof(_MzCallback));else{Debug.Log("Add event error", animator);_MzCallback();}}private bool CheckState(){if (!animator.HasState(stateName, layer)){_MzCallback();Debug.Log("Don't find stateName with " + stateName, animator);return false;}return true;}public void ListenCurrent(Animator animator, Action action, int layer = 0){Init(animator, action, layer);AddEvent();}public void Play(Animator animator, string stateName, Action action, int layer = 0){Init(animator, action, layer, stateName);if (!CheckState()) return;animator.Play(stateName, layer);StartCoroutine(DoInit());}public void CrossFade(Animator animator, string stateName, float fadeTime, Action cb, int layer = 0){Init(animator, cb, layer, stateName);if (!CheckState()) return;animator.CrossFade(stateName, fadeTime, layer);StartCoroutine(DoInit());}private IEnumerator DoInit(){while (IsInTransition() || !CurStateInfo.IsName(stateName)) yield return new WaitForEndOfFrame();yield return new WaitForEndOfFrame();AddEvent();}public void _MzCallback(){onFinishState?.Invoke();onFinishState = null;Destroy(this);}}

4、把以上方法封装成扩展方法(一句代码搞定的那种)。

    public static class _AnimatorClipFinishEventListener{static private AnimatorClipEventListener GetListener(Animator animator){AnimatorClipEventListener listener = animator.gameObject.AddComponent<AnimatorClipEventListener>();return listener;}static public AnimatorClipEventListener Play(this Animator animator, string stateName, int layer, float norTime, Action cb){AnimatorClipEventListener cbMono = GetListener(animator);cbMono.Play(animator, stateName, cb, layer);return cbMono;}static public AnimatorClipEventListener CrossFade(this Animator animator, string stateName, float fadeTime, Action cb, int layer = 0){AnimatorClipEventListener cbMono = GetListener(animator);cbMono.CrossFade(animator, stateName, fadeTime, cb, layer);return cbMono;}static public AnimatorClipEventListener MzAddFinishEventToCurrentState(this Animator animator, Action cb, int layer = 0){AnimatorClipEventListener cbMono = GetListener(animator);cbMono.ListenCurrent(animator, cb, layer);return cbMono;}static public AnimatorClipEventListener AddFinishEvent(this Animator animator, string clipName, Action cb){AnimatorClipEventListener cbMono = GetListener(animator);cbMono.AddEvent(animator, clipName, cb);return cbMono;}}

模拟回调(非同步事件)

这个整体解决思路就是每帧去判断当前动画是否播放。在这里就不再码代码了,感兴趣的可以尝试自己实现。

结语

代码粗糙需慎重使用,希望起到抛砖引玉的效果,也希望有大神可以提出其它的解决方案。

Animator 实现动画完成事件的一些思考相关推荐

  1. Android动画特效之Animator属性动画实现

    Android动画特效之自定义view: Android动画特效之自定义view_Angel-杭州的博客-CSDN博客_android view 设置动画 由于上期Android动画特效之自定义Vie ...

  2. Android源码解析(一)动画篇-- Animator属性动画系统

    Android源码解析-动画篇 Android源码解析(一)动画篇-- Animator属性动画系统 Android源码解析(二)动画篇-- ObjectAnimator Android在3.0版本中 ...

  3. Unity3D动画帧事件

    前几天在项目开发中碰到一个这样的需求,RPG游戏中,特效和动画播放不同步的.假如主角在攻击NPC时,先实例化特效,后播放动画.动画毕竟是有一个时间长度的.等到动画播放攻击挥刀的那一瞬间时,特效可能早就 ...

  4. Unity通过Animator获取动画clip时长

    ///获取动画状态机animator的动画clip的播放持续时长 public static float GetClipLength(Animator animator, string clipNam ...

  5. animator创建动画_为游戏创建动画的基础

    animator创建动画 You can consider animation as the technique or procedure of making the illusion of moti ...

  6. JQuery 动画和事件

      今天是JQuery的第四节课啦,今天主要讲JQuery的动画和事件,大家有不懂的在下方评论或者私信,也希望和小编一样在长沙的家人们,做好防疫措施,出门带好口罩,能不出门尽量不出门,不给国家添麻烦. ...

  7. JavaWeb开发 前端语言:jQuery(二)属性操作、DOM的增删改、CSS样式操作、动画、事件操作

    JavaWeb开发 前端语言:jQuery(二) 1.jQuery的属性操作 2.jQuery练习:使用jQuery完成全选.全不选.反选和提交功能 3.DOM的增删改 3.1 DOM的增操作 3.1 ...

  8. Unity中Animator播放动画后无法修改transform的问题

    本文分享Unity中Animator播放动画后无法修改transform的问题 在使用Animator时, 如果某些动画状态设计到transform的改动, 比如位置, 缩放等, 在默认情况下我们就不 ...

  9. U3D 动画帧事件问题

    测试版本U3D5.4. 1,为一个模型导入外部动画.为动画剪辑attack在某帧添加event,事件为 public void OnAttackEvent(){},函数体不做任何事情. 结果发现,在动 ...

最新文章

  1. logback-spring.xml读取spring的属性
  2. Service中的绑定服务总结
  3. NTU 课程笔记:CV6422 样本分布
  4. 微信平台开发1--开发者模式基本配置
  5. mysql use mysql_1、设置mysql远程访问执行mysql 命令进入mysql 命令模式,执行如下SQL代码mysql use mysql; mysql GRANT ALL ON ...
  6. 广东省计算机学校哪所最好,广东省哪个技校比较好哪里好
  7. centos/linux 安装node.js
  8. 端到端O-RAN用例测试
  9. iOS之有用的分类(UsefulCategory)
  10. sql中常见sqlcode原因分析
  11. Ext.grid.EditorGridPanel列表复选框不能随意多选的问题
  12. cmd指令ie打开html文件,命令行调用 IE 浏览器打开指定网址
  13. 【修色圣典】第一章 色彩、对比度和通道
  14. 量化择时之移动平均线初探
  15. Uni-App开发框架介绍
  16. fortran2010编译出错,无法找到exe文件的解决方法
  17. 计算机桌面怎么自定义,电脑怎么换壁纸自定义
  18. 西北乱跑娃 -- golang设置代理直连
  19. Matlab常用函数(control)
  20. menuconfig 图形化配置

热门文章

  1. php伪协议实现命令执行,任意文件读取
  2. linux mint 17.3中文输入法,linux mint17 中文输入法 五笔(or 拼音)
  3. Mysql sixth week
  4. unity查找物体方法
  5. 龙腾四海 计算机操作,龙腾四海指标代码及基本用法介绍
  6. 耳机插在电脑上怎么录音,在线录制音频的软件有什么?
  7. OpenCPN + Ubuntu 18.04 源码编译 + Codeblocks 调试 + wxFormBuilder 安装
  8. 史上最欠揍的28条短信
  9. 简单的数学题 - 数论
  10. 愿我们历尽千帆,归来仍是少年