Unity 动画系统

动画系统包含:

  • 动画片段 Animation Clip,记录物体变化的信息,可以是角色的闪转腾挪,也可以是一扇门的开闭
  • 动画状态机 Animator Controller,根据设置切换动画片段
  • 动画组件 Animator,Animation
  • 替身 Avatar,对人形动画进行复用

动画复用

把一个 .anim 的动画文件作为文本打开
在这里插入图片描述
里面有个path属性记录动画要操作的对象的路径,如果根据path找不到对象,这个动画就会失效

在这里插入图片描述
其他物体要使用这个动画就必须包含路径一致的对象,否则Animation窗口里就会显示Missing

在这里插入图片描述
对于人形动画的fbx文件,选中动画按Ctrl + D就可以把动画复制出来,同样作为文本打开

在这里插入图片描述
这里path为空,而attribute记录Avatar中对应的骨骼节点和进行什么操作

Avatar 替身系统

在这里插入图片描述
在mixamo上下载一个动画文件

在这里插入图片描述
不同DCC软件制作的骨骼节点的名称可能不一样的,不能直接使用该动画,这时候需要借助Avatar建立骨骼和Unity肌肉系统的映射关系

假设A,B两个模型的骨骼名称不同,要把A模型的动画片段A复用到B模型上
在这里插入图片描述
步骤:
1.创建AAvatar,AAvatar中保存模型A骨骼和Unity肌肉结构的对应关系,骨骼信息也被保存到Avatar文件中
2.创建BAvatar,BAvatar中保存模型B骨骼和Unity肌肉结构的对应关系
3.通过AAvatar,把动画片段A从描述A的骨骼变化翻译为描述unity肌肉拉伸
4.模型B使用动画片段A,通过BAvatar把动画文件中对Unity肌肉结构的描述翻译为对B模型骨骼变化的描述

具体操作
在这里插入图片描述
在模型的Rig面板中选择人形动画,点击Apply

Avatar DefinitionCreate From This Model 从这个模型本身创建Avatar,把当前模型的骨骼与Unity肌肉结构建立对应关系
Copy from OtherAvatar 从其他Avatar中复制骨骼层次结构、绑定信息等,需要确保两者具有相似的骨骼层次结构和绑定信息,模型不会创建新的Avatar,只会导入动画
Skin Weights蒙皮或者mesh上的节点可以被几个骨骼所影响
Strip Bones勾选后,Unity会自动删除所有不必要的骨骼,并将相邻的骨骼合并为一个单独的骨骼。这样可以减少骨骼数量和顶点权重数量,从而提高游戏性能
Optimize Game Objects勾选后会删除模型上的骨骼,从Avatar中读取骨骼信息

在这里插入图片描述
模型中会出现Avatar,点击Inspector界面上的Configure Avatar进入配置界面
在这里插入图片描述
这里可以看到骨骼和Unity肌肉的映射关系,这些Unity基本已经配置好了,一般不需要修改
在这里插入图片描述
动画的Rig面板中选择Copy From Other Avatar,并选择来源模型的Avatar,点击Apply
在模型的Animator组件中选择各自的Avatar文件,这时动画就可以复用了

Animator

在这里插入图片描述

Apply Root Motion有的动画片段自带位移,勾选后就会把位移应用到对象上,如果通过脚本控制位移就不勾选
Update ModeNormal 与Monobehaviour的Update同步
Animate Physics 与FixedUpdate同步,如果角色动画需要与物理系统交互,选这个
Unscaled Time 与Update同步,忽略TimeScale
Culling ModeAlways Animate 不剔除,始终运行动画
Cull Update Transforms 不可见时会禁用重定向,ik,Transform变化
Cull Completely 不可见时停止模拟,再次出现时,从停止的状态继续模拟

运行时最下面还会显示动画片段相关信息

在这里插入图片描述
在Animator中动画状态分为3种:单独的动画片段,多个片段组成的混合树,另一个状态机

在这里插入图片描述
在Animator窗口右上角有个Auto Live Link,选中后会在窗口内聚焦当前播放的动画状态
在这里插入图片描述

动画片段(AnimationClip)

在这里插入图片描述

Tag给动画加标签,在代码中就可以根据动画的标签执行不同的逻辑
Motion这个动画状态管理的动画片段,如果是混合树就显示管理的混合树
Speed动画播放速度,负值就是倒放,脚本无法修改Speed数值
Multiplier勾选右边的Parameter激活,选择Parameters中的一个浮点型参数关联,动画的速度就等于 Speed * Multiplier
Motion Time范围0~1f,修改后会使动画停在特定时间点,0是开始,1是结束
Mirror镜像动画
Cycle Offset播放时的偏移值,0表示不偏移,0.5表示从中间开始播放,偏移不是切割,动画还是会完整播放,只是动画的起始点改变
Foot IK使用IK矫正,把脚步实际位置向IK Goal位置拉近一点,需要设置IK Goal权重
Write DefaultsAnimator触发OnEable时,将遍历AnimatorController中的所有片段,收集所有片段的属性值。如果某个片段中没有描述某些属性的变化,是否为其写入默认值

正向动力学(Forward Kinematics):常见的动画一般是由骨骼根节点(人形动画是屁股)到末梢骨骼节点依次计算旋转位移缩放来决定每个骨骼的最终位置
反向动力学(Inverse Kinematics):末梢骨骼位置确定,反向计算各个父节点骨骼的旋转位移缩放,比如脚放在台阶上,反向计算其各个父骨骼

在这里插入图片描述
使用Avatar把骨骼系统转为肌肉系统后,双手,双脚位置可能出现一些偏移,Unity会保存转换前骨骼系统下手脚的正确位置,并把这些位置放在IK Goal(目标位置)上,也就是上图中双手,双脚位置的红球,手肘处的红球和膝盖处的红球是IK Hint(辅助位置),通过它防止肘部关节出现奇怪的扭曲,Foot Ik参考的是IK Goal的初始位置
在这里插入图片描述
使用IK需要在Layer上激活IK Pass,这样就可以在脚本中调用IK相关方法

public class AnimatorTest : MonoBehaviour
{
    [Range(0, 1)]
    public float weight;
    
    private Animator animator;
    //动画状态
    private AnimatorStateInfo stateInfo;
    //关联Multiplier参数
    private float animationMultiplier = 1;
    //Multiplier映射的hash值
    private int multiplierHash;

    void Start()
    {
        animator = GetComponent<Animator>();
        multiplierHash = Animator.StringToHash("Multiplier");
        animator.SetFloat(multiplierHash, animationMultiplier);

        //获取当前动画状态,0表示base layer
        stateInfo = animator.GetCurrentAnimatorStateInfo(0);
        //判断动画的Tag是否为Anim
        if (stateInfo.IsTag("Anim"))
        {
            //do something
        }
    }

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.W))
        {
            animationMultiplier += 0.1f;
            animator.SetFloat(multiplierHash, animationMultiplier);
        }
    }

    /// <summary>
    /// layer上开启IK Pass
    /// </summary>
    private void OnAnimatorIK(int layerIndex)
    {
        //设置右脚IKGoal的位置
        animator.SetIKPosition(AvatarIKGoal.RightFoot, new Vector3(1,0,0));
        //权重越高,右脚越靠近设置的位置
        animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, weight);
    }
}

调用时机
在这里插入图片描述
在这里插入图片描述
当Animator的Update Mode为Nomal或Unscaled Time,OnAnimatorMove和OnAnimatorIk方法与Update同步,当Update Mode为Animate Physics时,与FixedUpdate同步

状态转换(Transition)

在这里插入图片描述
两个状态之间可以添加多个转换,这样会变成三个箭头,选中右侧的某个转换就可以为其单独设置转换条件,比如条件1和条件2都会触发 idle -> walk 这个转换,分别设置转换条件即可,它们之间是 “或” 的关系

转换的优先级:
1.如果有勾选了Solo,只执行勾选的转换,不考虑其他的
2.勾选的Solo的转换中,哪个条件先满足就执行哪个转换
3.如果条件同时满足,优先执行上面的转换
4.勾选Mute的转换不会执行

在这里插入图片描述
上方可以修改转换名称

Has Exit Time勾选表示条件满足也要播放到某个时间点才执行转换,不勾选需要设置Conditions
Exit Time比例值,0表示从第一帧转换,0.5表示从中间帧开始
Fixed Duration勾选表示转换持续时间按秒计算,否则按百分比计算
Transition Duration转换持续时间
Transition Offset0表示下一个动画状态从第一帧开始播放,0.5表示下一个动画状态从中间一帧开始播放
Transition Duration转换持续时间
Interruption SourceNone 转换不能被打断
Current State 可以被其他相同起始状态的转换打断,设置Ordered Interruption需要优先级更高
Next State 可以被其他相同目标状态的转换打断
Current State Then Next State 有相同状态的转换都可以打断,但起始状态一样的优先级更高
Next State Then Current State 有相同状态的转换都可以打断,但目标状态一样的优先级更高
Ordered Interruption勾选表示转换按照优先级排序,优先级更高的状态才能打断
Conditions转换条件

状态机里只有四个状态,从A到D。当中的所有转换由相应的触发器变量(trigger)来控制
在这里插入图片描述
与A相关的转换有三个
在这里插入图片描述

选中A到B的转换,把Interruption Source设置为Current State且Ordered Interruption勾选
当激活触发器来启动A->B的转换时,从A到B的转换就可以被某些同样从A出发的转换所打断了,因为勾选Ordered Interruption,转换只能被优先级更高的A到C的转换打断

在这里插入图片描述
Trigger类型的参数只要激活就会触发转换,如果与Trigger相关的动画转换并没有被执行,Trigger会一直处于激活状态,直到转换被执行
Conditions可以设置多条,它们之间是 “与” 的关系,必须同时满足才能触发转换,注意如果勾选Has Exit Time,即使条件满足也得等动画播放到Exit Time才能触发转换

Root Motion动画

Root Motion动画自带根位移,会把动画上的位移应用到对象上,有效避免角色动画和实际位移不同步产生的滑步现象。动画文件会在每一帧里直接修改对象的坐标值和角度值(绝对值),而Root Motion则通过相对位移和转角来移动游戏对象

Animator勾选Apply Root Motion后,Unity会让游戏对象会乘以缩放矩阵,旋转矩阵,平移矩阵

public class AnimatorTest : MonoBehaviour
{
    private Animator animator;

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    /// <summary>
    /// 使用该方法用代码替代动画修改对象位移旋转
    /// </summary>
    private void OnAnimatorMove()
    {
        //animator.deltaPosition已经考虑了缩放值
        transform.position += animator.deltaPosition;
        transform.rotation *= animator.deltaRotation;
    }
}

在这里插入图片描述
勾选Apply Root Motion并实现OnAnimatorMove,动画由脚本控制

在Generic动画中的使用Root Motion,只需要管理一根根骨骼Root node,实际工作中,美术一般会给模型单独制作一根根骨骼,这个骨骼的作用就是记录模型的位移旋转
在这里插入图片描述
Unity会把Generic动画对这根骨骼的操作当作对游戏对象的操作,Apply Root Motion会把根骨骼节点上的绝对坐标和绝对角度,转换为游戏对象的相对位移和相对转角

在这里插入图片描述
Root Transform Rotation(绕y轴旋转),Root Transform Position(Y)(y方向位移),Root Transform Position(XZ)(水平方向位移),这三个属性与Root Motion相关的动画
在这里插入图片描述
动画文件中的Root Q和Root T表示对游戏对象的旋转和位移,勾选Root Transform Rotation下的Bake Into Pose就不会旋转游戏对象,而是去旋转根骨骼节点,当我们不希望动画带动游戏对象旋转时,就需要勾选这个Bake Into Pose。后面的loop match是检查动画第一帧和最后一帧的吻合度,红色表示不吻合,绿色表示吻合,红色就不要勾选Bake Into Pose
Root Transform Position(Y)下的Bake Into Pose同理,勾选后动画不会影响游戏对象y方向上的位移,而是去修改根骨骼的位移

Root Transform Rotation绕y轴旋转
Bake Into Pose勾选表示旋转只影响骨骼和蒙皮(外观),并不影响游戏对象的旋转,能不能勾选参考loop match
Based Upon游戏对象开始时对准的方向
Original 动画本来的朝向,美术制作时设置的朝向,一般选这个
Root Node Rotation(Generic) 对准根骨骼节点方向,一般不准确
Body Orientation(Humanoid) 对准上半身前方,一般不准确
Offset对旋转做偏移
Root Transform Position(Y)y方向位移
Bake Into Pose勾选表示y方向位移只影响骨骼和蒙皮(外观),并不影响游戏对象的位置,能不能勾选参考loop match
Based Upon垂直方向上把模型的哪个位置对齐到游戏对象的原点
Original 美术在设置的原点,一般选这个
Root Node Rotation(Generic)将根骨骼作为原点
Center of Mass(Humanoid) 质心作为原点
Feet(Humanoid) 脚作为原点,动画复用可能导致Original不准,此时可以选这项
Offsety方向偏移量
Root Transform Position(XZ)水平方向位移
Bake Into Pose勾选表示水平方向位移只影响骨骼和蒙皮(外观),并不影响游戏对象的位置,能不能勾选参考loop match
Based Upon水平方向上把模型的哪个位置对齐到游戏对象的原点
Original 美术在设置的原点,一般选这个
Root Node Rotation(Generic)将根骨骼作为原点
Center of Mass(Humanoid) 质心作为原点

在这里插入图片描述
在动画预览界面点击坐标轴图标就可以显示center of mass,这个质心也被称为body transform,它的位置接近hips骨骼
红色箭头为质心在水平面的投影,我们可以把这个投影当作Root Motion的根骨骼节点,这个点被称为root Transform,代码中这样访问它的位置和方向

private Animator animator;
void Start()
{
    animator = GetComponent<Animator>();
    Vector3 bodyTransformPos = animator.bodyPosition;
    Quaternion bodyTransformRotation = animator.bodyRotation;
    Vector3 rootTransformPos = animator.rootPosition;
    Quaternion rootTransformRotation = animator.rootRotation;
}

Humanoid动画中的Root Motion的原理:Unity会计算处一个root transform,Root Motion会把动画文件中描述的root transform的坐标和角度值,转换为相对位移和相对转角,并以此来移动游戏对象

动画切割

在这里插入图片描述
点击动画的fbx文件,在Animation页签的Clips点击 “+” 新建一个动画片段,移动左右的滑块调整动画范围,或者直接设置动画的开始帧和结束帧,下面的loop mathc表示动画起始帧和结束帧的相似程度,绿色表示相似度高,黄色表示不够相似,红色表示完全不同,如果要切割一个循环动画,需要保持绿色,否则不用管
在这里插入图片描述
设置好点击下面的Apply,就会生成相应的动画片段

动画曲线(Curves)

我们可以为动画文件添加一个Curve,或者说添加一个属性,把一些额外信息写入到动画中
在这里插入图片描述
拖动预览窗口中的滑块调整位置,点击添加关键帧后就可以输入数值调整当前帧的数值,也可以点开Curve窗口进行详细调整曲线
在这里插入图片描述
点击Apply保存,Animation窗口中就会显示刚才添加的曲线属性
在这里插入图片描述
在动画状态机中新建一个曲线同名float参数,Animator会自动把动画中该曲线的值赋给这个参数,最后在脚本中读取这个参数就行

在这里插入图片描述
另外我们可以在脚本中直接定义一个动画曲线Animation Curve

public class CurveTest : MonoBehaviour
{
    public AnimationCurve curve;
    private float _time;

    private void Update()
    {
        _time = (_time + Time.deltaTime) % 1;
        Debug.LogError(curve.Evaluate(_time));
    }
}

动画事件(Events)

动画播放到某一帧时触发事件,比如动画播放到某一帧时生成特效
在这里插入图片描述
在动画的fbx文件中找到Events,拖动下面动画预览的位置就可以调整事件触发时机,点击左侧Add Event按钮添加事件,或者右键添加事件,出现一个蓝色事件标记
在这里插入图片描述
添加后定义事件调用的方法(Function),支持float,int,string,object4种类型的参数,点击Apply保持

	public void PlayEffect()
    {
        Debug.LogError("test");
    }

脚本中需要定义同名public方法,脚本和Animator挂在同一个物体上,当动画播放到Event时,Animator会发出一个message,并以此调用这个游戏对象上所有组件里的同名方法
在这里插入图片描述
如果是单独的动画片段文件,打开Animation窗口,点击Add Event按钮添加事件

Animator Layers(动画层级)和Avatar Mask(替身蒙版)

有时候需要把多个动画组合到一起,比如行走的动画和持枪的动画组合到一起就是持枪行走动画
在这里插入图片描述
在Animator中可以创建多个Layer,分别控制身体的不同部分,通过调整权重来实现不同层级之间动画的组合
多个Layer时,下面的Layer优先级更高
在这里插入图片描述

Weight当前动画层级的权重
MaskAvatar Mask 设置身体哪些部分受动画影响,不设置会影响全身,设置后会有个M标记
BlendingOverride 当前层级的动画覆盖上面层级的动画,比如玩家受伤后,受伤移动的动画替换原本正常移动的动画
Additive 当前层级的动画和上面层级的动画叠加,比如玩家运动起来,把疲劳喘息的动画叠加到原本的动画上。选择Additive该层级的Avatar Mask上的动画curve不能是平的,否则没效果
Sync勾选后可以在Source Layer中选择当前层级要和哪个层级保持同步,会同步动画状态和它们之间的转换关系,但不会同步BlendTree。当前Layer和它同步Layer使用的动画时长可能不同,默认会对当前Layer的动画时长进行缩放,使其与同步Layer的动画时长一致
Timing勾选后动画时长由当前Layer和同步Layer共同决定,根据当前Layer的权重计算
IK Pass使用IK的话需要勾选

在这里插入图片描述
右键创建Avatar Mask
在这里插入图片描述
如果是人形动画,则在Humanoid中选择动画影响的部分,绿色表示受影响,红色不受影响,点击旁边空白处可以一次性选中或反选,图中的4个IK表示IK Goal,这里要播放手部动画,因此选择了手臂,手部及两个IK Goal
如果不是人形动画,在Transform中选择Avatar导入骨骼,勾选受影响的骨骼即可

1D混合树

在这里插入图片描述
创建混合树,双击进入,默认创建1D混合树,也就是通过一个变量来混合
在这里插入图片描述
点击 “+” 添加三个Root Motion动画
在这里插入图片描述

Parameter关联一个浮点型参数
三角形示意图横轴是Speed的值,纵轴是片段的权重,随着Speed的值增大,第一个片段权重减小,第二个片段权重增加
Threshold参数Speed为多少时,片段的权重为1
时钟符号片段播放的速度
人形符号是否要镜像动画,仅限人形动画使用
Automate Thresholds取消勾选 Automate Thresholds就可以修改Threshold的值
Compute Thresholds根据片段的一些属性重新计算Threshold,需要Root Motion动画
Speed 速度的绝对值
Velocity X/Y/Z 分别表示Root Motion在三个方向上的位移速度
Angular Speed(Rad)旋转速度,弧度每秒
Angular Speed(Deg)旋转速度,角度每秒
Adjust Time ScaleHomogeneous Speed 自动计算动画播放速度,使多个动画的移动旋转速度相等,idle动画不需要计算,可排除
Reset Time Scale 将所有片段的速度设为1

上图中Compute Thresholds选择Velocity Z,即根据前进后退方向上的速度计算Threshold,动画前进的速度大约是1.745667,后退的速度大约是-1.426688。Adjust Time Scale选择Homogeneous Speed,使得动画速度一致
在这里插入图片描述
Root Motion的速度不一定是匀速的,这里的1.745667是平均速度
在这里插入图片描述
在Animation窗口中观察z方向移动的动画曲线,曲线分成多段并不是线性的

注意:Unity动画做混合时,包含非线性插值计算,无法保证参数Speed为1时,动画速度也为1,这时可以调整播放速度来控制移动速度,把播放速度改成 1/1.745667,这样前进速度就是1

在这里插入图片描述
动画中前进后退的速度是针对原本骨骼的,使用Avatar复用动画后会根据骨骼的缩放值调整速度

public class PlayerMoveTest : MonoBehaviour
{
    private Animator _animator;
    private float _forwardSpeed = 1.745667f;
    private float _backwardSpeed = 1.426688f;
    private float _targetSpeed;
    private float _currentSpeed;
    void Start()
    {
        _animator = GetComponent<Animator>();
        //Root Motion会考虑物体的缩放值,humanScale记录了Avatar对骨骼的缩放
        //我们不希望整个animator的播放速度都受到影响,修改特定动画状态的Multiplier属性
        _animator.SetFloat("Multiplier",  1 / _animator.humanScale);
        Debug.Log("humanScale: " + _animator.humanScale);
    }
    
    void Update()
    {
        Move();
    }

    void Move()
    {
        _currentSpeed = Mathf.Lerp(_currentSpeed, _targetSpeed, 0.5f);
        _animator.SetFloat("Speed", _currentSpeed);
        Debug.Log("velocity.z: " + _animator.velocity.z);
    }

    public void PlayerMove(InputAction.CallbackContext callbackContext)
    {
        Vector2 movement = callbackContext.ReadValue<Vector2>();
        _targetSpeed = movement.y > 0 ? _forwardSpeed * movement.y : _backwardSpeed * movement.y;
    }
}

在这里插入图片描述
这里使用了Input System

Root Motion与Rigidbody一起使用

引入Root Motion是为了避免实际位移和动画表现位移不同步,Root Motion解决的是同步问题,而不是位移,要控制位移就需要通过脚本的OnAnimatorMove方法,注意:animator的update mode改为animate physics,动画记得Bake into pose

public class PlayerMoveTest : MonoBehaviour
{
    private Animator _animator;
    private Rigidbody _rigidbody;
    private float _forwardSpeed = 1.745667f;
    private float _backwardSpeed = 1.426688f;
    private float _targetSpeed;
    private float _currentSpeed;
    void Start()
    {
        _animator = GetComponent<Animator>();
        _rigidbody = GetComponent<Rigidbody>();
        _animator.SetFloat("Multiplier",  1 / _animator.humanScale);
    }

    private void OnAnimatorMove()
    {
        Move();
    }

    void Move()
    {
        _currentSpeed = Mathf.Lerp(_currentSpeed, _targetSpeed, 0.5f);
        _animator.SetFloat("Speed", _currentSpeed);
        //物理引擎中会修改rigidbody在y方向上的速度
        Vector3 vector3 = new Vector3(_animator.velocity.x, _rigidbody.velocity.y, _animator.velocity.z);
        //用animator从Root Motion动画中提取值,传递给受物理引擎影响的rigidbogy
        _rigidbody.velocity = vector3;
    }

    public void PlayerMove(InputAction.CallbackContext callbackContext)
    {
        Vector2 movement = callbackContext.ReadValue<Vector2>();
        _targetSpeed = movement.y > 0 ? _forwardSpeed * movement.y : _backwardSpeed * movement.y;
    }
}

Root Motion与CharacterController一起使用

CharacterController是一个简单的角色控制组件,如果角色不需要做真实的物理模拟,可以通过CharacterController简单的处理移动和碰撞。CharacterController继承自Collider,本身就是一个多功能碰撞体

Slope Limit最大爬坡角度
Step Offset角色能登上的台阶高度
Skin Width两个碰撞器相互穿透的深度与蒙皮宽度相同。Skin Width越大,抖动越小。Skin Width过低会导致角色卡住。较好的设置是将此值设为半径的 10%,当运动方向和碰撞方向一致时会起作用
Min Move Distance如果角色试图移动的距离低于指定值,则根本不会移动。这可以用来减少抖动。在大多数情况下,该值应保持为 0
public class PlayerMoveTest : MonoBehaviour
{
    public float RotateSpeed = 1000;

    private Animator _animator;
    private CharacterController _characterController;
    private Vector2 _playerInputVec;
    private bool _isRunning;
    private Vector3 _playerMovement;
    private Transform _playerTransform;
    private float _currentSpeed;
    private float _targetSpeed;
    private float _walkSpeed = 1.5f;
    private float _runSpeed = 4.2f;

    void Start()
    {
        _animator = GetComponent<Animator>();
        _characterController = GetComponent<CharacterController>();
        _playerTransform = transform;
    }

    void Update()
    {
        RotatePlayer();
        MovePlayer();
    }

    public void GetPlayerMoveInput(InputAction.CallbackContext ctx)
    {
        _playerInputVec = ctx.ReadValue<Vector2>();
    }

    /// <summary>
    /// 按下奔跑键
    /// </summary>
    public void GetPlayerRunInput(InputAction.CallbackContext ctx)
    {
        _isRunning = ctx.ReadValue<float>() > 0;
    }

    void RotatePlayer()
    {
        if (_playerInputVec == Vector2.zero)
            return;

        _playerMovement.x = _playerInputVec.x;
        _playerMovement.z = _playerInputVec.y;

        Quaternion targetRotation = Quaternion.LookRotation(_playerMovement, Vector3.up);
        _playerTransform.rotation = Quaternion.RotateTowards(_playerTransform.rotation, targetRotation, RotateSpeed * Time.deltaTime);
    }

    void MovePlayer()
    {
        _targetSpeed = _isRunning ? _runSpeed : _walkSpeed;
        _targetSpeed *= _playerInputVec.magnitude;
        _currentSpeed = Mathf.Lerp(_currentSpeed, _targetSpeed, 0.5f);
        _animator.SetFloat("Speed", _currentSpeed);
    }

    private void OnAnimatorMove()
    {
        //使用Move方法需要手写重力
        //_characterController.Move(_animator.deltaPosition);

        //SimpleMove会默认添加重力
        _characterController.SimpleMove(_animator.velocity);
    }
}

2D Simple Directional

2D混合树根据两个变量进行混合
在这里插入图片描述
2D简单方向混合,当你的动画代表不同方向时可以使用,如“向前走”、“向后走”、“向左走”和“向右走”
限制:同一个方向上不能有多个动画片段,如“向前走”,“向前跑”
在这里插入图片描述
Pox X 和 Pos Y 分别表示横向纵向的值,需要在状态机中定义两个变量关联
导入相关的动画,在方形区域内,蓝点表示动画片段,红点表示当前参数所在的位置,注意idle需要归零
选择Velocity XZ,根据动画在XZ方向上的速度计算Threshold,如果数值异常,需要调整下动画的Based Upon
在这里插入图片描述
修改两个参数的值调整红点位置,蓝色圆圈表示该动画在混合树中所占的权重,圆圈越大权重越大
在这里插入图片描述
点击某个动画,蓝色区域表示该动画影响的区域
在这里插入图片描述
点击没有蓝点的地方,就可以看到整个混合树权重的分布状态,蓝色越亮表示能在该区域施加影响的动画越少,颜色越黑表示能在该区域施加影响的动画越多
当原点处有动画片段,最多只能有三个动画片段上有权重,红点会被当作三角形加权重心,反推三个点的权重,它不允许同一个方向上有多个动画片段,因为这样可能找不到合适的三角形
在这里插入图片描述
把idle动画移开原点位置,Unity会假设原点处有个动画片段,计算权重后,把原点处的权重平均分配给所有动画片段

2D Freeform Cartesian

当运动不代表不同方向时使用效果最佳。例如 “向前走不转弯”、“向前跑不转弯”
在这里插入图片描述
它使用了梯度带算法,从红色到紫色,p1的影响值递减
在这里插入图片描述
每个采样点有一个梯形区域显示影响范围

2D Freeform Directional

同一方向上有多个运动,例如“向前走”和“向前跑”
限制:必须包含(0,0)位置处的idle动画,计算量较大
在这里插入图片描述
它使用极坐标下的梯度带算法
图(a)两个范例点p1和p2与原点距离一样角度不一样,梯度带沿着角度方向平均分布
图(b)两个范例点p1和p2角度一样,与原点距离不一样,影响值与长度成比例
图(c)其他情况两个范例点处于两条阿基米德螺旋线上,梯度带在它们之间均匀分布

参考

Unity动画系统详解

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值