最近发现一个动作播放的细节问题,策划在技能编辑器中配置了在极短的时间中连续造成伤害的技能,间隔时间比受击动作时间还要短,这就意味着播放同一个动作(受击)时,需要打断之前的播放,注意,我这里说的是同一个动作的打断,并不是同一个动作的播放
经测试,在Animator中同一个状态在播放过程中无法再次播放自己,只有切到其他状态,再切回来这一个方法,但显然这样会导致结果并不是我们想要的,那么如果将两个状态设置成一样动作,然后在这两个状态之间来回切换呢?答案是可以的
于是这里又出现另一个问题,项目已经到后期了,让美术同学把每个AnimatorController挨个改一遍不太现实,所以想到是否可以通过代码的方式动态添加Animator中的状态,可惜答案是否定的,Unity并没有给我们提供相应的接口,为了在不污染的原来美术资源的前提下实现这个功能,第一时间想到了AnimatorOverrideController,当前AnimatorOverrideController也没有添加AnimationClip的功能,不过它却提供了一个覆盖的功能ApplyOverrides,于是我想到了一个方法,首先创建一个包含所有状态的AnimatorController,然后在它里面添加更多的扩展状态,如下图
其中Extend1与Extend2是额外的用于扩展的状态(即然不能动态添加,那么提前加几个,在运行时覆盖),至于Extend1与Extend引用的动作文件,可以通过右键Create/Animation,创建出一个空白的动作文件,(此步是必须的,不可为空,因为AnimatorOverrideController是覆盖,必须要有AnimtionClip)如下图
最后我封装了一个AnimatorEx来代替Animator,这样也不会影响上层的使用
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace AnimationEx { public abstract class AnimatorEx { private Animator mAnimator; private AnimatorOverrideController mOverrideController; //模板动画控制器(状态最全的动画控制器) private RuntimeAnimatorController mTemplateAnimatorController; //模板动画控制器中所有的AnimationClip private Dictionary<string, AnimationClip> mTemplateClips = new Dictionary<string, AnimationClip>(); private Dictionary<string, AnimationClip> mOriginClips = new Dictionary<string, AnimationClip>(); //所有的替换AnimationClip private List<KeyValuePair<AnimationClip, AnimationClip>> mOverrideClips = new List<KeyValuePair<AnimationClip, AnimationClip>>(); private int mExtendClipCount = 2; private int mCurUseExtendClipCount = 0; protected AnimatorEx() {} private bool Init(Animator animator, UnityEngine.RuntimeAnimatorController origin) { if (origin == null) { Debug.LogError("the origin runtime animator controller is null"); return false; } mTemplateAnimatorController = Resources.Load<RuntimeAnimatorController>("AnimatorControllerEx"); foreach (AnimationClip clip in mTemplateAnimatorController.animationClips) mTemplateClips.Add(clip.name, clip); foreach (AnimationClip clip in origin.animationClips) { var templateClip = GetTemplateClip(clip.name); if (templateClip == null) { Resources.UnloadAsset(mTemplateAnimatorController); Debug.LogErrorFormat("template animator controller has no clip {0}", templateClip); return false; } var replaceInfo = new KeyValuePair<AnimationClip, AnimationClip>(templateClip, clip); mOverrideClips.Add(replaceInfo); mOriginClips.Add(clip.name, clip); } mOverrideController = new AnimatorOverrideController(); mAnimator = animator; return OnInit(); } protected AnimationClip GetTemplateClip(string clipName) { AnimationClip clip = null; mTemplateClips.TryGetValue(clipName, out clip); return clip; } protected AnimationClip GetOriginClip(string clipName) { AnimationClip clip = null; mOriginClips.TryGetValue(clipName, out clip); return clip; } protected abstract void OnChangeClips(); protected string AddClip(AnimationClip clip) { if (clip == null) { Debug.LogWarningFormat("add clip fail,the clip is null!"); return null; } if (mCurUseExtendClipCount < mExtendClipCount) { var extendClipName = string.Format("Extend{0}", ++mCurUseExtendClipCount); var extendClip = GetTemplateClip(extendClipName); if (extendClip == null) Debug.LogErrorFormat("template animator controller has no clip {0}", extendClipName); else { mOverrideClips.Add(new KeyValuePair<AnimationClip, AnimationClip>(extendClip, clip)); return extendClipName; } } else { Debug.LogErrorFormat("template animator controller has no enough extend clip to use, the max entend num is {0}", mExtendClipCount); } return null; } protected void ApplyChangeClips() { mOverrideController.runtimeAnimatorController = mTemplateAnimatorController; mOverrideController.ApplyOverrides(mOverrideClips); mAnimator.runtimeAnimatorController = mOverrideController; } public Animator animator { get{ return mAnimator; } } public virtual bool OnInit() { return true; } public virtual void Play(int stateNameHash, int layer) { mAnimator.Play(stateNameHash, layer); } public static AnimatorEx Instantiate<T>(Animator animator, UnityEngine.RuntimeAnimatorController origin) where T : AnimatorEx, new() { AnimatorEx ex = new T(); var ret = ex.Init(animator, origin) ? ex : null; if (ret != null) ret.OnChangeClips(); return ret; } public static AnimatorEx Instantiate<T>(Animator animator) where T : AnimatorEx, new() { return Instantiate<T>(animator, animator.runtimeAnimatorController); } } }
然后我们针对需求进行继承与扩展
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace AnimationEx { public class PFAnimator : AnimatorEx { private AnimationClip mHit1; private int mHit1PlayIndex; private int[] mHitHashCodeArray; public override bool OnInit() { mHit1 = GetOriginClip("Hit1"); if (mHit1 != null) { mHitHashCodeArray = new int[2]; mHitHashCodeArray[0] = Animator.StringToHash(mHit1.name); } return true; } protected override void OnChangeClips() { if (mHitHashCodeArray != null) //只处理战斗的动画控制器 { string extendName = this.AddClip(GetOriginClip("Hit1")); if (!string.IsNullOrEmpty(extendName)) mHitHashCodeArray[1] = Animator.StringToHash(extendName); this.ApplyChangeClips(); } } public override void Play(int stateNameHash, int layer) { if (mHitHashCodeArray != null) { if (stateNameHash == mHitHashCodeArray[0]) { mHit1PlayIndex = ++mHit1PlayIndex % 2; stateNameHash = mHitHashCodeArray[mHit1PlayIndex]; } } base.Play(stateNameHash, layer); } } }
使用方法如下
using System.Collections; using System.Collections.Generic; using UnityEngine; using AnimationEx; public class Demo : MonoBehaviour { public GameObject mEntity; private AnimatorEx mAnimator; private int mHit1HashCode; void Start() { var animator = mEntity.GetComponent<Animator>(); mAnimator = AnimatorEx.Instantiate<PFAnimator>(animator); mHit1HashCode = Animator.StringToHash("Hit1"); } void OnGUI() { if (GUI.Button(new Rect(0, 0, 150, 50), "TEST")) { mAnimator.Play(mHit1HashCode, 0); } } }
最终效果如下:
文章评论
大神牛逼 喜欢你的技术教程
大神可以的话 建一个github 可以吧示例工程分享给大家