人形动画一些属性的意思

T:位置;

Q:旋转;

Hand T:未执行SetIKPosition()时手IK的位置,相对于根骨骼;

Hand Q:未执行SetIKRotation()时手IK的旋转,相对于根骨骼;

Foot T、Foot Q同理。不同的是在动画预览窗口开启IK能直接看脚IK的效果,但是手IK不行。

TDOF:根据这里的解释,全称是Translation Degree of Freedom,好像是控制骨骼运动范围的。

What does TDOF mean in the animation window? - Unity Engine - Unity Discussions

Avatar

如果骨骼名称相同重定向时不用Avatar会出现的问题

不使用Avatar,还想给骨骼名称相同的不同人物复用同一个动画,会出现的问题:同一个部位的骨骼长度不同发生动画错位:

应用Avatar动作出现变形的问题

在blender里做一个举起右手的动画:

到Unity里应用Avatar后变成了手掌朝下的举手:

研究发现,使用Avatar重定向后的动画效果和Avatar的配置有关系。要把一个人物的动画精确复用给另一个人物,必须保证

1.人物在T pose,包括手掌向下,大拇指向前。上面的问题就是手掌没向下。

2.Configure Avatar界面里每个骨骼的世界旋转一致(但不代表每个骨骼的轴向和局部旋转要一致。比如两个骨架手部骨骼的轴向不一样,但它们在Configure Avatar里都是手掌向下,大拇指向前,此时它们手骨的局部旋转不一样,但复用也没问题)。但是做游戏人物时还是务必保证不同人物的骨骼轴向、Avatar配置里的骨骼的局部旋转都一致。

为此需要制定一套骨骼轴向和Avatar配置的标准。一种简单的标准是保证Avatar配置里T Pose里尽量多的骨骼和世界坐标轴平行。Hips、Spine、Chest、Neck、Head、Leg、Knee竖直,Shoulder、Arm、Elbow、Wrist左右水平。其他的手指、脚还没有标准,先由Enforce T pose自动摆。这样大部分骨骼在Avatar配置的旋转都是90度的整数倍。

下图就没有遵循这套标准,Chest向后倾斜,Shoulder向下倾斜。

如果不对可以选中骨骼调整旋转。

 动画Rig选择Copy From Other Avatar时报错的解决

看报错信息,是已有的Avatar里的Jaw对应的节点+ToothBone_D_A01没有找到。因为Jaw不是一个很重要的骨骼,可以把已有Avatar里配置的Jaw删掉。

动画重定向变形的问题

一个动画对安柏把手和头调正,复用给刻晴,手和头歪了。配置Avatar的时候我是保证从Hips到双手的轴向两个人是一致的,但是动画里根骨骼Hips的旋转都有明显的区别

Hips骨骼的旋转值:差别明显

安柏

刻晴

后来观察发现这个刻晴的Chest明显比安柏靠下,Spine骨骼短

调整过之后刻晴的手和头也摆正了:

这件事的结论是:骨骼长度会影响动画复用的效果,具体机制不清楚,但应该尽量保证各骨骼的长度比例相近。

把一个动画的Root Q复制给另一个,预览时两个动画人物的Hips的旋转不一样

研究发现,人物的Hips的旋转不单受Root Q影响,Spine、Chest、Upper Chest都会影响。在Animation窗口动一下这几个部位的参数,发现上下身都变化了。

关于Root Transform Rotation

Bake Into Pose勾选则动画里的旋转不会影响人物在世界里的旋转。

Based Upon:选择Body Orientation就把人物胸口的朝向作为人物的正前方。如果人物的姿势是侧身或斜侧身(如持枪),就不应该选这个选项。如下图,选择Body Orientation,人物胸口朝向前方,头和枪就朝向侧前方。

Original:把人物的根节点z轴作为人物前方。持枪状态应该选此选项。且可以发现,地上的红箭头指向的就是人物胸口的朝向。

人物向左、右走的动画同理。

如果选Body Orientation,则人物的胸口对准正前方,人物向左前方移动:

侧向走路应该的效果是,人物的胸口对向侧前方,人物向正侧方移动:

故应该选Original。

状态脚本的OnStateEnter()和OnStateExit()执行先后的问题

在一个状态的OnStateExit()打印退出了,下一个状态的OnStateEnter()打印进入了,执行:

说明是先执行下一个状态的OnStateEnter(),可能在过渡开始时就执行了,过渡结束时才执行上一个状态的OnStateExit()

关于动画事件

动画事件的局限性

  1. 不能加参数;
  2. 对于重写方法,可能不能执行正确的版本;
  3. 放在状态过渡期会不执行;

动画事件没有执行

1.在一个动画事件里设置一个transform的父节点,然后设置了这个transform的position和localEulerAngles,设置父节点生效了,设置position和localEulerAngles无效,transform的位置和旋转是在之前父节点坐标系里的值。设置旋转后打印了localEulerAngles,和设置的一样,但是检查器里是在之前父节点里的旋转值,场景里看也不对。

2.如果动画事件放在状态过渡期的时间,那么不会被执行。

Unity两种类型的动画事件

1.选中动画片段,再点Animation

此时动画参数前面没有物体名称:

此时点动画事件,需要输入回调函数的名字,并允许给4个参数:

2.选中物体,再点Animation

动画参数前面有物体名称:

选中动画事件,需要指定物体的一个组件脚本里的一个方法:

同一帧有多个动画事件

第一个标记为一个竖线,后面的标记为越来越短的竖线。

两个版本的动画剪辑:非Legacy和Legacy

 之前做了一把枪的回膛动画,没问题,去看那把枪的Animation Clip的检查面板,是这样的:

这次做的出问题的Animation Clip的检查面板是这样的:

发现在Project点右键建的Animation Clip的检查面板和图二一样:

在Animation窗口点Create New Clip建的Animation Clip的检查面板和图一一样:

执行时报了警告:Animation组件使用的Animation Clip必须被标为Legacy,也就是说上面图一检查面板的就是Legacy的Animation Clip。

把Legacy动画片段赋给Animator则会报如下警告:

结论:创建给Animation组件使用的Animation Clip必须使用Animation窗口的Create New Clip,创建的动画片段属于legacy动画片段。

另一个分辨Legacy动画片段的方法:

打开动画片段的.anim文件,m_Legacy:1的是Legacy动画片段:

关于Root Transform

如果没有勾选某个Bake Into Pose:

那么预览时人物相应的位移或旋转就不会显示,但是运行时人物的根节点会位移和旋转。Animation窗口Avatar里相应的Root T和Root Q只有第一帧能修改,后面不能修改。比如Root Transform Position(XZ)不勾选,Root T.x和Root T.z就只有第一帧能修改,后面都保持第一帧的值。

勾选了某种运动的Bake Into Pose,如XZ,Root T里的x和z任意帧都能修改,且在预览里能看到人物相应的位移或旋转,而运行时人物的根节点不会有相应的位移或旋转。

结论:不勾选Bake Into Pose时,1.只能修改第一帧相应的Root T和Root Q;2.预览里没有相应的位移、旋转效果;3.运行时人物根节点有相应的位移、旋转效果。勾选Bake Into Pose时,1.可以在任意帧修改相应的Root T和Root Q;2.预览时显示Root T和Root Q;3.运行时人物根节点没有相应的位移、旋转效果。

应用

那么如果想给一个动画加xz平面内的运动效果,方法就是1.不勾选Root Transform Position XZ;2.在Animation窗口调整Root T.x和Root T.z给一个位移;3.勾选Root Transform Position XZ

IK

默认IK:Hand T、Hand Q、Foot T、FootQ

IK的定义不说了。很多人都知道可以在OnAnimatorIK()里调用animator.SetIKPosition()、animator.SetIKRotation()设置IK目标的位置旋转。但是只要一层勾选了IK Pass,IK就生效了,如果不执行animator.SetIKPosition()、animator.SetIKRotation(),IK目标的位置旋转是怎样的?其实Humanoid动画剪辑里的Hand T、Hand Q、Foot T、FootQ就是默认的手脚IK目标的位置旋转,相对的是根骨骼Hips。这几个属性在Animation窗口点预览无效,运行才能看到效果。

而脚IK在动画预览窗口勾选IK后可以预览,还可以修改Foot T、Foot Q直接看效果:

总之Unity动画系统的IK功能是比较完整的,但是界面设计的稀碎,系统的各元素分散,关系隐秘,也没有明显的说明。

遇到的问题:在Animation窗口里预览的和运行时的效果不一样

想把跳跃的动画的弯的腿调直,动画预览窗口效果:

运行效果:

另一个动画在动画窗口预览和运行时的效果:

经过实验:

运行的效果是之前的一个版本,好像对动画的修改没有保存下来。

其他动画没问题

新建一个动画片段文件,从出问题的动画复制全部关键帧,有问题

从fbx的动画里复制全部关键帧,运行和fbx动画的效果一样;

从出问题的动画把Right Upper Leg的3个参数的关键帧覆盖,没有生效;

把Root T的关键帧覆盖,生效了;

从没问题的动画把右腿右脚的参数的关键帧覆盖,没有生效;

把Right Foot T覆盖,,右腿生效了;之前在预览里调Foot T,没有效果;试着把Left Foot T关键帧全删除,预览里没有任何不同,但是运行时变成了这样:

说明:

1.Foot T这个参数在预览里看不出效果,在运行时才能看出来。

2.但是它影响了整个腿部的效果,它没有调好,只调好Upper Leg、Lower Leg、Foot的参数在运行时是无效的

使用脚本关闭脚的IK权重,可以消除Foot T、Foot Q的影响:

查资料:

Animator-leftFeetBottomHeight - Unity 脚本 API (unity3d.com)

得知Foot T、Foot Q、Hand T、Hand Q就是足部和手部IKGoal的位置和旋转,这4个参数在预览里是无效的,在运行时有效,会导致运行时的动画和预览时效果不一样。可以在脚本的OnAnimatorIK()里设置SetIKxxxWeight控制是否启用。还需要搞清楚位置和旋转是相对谁的。

animator.SetBoneLocalRotation()的问题

使用animator.SetBoneLocalRotation()写入骨骼的localRotation,骨骼的旋转也会改变。

void OnAnimatorIK(){
        //旋转手臂改变仰角
        if(rotateArm){
            Quaternion leftArmRot=Quaternion.AngleAxis(angleX,
            leftArm.InverseTransformDirection(transform.right));
            animator.SetBoneLocalRotation(HumanBodyBones.LeftUpperArm,leftArm.localRotation);
            Quaternion rightArmRot=Quaternion.AngleAxis(angleX,
            rightArm.InverseTransformDirection(transform.right));
            animator.SetBoneLocalRotation(HumanBodyBones.RightUpperArm,rightArm.localRotation);
        }
    }

对运动动画腿部穿模的调整

为了排除调动画时IKGoal的影响,选择不启用IK,导致IK对腿部穿模的调整作用也没了,只能手动调整防止穿模。

找出一条腿走路循环的五个关键帧:迈出、离地、两次两腿交错和用于循环的,把其他关键帧删除,可以防止插有原来的关键帧造成的抖动。

关于Animation组件

使用Animation组件就是为了方便给不同枪指定不同动画,但是没找到任何方法得到检视器里的Animations数组,Animation.Play()是通过动画片段名字的字符串控制播放哪个片段的。就是说想要不同枪播放各自的后座动画,要么

1.在脚本里写死片段的名字,给不同枪的后座动画起相同的名字;

2.再声明几个字符串字段记录使用的动画片段的名字;

3.再或者声明几个AnimationClip字段,执行animation.clip=clip1;

这个设计很脑残。我预想的是直接能访问Animations数组,animation.Play()输入一个数字指定播放第几个动画片段。

又试了一下animation.AddClip()这个方法,能把一个AnimationClip加到Animations数组里,并给它取个别名。又是让人匪夷所思的操作,这个AnimationClip本身没有名字吗。我突然想到这个别名可能演变成了后来状态机里的状态名。

经过以上的实验,我决定枪的动作动画也由Animator来做,给每种枪做一个AnimatorController,里面三四个状态,也不会太复杂。

Animator组件有Root position or rotation are controlled by curves

动画状态机里包含的动画含有Position或Rotation的关键帧时,会出现这段话。即使没有勾选Apply Root Motion,物体的位置旋转还是会被影响。

运行中动态修改动画状态机使用的动画片段

这需要用到另外两个类,RuntimeAnimatorController和AnimatorOverrideController。

RuntimeAnimatorController - Unity 脚本 API

AnimatorOverrideController - Unity 脚本 API

为什么需要这两个类?根据我的理解,Animator Controller出现在Assets窗口,和模型、动画片段、脚本、预制体一样,是一种资源,所有的资源运行时都不能修改。要动态修改动画状态机,必须定义一个它在运行时的对应类型,就像预制体运行时不能修改,但是预制体实例可以修改,这就是RuntimeAnimatorController。RuntimeAnimatorController拥有animationClips字段,能获得状态机里所有动画片段的名字,但是还没有改变里面动画片段的方法。

而AnimatorOverrideController包含了一个RuntimeAnimatorController,拥有一个索引器字段,可以根据动画片段名字读取动画片段、指定要替换的动画片段名字。知道了它们的区别,我还是不明白有什么必要定义两个类,好像完全可以用一个类的一个字段读写。

AnimatorController的官方文档也暗示了这一点,AnimatorController是UnityEditor里的类,使用UnityEditor的脚本都不许打包导出,否则报错,就是不允许运行时脚本修改AnimatorController。

Animations.AnimatorController - Unity 脚本 API

具体操作看这篇文章。

Unity中Animator动态添加AnimationClip - 炘恪 - 博客园 (cnblogs.com)

这段代码新建了一个AnimatorOverrideController,让里面的RuntimeAnimatorController等于AnimatorController里的RuntimeAnimatorController,然后修改里面的动画片段,再把自己赋给Animator的RuntimeAnimatorController。看起来操作有点多余。

我试了简化成下面的代码:

没有生效。

我试了试删除那一句animator.runtimeAnimatorController=null,报了这个错:

超出了我的理解能力。

替换哪个动画片段是通过动画片段名字字符串指定的,这个也不太好用。希望能通过状态名指定替换哪个动画片段。

总结一下这4个类的特点:

Animator

相当于动画播放器。

Animator Controller

继承自RuntimeAnimatorController,记录多个动画片段和转换关系的文件,是一种Asset,运行时不能修改。

RuntimeAnimatorController

Animator Controller在运行时的形式,和Animator Controller的关系类似类和实例。

AnimatorOverrideController

继承自RuntimeAnimatorController,用于修改RuntimeAnimatorController的类。不知道为什么非要定义它。

它们的关系:

.overrideController文件

又看到了这篇文章

Unity Runtime创建和编辑AnimationClip - 弹吉他的小刘鸭 - 博客园 (cnblogs.com)

学到了更好用的方法:.overrideController文件。可以在一个AnimatorController的基础上对不同人替换一些动画片段,但还不能解决对话时动态修改同一个状态的动画片段的需要。

然后我知道了.overrideController其实就是AnimatorOverrideController类型的文件格式,上面那一段代码对应到编辑器操作,分别是:

对应

对应

对应

对应在按Backspace把Controller变成None

对应

那么这一套代码无效的原因也就清楚了,这里创建了一个.overrideController,把它的源.controller指定成新动画片段,但是没有改animator.runtimeAnimatorController。

然后其实那句animator.runtimeAnimatorController=null是可以删掉的,报错是因为initialAnimatorController是空的。

.overrideController文件和RuntimeAnimatorController代码联合使用的问题

把.overrideController文件交给animator使用:

执行下面代码的时候:

报错:

这里是试图把一个.overrideController赋给另一个.overrideController的Controller:

很明显这是不行的。

我又发明了一种写法,如果人物用的本来就是.overrideController,即重写的状态机,就不再新建AnimatorOverrideController;如果是.controller,完整的状态机,再新建AnimatorOverrideController。

可以看到,对于原本使用.controller的人物,这么操作后变成了:

Controller是运行时创建的无名.overrideController。

状态设置

未勾选Write Defaults导致过的灵异现象

我使用代码设置Axis的仰角,未勾选Write Defaults导致Axis的仰角被锁死不能改变。尽管整个动画状态机并无一个动画剪辑的一个关键帧设置了Axis的旋转。

问题记录

Broken text PPTr in file(Assets/Resources/Animators/Animator Controller1.controller). Local file identifier (-6880487854150155466) doesn't exist!

保存Animator Controller时出现此错误。暂时没什么影响。

人物挂有RigidBody,运行时人物缓慢上升

原因:动画设置里没有选择Root Transform Position(Y)的Bake Into Pose,具体原因不清楚。推测是动画改变了人物的y方向位置,RigidBody保持了这个速度。但是试了y方向没有位移的动画和y方向向下移动的动画,都会向上移动。另外,人物1.不挂RigidBody;2.不选Use Gravity;3.不挂Animator;4.不选Apply Root Motion都不会有此现象。

解决方法:没有打算让动画改变位置或旋转一定要Bake Into Pose。

Animation窗口点击Preview后有的人物会到世界原点,有的人物不会

对于脚本里没有OnAnimatorMove(),也就是Apply Root Motion没有变成Handled by Script的人物,勾选Apply Root Motion时,预览动画人物回回到世界原点,没有勾选时,在原地。

经过实验发现,对于Apply Root Motion变成Handled by Script的人物,预览动画时是否回到世界原点取决于把相关脚本移除后是否勾选了Apply Root Motion。

点击预览动画,人物不见了,人物的pivot还在原地

原因:动画里的Root T.y在-51,就是人物跑到了下面-51的地方。

骨架放在场景里,挂载animator,Animation窗口点预览,无效

原因:需要给骨架加一个父节点,animator组件加在根节点上。

Animation组件执行Animation.Play()返回Default clip could not be found in attached animations list.

背景:做枪械回膛动画,因为比较简单,就用了Animation组件。

XXX AnimationEvent has no function name specified!

但是动画事件的方法指定了,也执行了。

原因:注意动画事件竖线后面的短竖线!代表同一帧的其他动画事件。

人物死亡没闭眼

状态机Eyes层显示进入了死亡状态,死亡状态有闭眼动画,预览时动画有效。

从Any State进入一个状态不停反复

排查过程:因为是第一次用Any State,我很怀疑和它有关,就改成从一个状态过渡到死亡,结果人物正常播放死亡动画。难道是因为这个Dead也被算做Any State了,结果就是Dead和它自己之间不停过渡?

然后随便翻看,看到Any State到Dead的过渡有这么一个参数Can Transition To Self,感觉有关系。不勾选,解决。

Animator窗口状态图看不见状态

解决方法:按F,聚焦到默认状态。

animatorOverrideController.ApplyOverrides()一次重写多个动画剪辑没生效

代码基本上是跟官方例子抄的。

AnimatorOverrideController - Unity 脚本 API (unity3d.com)

原因:List没有这几个键值对,需要Add(),直接用索引赋值相当于没做。应该这样写:

人物动画失效并报错System.InvalidOperationException: The TransformStreamHandle cannot be resolved. 

看起来是AnimationRigging相关的错误。

 这个错误是我把一把枪的脚本拖给人物的武器槽后播放发生的。如果人物的武器槽有武器,Start()里会执行拿起这把枪。把Start()里的拿枪函数注释,不报错了。可能是各对象Start()执行顺序不对造成的。

拿枪函数里有一个做出拾取动作,把这一句注释掉,还报错。

public void TakeGun(Gun gun){
        GunConfig gunDataBin=MyConfigManager.Instance.GetGunDataFromSO(gun.id);
        switch(gunDataBin.gunType){
            case GunTypes.Handgun:
                if(handgunScript){
                    SetDropGunTransform(handgunScript);
                }
                TakeHandgun(gun);
                break;
            case GunTypes.MainGun:
                if(rifleScript){
                    SetDropGunTransform(rifleScript);
                }
                TakeRifle(gun);
                break;
        }
        gun.Taken(this);
        // animator.SetTrigger(pickUpPara);
    }

然后就把TakeGun()的代码从下到上一句句注释,发现从注释TakeRifle()开始不报错。然后看TakeRifle():

protected virtual void TakeRifle(Gun gun){
        rifleScript=gun;
        gun.transform.SetParent(rifleHolder);
        gun.transform.localPosition=Vector3.zero;
        gun.transform.localEulerAngles=Vector3.zero;
    }

 发现从执行SetParent()开始报错。我不知道为什么。把这句注释,除了枪不跟随人物,其他正常。然后在SetParent()上测试,改成

transform.SetParent(transform);

不报错。然后我想到这里设计有问题,先检查自己有没有步枪,如果有就放下,再捡目标枪,如果目标枪就是自己的枪,会在一帧里两次SetParent()。 然后把第二次SetParent()换个位置,顺序不变,还报错。

查资料并没有发现一帧内多次SetParent()会有问题,加上这个报错提到了AnimationRigging,这个问题可以说是在使用AnimationRigging时,一帧内多次SetParent()会导致报错System.InvalidOperationException: The TransformStreamHandle cannot be resolved.

ai在死亡动画快结束时通过动画事件关闭animator以节约性能,但偶尔有人物关闭animator时死亡动画还没播放完的情况

后来发现是animator剔除的原因。人物死亡时如果没有看着,动画就不更新,此时关闭animator就会看到动画没播完。那么可以理解剔除就是人物不在相机视野里时不更新,进入相机视野时直接更新到它应播放的帧。

解决方法:当然是改成Always animate。

对状态机行为脚本用法的理解

动画系统执行代码的三大工具:关键帧、动画事件、状态机行为脚本。之前说过,Monobehavior脚本能做的绝大部分功能,动画系统都能做。修改字段对应关键帧,执行方法对应动画事件。区别就是脚本擅长逻辑判断,不擅长控制时间,动画系统不会逻辑判断,擅长控制时间。状态机行为脚本和动画事件类似,区别是对于循环播放的动画,只想在这个状态执行一次的方法,只能用状态机行为脚本。

刚学会状态机行为脚本的时候我以为得到了神器,曾经把按状态开关约束(适合用关键帧做的)和一些动画事件放在里面,结果一大堆状态都有状态机行为脚本,而且都是改变animator挂载的对象的行为,先animator.GetComponent获得人物脚本,比较麻烦。

后来我意识到状态机行为脚本好像不是这么用的,它不应该用来改变animator挂载的对象身上的其他脚本。它的每个函数都传入animator,可以用来让状态机自己改变参数。

动画事件和重写方法的矛盾

因为动画事件通过反射,也就是GetMethod(函数名)的方法调用函数,而且调用的一定是无参函数,函数被重写时,这个类里有多个同名函数,调用的版本可能不能想要的。此时会出现这个警告:

所以如果想让动画事件根据子类执行相应的代码,应该在基类方法用判断然后执行相应代码。

if(this is ChildXXX){

}

动画状态机设计可能遇到的困难

有些动作使用AvatarMask,有些不使用,结果很多Base层动作在覆盖层还要加空状态

不妨把不使用AvatarMask的动画在最上面加一层。

很多种状态都可能转换到某状态,结束后又要转换回去,状态图像蜘蛛网

如果是所有状态都可能转换到某状态就用Any state。如果是大部分状态,也可以在上面加一层。

Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐