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

ops/2024/11/2 15:10:04/
  • 【Unity实战笔记】第二一 · 基于状态模式的角色控制——以UnityChan为例
  • 【Unity学习笔记】第十一 · 动画基础(Animation、状态机root motion、bake into pose、blendTree、大量案例)

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

SMBawake_4">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

SMBpublic_84">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 切换时动画上移,视觉上造成卡顿

在这里插入图片描述

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方法都会执行

// 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模型和动画,不动的是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的动画好。

在这里插入图片描述

先到这吧。


http://www.ppmy.cn/ops/130453.html

相关文章

硅谷甄选(三)登录注册

今天跑了步很舒服 一.登录模块 1.1登录路由静态组件 src\views\login\index.vue <template><div class"login_container"><el-row><el-col :span"12" :xs"0"></el-col><el-col :span"12" :xs&quo…

【学习】ZLMediaKit试用

服务端准备 下载ZLMediaKit压缩包&#xff0c;解压 /linux/Release路径下启用MediaServer ./MediaServer -d &/linux/Release路径下config.ini更改配置 也可以将进入web控制台 rtmp默认端口1935, rtsp默认端口554,http默认端口80, SSL默认端口443 进入web控制台 http…

js中什么是闭包,它和柯里化函数有什么关系

在JavaScript中&#xff0c;闭包是一个非常重要的概念&#xff0c;它指的是一个函数和它声明时所处的词法环境的组合。这意味着该函数可以访问并操作它被创建时作用域中的变量&#xff0c;即使它在那个作用域之外被调用。 闭包的定义和特点 访问外部变量&#xff1a;闭包允许…

tornado,flaskd这两个框架主要是干什么的

Tornado是一个Python的Web框架&#xff0c;主要用于构建高性能的异步Web应用程序。它基于非阻塞的网络I/O模型&#xff0c;可以处理大量并发连接&#xff0c;适用于需要处理实时性要求较高的应用场景&#xff0c;如实时聊天、实时数据推送等。 Flask是另一个Python的Web框架&a…

git入门教程12:git命令与技巧

一、Git高级命令 Git Rebase 功能&#xff1a;清理提交历史记录&#xff0c;使其更清晰和线性。在多人合作中&#xff0c;可以使用rebase合并功能分支的更改到主分支。交互式Rebase&#xff1a;使用git rebase -i HEAD~n&#xff08;n为你想重新排序、编辑或合并的提交数量&…

2024 Rust现代实用教程 流程控制与函数

文章目录 一、if流程控制与match模式匹配1.流程控制2. IF流程控制3.match 表达式 二、循环与break continue以及与迭代的区别1.Rust中的循环Loops2.break && continue3.迭代4.循环与迭代的不同 三、函数基础与Copy值参数传递1.函数的基础知识2.Copy by value 四、函数值…

mongodb指定引擎并设置内存使用大小

storage:dbPath: /www/server/mongodb/datadirectoryPerDB: trueengine: wiredTigerwiredTiger:engineConfig:cacheSizeGB: 4 WiredTiger 存储引擎&#xff1a;默认的存储引擎为 WiredTiger 存储引擎&#xff0c;非常适合大多数工作负载&#xff0c;建议用于新部署。WiredTige…

图解Redis 06 | Hash数据类型的原理及应用场景

介绍 Hash 类型特别适合存储对象&#xff0c;例如用户信息等。 String类型也可以用于存储用户信息&#xff0c;Hash与String存储用户信息的区别如下图所示&#xff1a; 内部实现 Hash 类型 的底层数据结构是通过压缩列表&#xff08;Ziplist&#xff09;或哈希表&#xff…