Unity 动画系列四 代码控制动画实例 和 RootMotion

参考
Unity动画系统详解4:如何用代码控制动画?
学习笔记 --- Unity动画系统
【Playable API】不用Animator如何播放动画?

一、下载并导入角色动画

参考【建议收藏】找不到免费的角色动画?来试试mixamo

1.按照大智的教程下载

www.mixamo.com/

image.png

一般只需要调一下第一个选项Format,设置为Fbx for Unity(.fbx)即可。
Pose选项一般不用设置,除非你不想下载蒙皮模型,只想要动画文件,可以选择里面的No Character。

如果动画选择的是单个动画,这个Pose会显示为Skin,可以选择是否包含蒙皮。

2.导入
点击Fix now
3.批量修改Mixamo的动画名称变为fbx文件名称
image.png

在Assets下创建一个Editor文件夹,Editor文件夹下创建RenameMixamoAnimationClip.cs,然后在洪流学堂公众号回复mixamo下载最新代码)复制进来,如果有报错,检查一下小于号是不是报错了,改一下

using UnityEditor;

public class RenameMixamoAnimationClip
{
    [MenuItem("Assets/Auto Rename Mixamo AnimationClip")]
    private static void RenameMixamoANimationClips()
    {
        var objs = Selection.gameObjects;
        if (objs == null) return;

        for (var i = 0; i < objs.Length; i++)
        {
            var assetPath = AssetDatabase.GetAssetPath(objs[i]);

            var modelImporter = (ModelImporter)AssetImporter.GetAtPath(assetPath);
            if (modelImporter == null) continue;

            var clips = modelImporter.clipAnimations; // get first clip
            if (clips == null || clips.Length == 0)
                clips = modelImporter.defaultClipAnimations;

            for (var j = 0; j < clips.Length; j++)
            {
                clips[j].name = objs[i].name;
            }

            modelImporter.clipAnimations = clips;
            modelImporter.SaveAndReimport();
        }
    }
}
image.png

选中要改名的fbx,在Assets菜单下执行Auto Rename Mixamo AnimationClip即可。

二、状态机设置
1.加入状态机
image.png

可以将这些动作文件选中多个,直接拖到Animator窗口,这样就能在Animation窗口里预览动作了。


image.png
image.png

现在把这两个动作用transition连接上,就能看到角色走着走着,直接死掉倒地。

2.按参数切换动画
image.png
image.png

现在把场景运行起来,然后手动改一下speed的值,就能看到动作切换了。


image.png

“但是现在感觉不太流畅,问题在哪呢?”
“第一个,你这个Transition除了speed大于0这个条件,还有一个条件是Has Exit Time,就是播放完idle的动画并且speed>0才会切换到下一个动画,这个Has Exit Time得去掉。第二个,你这个两个动画都应该是循环播放吧”

3.控制人物移动

使用动画系统时,有两种控制人物移动的方式:”

  • 使用动画中的位移
    好处是:人物的脚步会跟地面贴合,不会出现滑步的问题(人物的移动距离比步子大或者小),控制简单
    缺点是:比较依赖动画的制作,程序控制性不高
  • 使用代码控制人物的位移
    好处:可控性高
    缺点:容易出现滑步,控制复杂
4.使用动画中的位移

这种方式需要先设置两个地方:

  • 物体上Animator组件的Apply Root Motion需要勾选,相当于就是把动画中的位置修改应用到物体上;
  • 人物的动画类型需要设置为Humanoid。

这两个地方设置好以后,再用代码去修改animator组件中的参数就行了。设置参数的办法是使用SetInteger、SetFloat、SetBool、SetTrigger四个方法。

SetInteger有两个重载:

public void SetInteger(string name, int value);
public void SetInteger(int id, int value);

对于第一个重载,第一个参数类型是string,对应的是parameter中的参数名称。第二个参数是要设置的值。
对于第二个重载,第一个参数是animator中parameter中参数的ID

其他SetFloat、SetBool、SetTrigger都类似,但是唯一不同的是SetFoat还有额外的两个重载方法:

public void SetFloat(string name, float value, float dampTime, float deltaTime);
public void SetFloat(int id, float value, float dampTime, float deltaTime);

其中前两个参数和上面是类似的,不同的是后面两个参数。
参考Animator.SetFloat(string name,float value,float dampTime,float deltaTime)详解

  • dampTime: 用dampTime长的时间,将我们设置的叫做name的float类型的值由原本的值 改变到给定的value 值。
  • deltaTime:两次执行该方法的时间间隔。(因为这个方法会每 deltaTime 执行一次,直到 name = value)

经过上面的解释,相信这个重载函数就比较好理解了,说简单点,就是类似于普通SetFloat的Lerp版本。

参考Unity代码笔记生成大量npc+人物随机移动

void Update () {
    if (animator == null)
    {
        return;
    }
    //对跳跃翻滚的全部随机设置
    int r = Random.Range(0,50);
    animator.SetBool("jump", r==20);
    animator.SetBool("dive", r==30);
    //设置计算两个坐标点之间的距离
    if (Vector3.Distance(TargetPosition, animator.rootPosition) > 5)
    {
        //注意这个SetFloat函数, 其中SpeedDampTime的是到达目标值所用时间
        //一个缓入的过程
        animator.SetFloat("speed", 1, SpeedDampTime, Time.deltaTime);
        ...
5.难点详解

“听好了,第一题:bool参数和Trigger参数的区别是什么?”

“bool参数和trigger参数很像,都是代表布尔值,但是trigger参数只能被设为true,一旦被transition使用,就会自动被设为false。”

“回答的不错,嗯。。。”小新沉思了10秒钟

“小样儿,就你。bool类型一般用于持续的状态,比如角色是否趴下。而trigger一般用于使用一次就会恢复的状态,比如开枪,开枪动画播放完以后,会自动恢复到之前的动作。”

“我明白了。第二题:parameter的id是什么?”

“我们在设置parameter的时候设置的是一个字符串的名称,但是在Unity内部是有一个数字id跟它对应的,使用Animator.StringToHash这个API可以将字符串的参数名转为数字id。使用数字id的代码运行效率会稍微高一些。”


//这里的名称要与Animator窗口中,动画参数的名称对应
//通常对于调用频繁的动画参数我们使用哈希值进行快速访问
int runHash=Animator.StringToHash("Run");

//下面设置/获取动画参数均有使用String参数名称进行映射的重载和使用哈希值进行映射的重载


//获取设置Float类型参数,通常结合Input轴线
animator.GetFloat(blendHash);
animator.SetFloat(blendHash, Input.GetAxis("Horizontal"));

//获取设置Int类型参数
animator.GetInteger(intHash);
animator.SetInteger(intHash,Number);

//获取设置Bool类型参数
animator.GetBool(boolHash);
animator.SetBool(boolHash, true / false);

//触发,取消触发Trigger的方法
animator.SetTrigger(jumpHash);
animator.ResetTrigger(jumpHash);

“第三题:SetFloat的那个damp是怎么用的?”

“damp翻译过来一般是阻尼的意思,你可以理解为缓行。这样Fload值会渐变过去,而不是一下子变成设置的Float值,这个在有些情况下很有用,比如人物的速度。玩家按下W的时候,应该是一个逐渐从0到最大速度的过程,而不应该一下从0到最大速度,这时候就可以用到damp。如果你对那两个参数还不知道怎么设置,可以看一下这个公式:”

image.png

简单总结一下:

  • Animator中可以设置参数,用来控制Transition的变化
  • Has Exit Time也是transition切换的一个条件,只有transition的所有条件都满足时才会进行切换
  • 在代码中可以使用Animator类中的SetXXX方法控制参数,进而控制状态的转换。
三、实例
1.控制动作

在上一节中讲过AnyState,也可以参考Unity3D基础42:AnyState大法

AnyState的用法前面其实已经讲过了,就是所有状态的代表,如果AnyState向状态B连线,条件为A,那么同等于所有状态都向B连了一条条件为A的线。说白了,AnyState大法就相当于是动画池,不管你当前的状态,我要你现在干什么,你就立刻去干什么。

现在把想要的几个动作,使用AnyState连接起来


image.png

然后新加一个变量去控制动作切换


image.png

image.png

这里注意Can Transition To Self,意思是自己触发自己,要取消勾选。把Conditions的Equals条件,每个Transitions都设置为不一样的,比如0,1,2,3,4

新建一个脚本,绑定在角色身上

using UnityEngine;

public class CheAnimatorController : MonoBehaviour
{

    public Animator ani;// 设置 Animator 参数,获取 Animator

    private void Update()
    {
        // 按下不同键触发不同动画
        if (Input.GetKeyDown(KeyCode.Alpha0))
        {
            ani.SetInteger("Animation_Int", 0);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            ani.SetInteger("Animation_Int", 1);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            ani.SetInteger("Animation_Int", 2);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            ani.SetInteger("Animation_Int", 3);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha4))
        {
            ani.SetInteger("Animation_Int", 4);
        }
    }

}

现在按下键盘上的0,1,2,3,4就能看到角色触发不同的动作了。

2.使用动画中的位移

这种方式需要先设置两个地方:
物体上Animator组件的Apply Root Motion需要勾选,相当于就是把动画中的位置修改应用到物体上;
人物的动画类型需要设置为Humanoid。

image.png
image.png

以walking动画为例,下载到的资源是Generic类型的,无法看到角色位移。现在我强制改为Humanoid后,确实位移了,但是人物的动作也消失了!!!

然后参考新的动画系统中适应root motion的那种带位移信息动画该如何做最后一条回复

Animation Type不需要选humanoid,指定Root node为你动画骨架的root,点击Apply


image.png

顶部切换到Animations往下拉,会看到Root Transform等的配置,如图配置就行


image.png

现在把角色拖到场景里,勾上Apply Root Motion,确实播放动画时,角色坐标移动了。

四、Root Motion详解

这里提前把大智第六部分放过来:Unity动画系统详解6:如何做好角色的移动动画?(Root Motion详解)

“智哥,自从用了混合树来做人物移动,腰不酸腿不疼,思路更清晰了,一口气能写12小时代码!”
“哟,疗效这么好,我看你应该再码12个小时”
“那也没问题,你来看看我做的这个人物的混合树是不是棒极了”
小新信心满满地打开混合树,给大智看。
“嗯,这个混合树确实做的不错,不过你这个角色还不能用啊!”
“怎么不能用了,你看这个走起来不是走的好好的么?”
“你走到那个坡上去试试”
小新操作人物往坡上走,只见直接穿了进去。
“哎?我好像忘了给人物加碰撞了,等我给他加上”
小新给角色加上Collider和Rigidbody组件,再次操作人物往坡上怼过去。
“智哥,你看现在OK了吧?”
“别急,你再下坡看看”
“哎,这怎么不受重力啊,怎么不会掉下去!明明加了刚体组件啊”
“这个是因为动画在控制人物的Y轴,所以才没有掉下去”
“但是我不都设置Apply Root Motion了么,咋还这样呢?”

“那你知道Root Motion到底是什么?”
“那不就是……人物的动画会带动人物移动嘛”
“这只是他的一个表现,这时候你该去好好理解下Root Motion到底是什么了,这个Unity的文档中有,你去好好看一下吧!”

1.Body Transform(身体变换)

Body Transform是角色的质心(重心)。用于Mecanim系统的重定向引擎中来提供稳定的模型移动。身体朝向是角色模型在T姿势下上身和下身朝向的平均值。

Body Transform和朝向存储在Animation Clip中,这两个是Animation Clip中存储的唯二世界空间的曲线,其他的动画曲线都是以相对body transform的形式存储的。

2.Root Transform(根变换)

Root Transform是body transform在Y平面上的投影,并且是运行时计算的。每一帧Root Transform的变化实时计算。然后Transform的变化会被应用到GameObject上从而让物体移动。


image.png
3.调整Root Transform

通过对Animation Clip的设置来控制Body Transform投影到Root Transform的结果。


image.png
4.Root Transform Rotation

用于设置Root Transform的朝向(旋转)。

  • Bake into Pose:选中后,角色的朝向会基于body transform(Pose)。Root Orientation会是一个常量,意味着Animation Clip不会旋转这个物体。只有AnimationClip的开始和结束位置旋转相似的时候,才应该使用这个选项。可以通过右边的绿灯判断。通常用于向前直行的走或跑的动画。

  • Base Upon:可以设置动画的朝向基于的地方。

    • Body Orientation:动画会朝向身体正前方。这个设置适用于大多数身体朝前的动画,比如走跑跳。但是如果动画是向左或向右平移的话,会有问题。这时候可以使用下面的Offset来调节角色的朝向。
    • Original:有的动画师会给动画手动加上旋转,确保动画的朝向正确,这时候可以使用这个选项,一般就不用再手动调整Offset了。
  • Offset:基于Base Upon的设置,调整偏移量。

5.Root Transform Position (Y)

用于设置Root Transform位置的Y轴位置。

  • Bake into Pose:选中后,动画的Y轴的运动会保留在Body Transform(Pose)上。Root Transform的Y轴会是一个常数(不会受动画影响变化),也就是意味着动画不会改变物体位置的Y值。右边有一个绿灯指示动画起始位置和结束位置的高度是否一致,可以看出动画是否适合使用此选项。
    大多数的动画应该选中此选项,除了那些会改变物体高度的动画比如跳起、跳下这些动画。
    注意:Animator.gravityWeight是由Bake Into Pose position Y控制的。选中时gravityWeight = 1,不选中时gravityWeight = 0。gravityWeight用来在state转换时进行混合。
  • Base Upon:和Root Transform Rotation设置类似,除了Original 或 Mass Center (Body)选项外,还有一个Feet选项。Feet选项非常适合改变物体高度的动画(不勾选Bake Into Pose)。使用Feet时,Root Transform Position Y会匹配骨骼中脚部的Y位置(更低的那个)。Feet选项可以避免混合或转换时浮空的现象。
  • Offset:可以设置高度的偏移量。
6.Root Transform Position (XZ)

用于设置Root Transform位置的XZ轴位置。

  • Bake Into Pose:通常用于原地不动的动画(动画在XZ轴上的位置为0)。可以用来去除动画循环累计的误差,造成位置的移动。也可以通过设置Based Upon Original来强制使用动画师设置的位置,否则会使用角色的重心作为Root。
  • Loop Pose
    Loop Pose(比如混合树或Transition中的混合)会基于Root Transform。Root Transform在每帧被计算出来后,动画的位置会相对Root Transform。开始帧和结束帧的差别会被计算出来,然后分布到动画的0-100%。完全没看明白,得问大智了
7.Generic Root Motion

Generic和Humanoid基本是类似的,但是Generic的动画的Root Transform是手动设置的Root Node属性。

8.问题

“大智,我看完Root Motion的文档了,不过还是有点云里来雾里去的,你能不能用简单的几句话说说设置Root Transform的作用是什么?”

“简单来说,如果不设置Root Transform中的Bake Into Pose,动画中的曲线会影响物体的Root Transform,而勾选了Bake Into Pose以后,动画的曲线就不会影响物体的Root Transform。再直白点说,比如勾选了Root Transform Position (Y)的Bake Into Pose,那动画就不会影响物体的Y轴位置了。对于你遇到的刚体不会掉落的问题,也能解决了。”

“emmm,大概能明白,我还是得去试一试看看不同的效果。第二个问题:什么是重定向?”

“重定向就是把A角色做的动画用到B角色上。如果A和B两个角色的骨骼结构完全一样,那动画可以直接重用。但是如果A和B的骨骼结构不一样,但是是Humanoid类型的,可以使用Unity中的Retargeting系统,这个文档里也有,建议你先去看看。”

“哦,我现在貌似还用不到,等我用到的时候去查一下。最后一个问题哈,Loop Pose的作用是什么?我看了半天也没看明白是什么意思”

image.png

“看不明白很正常,文档中那个解释确实有些不太直观。这个作用是,如果一个循环动画的首尾帧有差别,选中这个选项Unity会给你插值,让首尾帧看起来是连贯的,循环起来没有缝隙,但是可能会看起来有些奇怪。不过我们使用的动画,一般动画师都会做成无缝循环的动画,所以这个选项也不经常使用。”

五、Root Motion详解二

转自学习笔记 --- Unity动画系统
所谓根节点就是一个角色的最高父节点,这个最高父节点在虚拟世界中的位置将决定角色的位置,根节点运动时,整个角色就会开始运动(走动,跑动)

在Animator组件中我们可以选定是否要引入rootMotion,即将动画中根节点的位移,植入到Animator所在的物体上。根节点的位移植入,可以理解为,是否要让动画师来决定角色的位移。

动画师制作一段奔跑的动画,角色就会在动画师的那个虚拟世界(3dMax Maya)里自由奔跑...(根节点在动画制作软件的虚拟世界中运动)

然而使用Unity制作角色,并应用那段动画时,就会产生一个问题,角色在Unity创建的游戏世界中的奔跑的这个位移效果要如何实现?

RootMotion总共有三种状态,勾选,不勾选,以及Handled by Script


image.png
    private void OnAnimatorMove()
    {

    }

当我们在Animator所在脚本上添加OnAnimatorMove方法后,就是Handled by Script了

1.模式一:RootMotion不勾选,引用位置/方位坐标为本地坐标

这种情况下,在外部(3dMax Maya)中制作动画时,根节点的位置坐标,旋转坐标,将转为根节点在Unity中父节点下的本地位置和本地旋转。

此时我们需要把角色挂在Unity中的一个节点下方,而不能直接让角色作为Unity世界的子物体。否则根节点的位置和方位就会被转换为角色的世界坐标和世界朝向,那么播放动画时,角色将返回3dMax/Maya中制作动画时,所在的位置运行动画,无法在Unity世界中进行自由移动。

这种情况下通常是进行了“围绕原点的动画制作”,我们会将角色挂在一个Unity中的节点下方,让根节点中的位置和朝向,转换为在这个节点下的本地位置和朝向。制作动画时,围绕原点的运动,在Unity中就会表现为围绕这个父节点进行运动。我们则可以在角色围绕父节点进行动画运动的同时,通过父节点在Unity世界中的运动,带动角色进行运动。

例如围绕主角飞行的幽浮,制作动画时,我们围绕虚拟世界的原点进行动画制作。Unity中我们将幽浮作为主角的子节点,从而幽浮就会围绕主角飞行,并跟随主角在Unity世界中运动

2.模式二:RootMotion勾选,位移/角位移植入

这种情况,Unity将计算动画制作时,根节点的位移和角位移,并植入到Animator所在的物体,让动画制作时根节点的位移和角位移,影响到Animator所在物体,在Unity世界中的位移和朝向变换。

这种情况下我们不必进行围绕原点的动画制作,因为被植入的是根节点的相对运动。通常这种情况下,animator所在的物体,将直接作为Unity世界的子物体,让被植入的位移和角位移,作为在Unity世界中的位移和角位移。

使用这种情况,就是所谓的“让动画师来控制”,角色移动可以考虑采用这种模式来避免滑步

根据Animator组件所在的物体,位移/角位移的植入有三种不同的表现形式:

  • 物体没有Rigdbody,也没有character controller组件,则位移/角位移将被植入到Transform变换中
  • 物体拥有Rigdbody组件,则位移/角位移将被植入到Rigdbody的速度和角速度中,根节点的位移和角位移将借助物理系统的刚体速度,刚体角速度来体现
  • 物体拥有Character Controller组件,则位移/角位移将被植入到Character Controller的速度和角速度中,根节点的位移和角位移,将借助Character Controller的运动来实现
3.模式三:脚本中实现了OnAnimatorMove,根节点控制权移交

如果你只是定义了一个空方法,这种情况下将完全忽略根节点的位置/方位信息,也不会将根节点的位移/角位移植入到Unity中。这种情况下,播放人物/角色动画时的效果就变为了,在我们设定好的位置原地做动作,但并不产生运动效果(这个动作是根节点下的子节点的动画效果,根节点自身的位置位移/方位角位移会被消除)。

此时根节点可以直接作为Unity世界的子物体,你可以通过脚本控制根节点在Unity世界中进行运动,根节点的控制权被移完全交给了Unity,动画不会对根节点产生任何影响。角色运动的控制代码你可以写在OnAnimatorMove,也可以只定义一个空方法,将运动控制写在Update里。

不过使用OnAnimatorMove最大的魅力在于:你可以在OnAnimatorMove中通过这个方法,启用Apply Root Motion的效果。

    private void OnAnimatorMove()
    {
        //此时相当于勾选Apply Root Motion的效果
        animator.ApplyBuiltinRootMotion();
    }

借此你可以通过分支,在需要的时候由Unity来控制角色运动。一些动画表现的思路会用到这种模式,例如一个锁定怪物的冲刺斩击的动作,实际在应用到开发中时,我们需要通过投射检测,决定角色需要冲刺的目标位置。此时我们就可以通过OnAnimatorMove,平时使用Apply Root Motion进行角色的运动控制,而在冲刺斩击时,由脚本代码控制角色位移到合适的位置,角色动画只做动作即可。

你可能感兴趣的:(Unity 动画系列四 代码控制动画实例 和 RootMotion)