最近在制作中发现 Unity 的两种角色控制方案都有其不足,在寻找结合两种控制方案的过程中对 Collide and Slide 算法进行了一定的学习。
游戏中角色控制器是游戏中非常重要的一部分,不同的游戏往往有着不同的需求,因此对于角色控制器有着多样的定制。但在物理交互上往往可以分为以下三类:
Dynamic
通过力学间接控制,能与常规 Rigidbody 进行物理交互
Kinematic Rigid-body
通过输入位移矢量进行移动,具有速度的物理属性,能与常规 Rigidbody进行物理交互
Kinematic
通过输入位移矢量进行移动,无法与常规 Rigidbody 进行物理交互
Unity 内置的 PhysX物理引擎自带了 CharacterController(CC)组件[1],带有基本的 Collide and Slide 算法来保证角色在几何体表面的运动。但其实该组件相当Low-Level,没有集成移动平台的附着功能,需要与其他 Rigidbody 进行交互的话则需要进行手动的力的施加。同时该组件拥有一个可以说是致命的缺陷——胶囊碰撞体的竖直方向永远是世界坐标的y轴正方向!也就是说如果我们想要实现类似银河马里奥的星球行走,该组件就完全无法胜任。
Rigidbody在 Kinematic 状态下也可以制作 Kinematic Rigidbody类型的角色控制器,这时候就需要我们自己来写 Collide and Slide 算法来保证 CC 的正确位移(虽然也许对其他物体施加力反而是一种更加方便地操作),但当我们需要对某些 CC 内部已经获得数据进行使用或者对 CC 的判断行为模式需要进行特定修改时,使用 CC 组件则会显得有些力不从心。Unity近些年大力宣传的DOTS也由于核心ECS架构的存在需要对System部分进行与以往OOP不同的编写,也需要我们对于 CC 所使用的算法有所了解[2]。因此选择手写 Collide and Slide 算法来制作 Kinematic Rigidbody CC。
核心思想
通过不断地迭代玩家位移与受到碰撞时调整移动方向最终使玩家达到指定位移。
在单次迭代中,检测角色控制器在当前方向上运动是否会受阻,检测到受阻时将角色移动到被阻挡的位置,并根据阻挡物调整玩家当前的运动方向供下一次迭代。
迭代的次数限制以及完成给定位移都可以作为终止迭代的条件。这里我选择使用原生的 Max Iteration Solver 作为最大迭代次数得限制。
流程
(参考 KCC[3] 和UE4 SafeMoveUpdate[4] 的检测流程总结) -- Move With Moving Platform():
探测 CC 底面的地面,并在 CC 速度小于临界值的时,将玩家吸附在地面上,防止玩家完成上坡位移过大导致离开地面。在每一个 FIxedUpdate 中只检测一次似乎不太精确,但如果我们每进行一次 SafeMoveUpdate 就调用一次不仅开销过大,还可能得出一些我们意料之外的结果。我的做法是在每一次 SafeMoveUpdate 中 Try Slope 和 Try Step 之后将_isGrounded 状态和缓存的地面信息进行更新。 -- Save Relative Position And Rotation: