最近发现一个动作播放的细节问题,策划在技能编辑器中配置了在极短的时间中连续造成伤害的技能,间隔时间比受击动作时间还要短,这就意味着播放同一个动作(受击)时,需要打断之前的播放,注意,我这里说的是同一个动作的打断,并不是同一个动作的播放
经测试,在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 可以吧示例工程分享给大家