动画事件

近来想在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. CPU的自动调度矩阵乘法
  2. start.aliyun.com 正式上线!极速构建 Spring Cloud 应用
  3. 【收藏】银联在线支付商户UPMP接口的使用和说明
  4. 人生苦短,开发用云 | 如何优雅完成程序员的侠客梦?
  5. java 方法重载 应用举例,Java 实例 - 重载(overloading)方法中使用 Varargs
  6. NLP 专题论文解读:从 Chatbot 到 NER | PaperDaily #11
  7. python中的所有功能_python – 是否可以列出模块中的所有功能?
  8. Linux-No.04 Linux 设置定时任务发送邮件功能
  9. ios framework 调用第三方 framework_Python基础:标准库和常用的第三方库
  10. 阿里云飞天洛神2.0:高性能网络软硬一体化技术实践
  11. 你们期待的小屏旗舰来了: 骁龙855 没有刘海!
  12. python Binary I/O
  13. IP数量就是计算机数量吗,如何利用bash/python计算IP子网容纳计算机数量
  14. python 笔记 之 装饰器
  15. 南信大校园网稳定|多拨|软路由|硬路由|保姆级教学|一步到位|openwrt|pandavan老毛子
  16. java毕业设计——基于java+JDBC+sqlserver的POS积分管理系统设计与实现(毕业论文+程序源码)——POS积分管理系统
  17. 【全国第二批】供应链创新与应用示范企业和示范城市申报材料条件内容认定好处费用时间
  18. 图像预处理库CV-CUDA开源了,打破预处理瓶颈,提升推理吞吐量20多倍
  19. html标签手册 360doc,基于AJAX的文件上传控件NetAdvantage for jQuery
  20. CLIP学习笔记:Learning Transferable Visual Models From Natural Language Supervision

热门文章

  1. 【串口按帧接收数据】
  2. 结电场已经是光入射之前内部各种因素平衡所致的。凭什么说光生载流子是结电场推过去的?好像光生载流子只受结电场作用似的。提出动能假设 ?网友的相同疑问
  3. 软件设计师考试 | 第九章 数据库技术基础 | 关系数据库的规范化
  4. 【智能制造】浅谈中国工业4.0的发展方向
  5. 5G扬帆 -- 2022中国国际信息通信展推出数字医疗健康展区
  6. 服务器抓取MIUI ota信息,小米手机获取root权限(保留OTA升级 / 刷第三方Recovery )...
  7. 首届智能网络产业论坛成都开幕 迅游科技抢先布局5G
  8. 2021年完美的借条怎么写
  9. ME02 认知之2017罗胖跨年演讲
  10. 推荐给大家一款免费的跨境电商免费选品软件