动画系统包含:
- 动画片段 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 Definition | Create 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 Mode | Normal 与Monobehaviour的Update同步 |
Animate Physics 与FixedUpdate同步,如果角色动画需要与物理系统交互,选这个 | |
Unscaled Time 与Update同步,忽略TimeScale | |
Culling Mode | Always 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 Defaults | Animator触发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 Offset | 0表示下一个动画状态从第一帧开始播放,0.5表示下一个动画状态从中间一帧开始播放 |
Transition Duration | 转换持续时间 |
Interruption Source | None 转换不能被打断 |
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不准,此时可以选这项 | |
Offset | y方向偏移量 |
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 | 当前动画层级的权重 |
---|---|
Mask | Avatar Mask 设置身体哪些部分受动画影响,不设置会影响全身,设置后会有个M标记 |
Blending | Override 当前层级的动画覆盖上面层级的动画,比如玩家受伤后,受伤移动的动画替换原本正常移动的动画 |
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 Scale | Homogeneous 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)其他情况两个范例点处于两条阿基米德螺旋线上,梯度带在它们之间均匀分布文章来源:https://www.toymoban.com/news/detail-764764.html
参考
Unity动画系统详解文章来源地址https://www.toymoban.com/news/detail-764764.html
到了这里,关于Unity 动画系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!