【Unity实战笔记】第二十二 · 基于SMB的角色控制中遇到的一些问题(斜坡移动鬼畜、落地卡顿、角色突进、头发动画失效等)

注: 本文紧接上一篇 Unity实战笔记 · 第二一,补录后续遇到的一些问题。

SMB无法使用awake,变量无法只进行一次初始化

解决办法:在OnStateEnter中添加入Initiate(animator)方法,Initiate中检测是否完成过初始化,有就直接跳过。

csharp 复制代码
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赋值一次变量,减少没必要的损耗。

下坡鬼畜


下坡鬼畜

鬼畜产生的原因在于:移动角色时没有考虑地面坡度,还是水平移动,导致在斜面上的运动实际上是 "水平移动→坠落→水平移动",这就产生鬼畜效果。

解决办法也简单,用射线检测检查斜面坡度,移动时基于斜面法线 即可。(原先默认是vector3.up

注意旋转还是基于vector3.up,否则移动后就垂直于斜面上了。

  • 获取斜面法线

    csharp 复制代码
    public Vector3 GetSlopeNormal()
    {
        RaycastHit hit;
        if (Physics.Raycast(transform.position, Vector3.down,out hit,radius,layerMask))
        {
            return hit.normal;
        }
        return Vector3.zero;
    }
  • 角色移动

    csharp 复制代码
    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);
        }
    }


下坡鬼畜修复

SMB脚本中public变量无法运行时修改

解决办法:将参数用ScriptableObject封装,运行时修改SO即可。

  • 添加Player_State_SO 脚本,标明CreateAssetMenu,然后右键添加SO文件

    csharp 复制代码
    [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

    csharp 复制代码
    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 切换时动画上移,视觉上造成卡顿


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有突然前移现象


跳跃到fall有突然前移现象


切到fall有突然前移现象(在scene窗口现象更明显)

测试发现将jump→fall的过渡置0,就没有这中切换突进问题,过渡时长越大,突进越明显。

猜测问题出自过渡时两个两个状态都执行update导致两倍移动?

打印执行顺序,发现确实过渡时两个状态的OnStateUpdate方法都会执行

csharp 复制代码
// 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添加个过滤

    csharp 复制代码
    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模型和动画,不动的是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的动画好。

先到这吧。

相关推荐
_oP_i17 分钟前
Unity 中使用 WebGL 构建并运行时使用的图片必须使用web服务器上的
前端·unity·webgl
司军礼4 小时前
Unity自动打包——Shell交互
unity·游戏引擎·交互
小汤猿人类6 小时前
苍穹外卖Bug集合
bug
无敌最俊朗@9 小时前
unity3d————球形插值知识点
开发语言·学习·unity·c#·游戏引擎
Thomas_YXQ1 天前
Unity3D中管理Shader效果详解
开发语言·游戏·unity·unity3d·游戏开发
且行且知1 天前
CubeIDE BUG-project‘hello‘has no explict encoding set hello
bug
一条破秋裤1 天前
针对解决前后端BUG的个人笔记
笔记·bug
马浩同学1 天前
【ESP32】ESP-IDF开发 | I2C从机接收i2c_slave_receive函数的BUG导致程序崩溃解决(idf-v5.3.1版本)
c语言·单片机·嵌入式硬件·mcu·bug
孤客网络科技工作室2 天前
Unity3D 开发教程:从入门到精通
unity
刘培玉--大王2 天前
Unity发布微信小程序-实战问题汇总
unity·微信小程序·游戏引擎