【Unity实战笔记】第二十二 · 基于SMB的角色控制中遇到的一些问题(斜坡移动鬼畜、落地卡顿、角色突进、头发动画失效等)
- 【Unity实战笔记】第二一 · 基于状态模式的角色控制——以UnityChan为例
- 【Unity学习笔记】第十一 · 动画基础(Animation、状态机、root motion、bake into pose、blendTree、大量案例)
注: 本文紧接上一篇 Unity实战笔记 · 第二一,补录后续遇到的一些问题。
SMB无法使用awake,变量无法只进行一次初始化
解决办法:在OnStateEnter
中添加入Initiate(animator)
方法,Initiate中
检测是否完成过初始化,有就直接跳过。
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
Initiate(animator);
}
// 只进行一次的初始化
private void Initiate(Animator animator)
{
if (_playerInput != null)
{
return;
}
_playerInput = animator.GetComponentInParent<PlayerInput>();
_playerController = animator.GetComponentInParent<PlayerController>();
_playerTransform = _playerController.transform;
_playerRig = animator.GetComponentInParent<Rigidbody>();
_camTransform = Camera.main.transform;
}
这样就避免每次进入OnStateEnter都要GetComponent
赋值一次变量,减少没必要的损耗。
- Unity StateMachineBehaviour : Start function called once?
下坡鬼畜
鬼畜产生的原因在于:移动角色时没有考虑地面坡度,还是水平移动,导致在斜面上的运动实际上是 “水平移动→坠落→水平移动”,这就产生鬼畜效果。
解决办法也简单,用射线检测检查斜面坡度,移动时基于斜面法线即可。(原先默认是vector3.up
)
注意旋转还是基于vector3.up
,否则移动后就垂直于斜面上了。
-
获取斜面法线
public Vector3 GetSlopeNormal() { RaycastHit hit; if (Physics.Raycast(transform.position, Vector3.down,out hit,radius,layerMask)) { return hit.normal; } return Vector3.zero; }
-
角色移动
protected void DoMoveInPhysics() { if (_playerInput.moveInput != Vector2.zero) { Vector3 moveInput = new Vector3(_playerInput.moveInput.x, 0, _playerInput.moveInput.y); // slopeNormal用于计算地面坡度 var slopeNormal = _playerController.slopeNormal(); // 相对主摄的移动(注意最后需要投影到水平面,否则会有上下位移导致镜头波动) Vector3 _camMoveWithSlope = Vector3.ProjectOnPlane(_camTransform.TransformDirection(moveInput).normalized, slopeNormal != Vector3.zero ? slopeNormal : Vector3.up); Vector3 _camMoveWithoutSlope = Vector3.ProjectOnPlane(_camTransform.TransformDirection(moveInput).normalized,Vector3.up); // 转向 _playerRig.MoveRotation(Quaternion.RotateTowards(_playerTransform.rotation, Quaternion.LookRotation(_camMoveWithoutSlope), 30)); // 移动 _playerRig.MovePosition(_playerRig.position + _camMoveWithSlope * playerStateSo.runSpeed * Time.fixedDeltaTime); } }
- 角色控制器斜坡与楼梯简易处理方法
- Player “bouncing” down slopes
SMB脚本中public变量无法运行时修改
解决办法:将参数用ScriptableObject
封装,运行时修改SO即可。
- 添加Player_State_SO 脚本,标明CreateAssetMenu,然后右键添加SO文件
[CreateAssetMenu(menuName = "Data/SO/Player_State_SO",fileName = "Player_State_SO")] public class Player_State_SO : ScriptableObject { public float jumpForce = 200f; public float runSpeed = 3f; }
- 状态SMB中引入上面的SO
public class Player_Base_SMB : StateMachineBehaviour { [Tooltip("在project中右键添加对应SO,并在状态机状态中添加SO,那样运行时就可在SO中调整参数")] public Player_State_SO playerStateSo; }
落地卡顿
经过按帧观察,中间黑字出现的几帧比较卡顿。
原本 fall clip是 12-18.5帧,land是19帧-45帧。
我思考可能是 clip裁截不够合理。
因为降落速度比较快,着陆前几帧不应该紧接fall,中间抽掉两帧,利用人眼视觉暂留,使整体动能衔接更顺畅。
解决办法: fall clip是 12-18帧,land是21帧-45帧。
但中间几帧能看出来落地时有轻微上移过程,原因待继续分析。
Idle→fall 切换时动画上移,视觉上造成卡顿
从中间两帧可以看到fall动画会比idle动画高,原因在于fall clip的帧是跳到最高点附近的动画,想要跳跃和Idle动画本身高度一致,就用到bake into pose了,generic如果不设置root 几点,没有这个选项,所以首先得给动画设置 “ 大根 ”。
但是generic动画如果未apply root motion,似乎root transform的设置没有用。
所以将所有动画改成humanoid,也方便未来动画复用。
将三个跳跃相关的动画 base upon 都设置成feet。
这样Idle→fall 切换就非常丝滑了,顺带之前落地卡顿问题也一并解决
跳跃到fall有突然前移现象
测试发现将jump→fall的过渡置0,就没有这中切换突进问题,过渡时长越大,突进越明显。
猜测问题出自过渡时两个两个状态都执行update导致两倍移动?
打印执行顺序,发现确实过渡时两个状态的OnStateUpdate
方法都会执行:
// 1. 开始跳跃,进入Jump状态,执行jump enter,然后执行jump update
进入JumpUp state,当前帧:815153
执行jumpUp SwitchState,当前帧:815165
执行jumpUp DoStateJob,当前帧:815165
...
执行jumpUp SwitchState,当前帧:815632
执行jumpUp DoStateJob,当前帧:815632
// 2. 跳跃→降落 过渡开始,进入fall状态,执行fall enter,然后执行fall update
进入fall State,当前帧:815632
执行jumpUp SwitchState,当前帧:815644
执行jumpUp DoStateJob,当前帧:815644
// 可以发现fall 的update方法也会执行
执行fall SwitchState,当前帧:815644
执行fall DoStateJob,当前帧:815644
...
执行jumpUp SwitchState,当前帧:815852
执行jumpUp DoStateJob,当前帧:815852
执行fall SwitchState,当前帧:815852
执行fall DoStateJob,当前帧:815852
// 3. 过渡结束,执行jump exit,跳跃状态退出
退出jumpUp state,当前帧:815865
// 4. 继续执行fall update
执行fall SwitchState,当前帧:815865
执行fall DoStateJob,当前帧:815865
...
执行fall SwitchState,当前帧:815888
执行fall DoStateJob,当前帧:815888
// 5. fall结束,执行fall exit
退出fall,当前帧:815901
当前fall状态持续时间:0.44
解决方法:
- 可以取消 JumpUp→Fall 过渡
- 或者给JumpUp执行update添加个过滤
protected override void DoStateJob(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { // 因为 jump 到 fall 过渡时会同时执行两个状态的update方法, //防止DoMoveInPhysics执行两次导致突然加速,进行特殊处理。或者取消jump to fall过渡,不过动画切换就显得生硬了些 if (animator.IsInTransition(layerIndex) && animator.GetNextAnimatorStateInfo(layerIndex).shortNameHash == PLAYER_STATE_FALL) { return; } DoMoveInPhysics(); }
当然两者结合也可以,最终跳跃降落的突进迁移问题就顺利解决了
但是还存在一个微位移问题。
单独跳跃切降落时依然发现角色动画有轻微位移。
原因不太清楚,我给JumpUp 和 Fall添加过渡(JumpUpState.length * 0.3)效果会好一点。
使用humanoid导致角色头发动画失效
查看generic 模型和动画,可以发现它的骨骼还是比较复杂的 ,一层套一层,头发是是head下一级
而humanoid动画的骨骼都是打平的,且只有标准的十几个骨骼节点
去avatar 上去看,就是其中圈出来的重要关节节点,像非标准的发型是不包含的,所以Unity chan切到hunmanoid头发动画就失效了。
那之前为什么要用humanoid模型和动画呢?回顾了下,是为了解决fall切land卡顿问题,因为fall动画存在一个偏移,而generic rig 在未apply root motion时,是无法调整root transition相关参数的。但是一旦用了humanoid rig,头发之类的动画又失效了。所以这里还要重新尝试用generic rig。
首先勾选 apply root motion
然后将跳跃3个动画所有root transform的bake into pose都勾选,这样就可以利用里面的offset参数调整fall与地面的偏移了(我这里调整为0.3,根据实际视觉效果来定)。
一定要注意JumpUp、Fall、Land 的Bake into pose全部置为true,不然可能会越跳越高(jump up root tranform position Y未勾选bake into pose就会这样)。
最终效果比humanoid rig的动画好。
先到这吧。