前言
今天在做人物攻击的部分时遇到一个问题,因为攻击动画是东平西凑的素材,所以每个clip的时长并不符合我项目中
的实际武器攻击时长,对每一个clip做帧的删除和添加又太过麻烦,我希望能够通过我期望的攻击间隔(攻击动画播放
时间)和当前的动画时间计算一个动画播放速度来动态的修改animator中state的speed,原本以为很简单的操作却扯
出一片我以前都不知道的动画脚本操作。
探索
Animator为我们提供的与state和clip有关的操作少的可怜,你只能通过GetCurrentStateInfo和GetCurrentClipInfo获取当前正在播放的动画和所处状态,而且返回值均为Info,也就是只读信息不能进行修改,换句话说unity压根就没想让你通过Animator这个类进行动态的state和clip修改参数。 但是只利用这些也是可以实现我上述问题的,虽然有点牵强,代码如下
animator.SetTrigger("Attack");
float speed = animator.GetCurrentAnimatorStateInfo(0).length / attackDuration;
animator.speed = speed;
如果你的状态转换中有Exit Time的话就要换成GetNextAnimatorStateInfo因为在ExitTime过渡状态时还是算做上一个状态的,这里直接修改了整个animator的speed,动画结束后记得改回1即可。 这就是我们能用animator做的所有事情了,要想对动画操纵更多你需要引入一个新的命名空间UnityEditor.Animations,我们需要编辑器层面的操作才能实现对state和clip的修改,其实原因也很简单,这里有一个容易忽视的地方那就是state不是属于我们挂载在物体上的Animator组件的,state能持久保存说明它是一个persistent asset,你不可能通过Animator(MonoBehaviour)来对它进行修改因为MonoBehaviour是游戏运行时才存在的,所有对Asset的操作基本都需要UnityEditor这一命名空间。那很容易想到它是属于这个东西的 它对应的类叫做AnimatorController,实际上我们的代码在正常逻辑中几乎不会用到它,但我们经常会用到它的基类RuntimeAnimatiorController,每个Animator组件都包含一个RuntimeAnimatorController的成员,可以用它实现运行时替换AnimatorController。
public sealed class AnimatorController : RuntimeAnimatorController
这同时就带来一个问题,如果你通过变量直接加载对应的Animator Controller Asset然后进行修改当然没有问题,所有以它作为RuntimeAnimatorController的Animator都会同时被修改。 但如果你想通过Animator去访问该Animator正在引用的AnimatorController,Animator只包含RuntimeAnimatorController成员,你需要将RuntimeAnimatorController强制转换为AnimatorController(父转子),这个转换不是安全的,正常情况下是没问题的无论你是直接在Inspector窗口为Animator的RuntimeAnimatorController赋值还是用代码运行时赋值,因为你的赋值都是一个AnimatorController所以这个转换可以完成 但我有个手残的操作在这里作为一个提醒,我们一般替换RuntimeAnimatorController都会使用OverrideAnimatorController,在替换时你可以有以下两种方式
animator.runtimeAnimatorController = overrideAnimator;
animator.runtimeAnimatorController = overrideAnimator.runtimeAnimatorController;
因为OverrideAnimatorController也是继承自RuntimeAnimatorController,所以第一种可以直接完成隐式转换,单就效果来说也是完全相同的。但也就是说它和AnimatorController是兄弟关系,在这种情况下你如果再尝试把runtimeAnimatorController转换为AnimatorController的话就会报错因为它其中的值实际上是一个OverrideAnimatorController。
public class AnimatorOverrideController : RuntimeAnimatorController
从这个问题我们也可以看出来,OverrideAnimatorController保留了父类RuntimeAnimatorController的clips等信息,但它实际上完全没有存储独立的State等信息而是保留了一个runtimeAnimatorController的成员(实际上其中存储的是AnimatorController对象也就是它Override的那个Controller),所以以后还是注意一下使用第二种比较好
解决方案
现在最初要解决的问题就已经可以完美解决了,我使用了ExtensionMethod方便操作,代码如下
public static class ExtensionAnimator
{
public static AnimatorState GetAnimatorState(this AnimatorController ac, int layer, string statename)
{
AnimatorControllerLayer acl = ac.layers[layer];
foreach (var state in acl.stateMachine.states)
{
if(state.state.name == statename) return state.state;
}
return null;
}
public static AnimationClip GetAnimationClip(this Animator animator, string name)
{
foreach (var clip in animator.runtimeAnimatorController.animationClips)
{
if(clip.name == name) return clip;
}
return null;
}
}
void Attack()
{
AnimatorController ac = (AnimatorController)animator.runtimeAnimatorController;
AnimatorState state = ac.GetAnimatorState(0, "Attack");
AnimationClip clip = animator.GetAnimationClip(state.motion.name);
state.speed = clip.length / attackDuration;
animator.SetTrigger("Attack");
}
|