Unity的新动画系统(animator)在脚本中的操控

前言

  今天在做人物攻击的部分时遇到一个问题,因为攻击动画是东平西凑的素材,所以每个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),所以以后还是注意一下使用第二种比较好
修正
之前说错了,如果使用第二种的话改了等于没改(没有用到override的部分),已经说过RuntimeAnimatorController是AnimatorController和OverrideAnimatorController的基类,并且OverrideAnimatorController中的runtimeAnimatorControlle本质上是它所override的那个AnimatorController,所以如果直接使用第二种的话就相当于没有把Override的信息交给Animator组件(Override的部分在OverrideAnimatorController里面)只是应用了没override前的那个AnimatorController
至于具体如何使用的话我决定还是把动画名统一了(即没有使用上面说的一堆东西),因为如果要像最初设想的一样通过AnimatorController获取state,然后根据state获取clip就行不通了,overridecontroller无法提供state获取方法(GetAnimationState),如果使用runtime即覆盖前的controller的话获取到的state中的clip名字还是未override的,除非再通过未override版本去使用overridecontroller中提供的GetOverrides查找替换后的clip名字,但那样也就太麻烦了

解决方案

现在最初要解决的问题就已经可以完美解决了,我使用了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;
    }
}

如果你没有使用overrideAnimatorController的话可以使用下面这个版本

void Attack()
{
    /*animator.SetTrigger("Attack");
    float speed =  animator.GetCurrentAnimatorStateInfo(0).length / attackDuration;
    animator.speed = speed;*/
    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");
}

我最后使用的是统一动画名字为Attack

void Attack()
{
    /*animator.SetTrigger("Attack");
    float speed =  animator.GetCurrentAnimatorStateInfo(0).length / attackDuration;
    animator.speed = speed;*/
    inAttack = true;
    AnimatorController ac = animator.runtimeAnimatorController as AnimatorController;
    if(!ac) ac = (animator.runtimeAnimatorController as AnimatorOverrideController).runtimeAnimatorController as AnimatorController;
    AnimatorState state = ac.GetAnimatorState(0, "Attack");
    AnimationClip clip = animator.GetAnimationClip("Attack");
    state.speed = clip.length / combat.attackDuration;
    animator.SetTrigger("Attack");
}

你可能感兴趣的:(unity,unity3d,c#,游戏开发)