mastertravels77 发表于 2023-3-6 10:44

[ Unity ] Collide and Slide 算法 制作角色控制器

简介

最近在制作中发现 Unity 的两种角色控制方案都有其不足,在寻找结合两种控制方案的过程中对 Collide and Slide 算法进行了一定的学习。
游戏中角色控制器是游戏中非常重要的一部分,不同的游戏往往有着不同的需求,因此对于角色控制器有着多样的定制。但在物理交互上往往可以分为以下三类:
Dynamic通过力学间接控制,能与常规 Rigidbody 进行物理交互Kinematic Rigid-body通过输入位移矢量进行移动,具有速度的物理属性,能与常规 Rigidbody进行物理交互Kinematic通过输入位移矢量进行移动,无法与常规 Rigidbody 进行物理交互 Unity 内置的 PhysX物理引擎自带了 CharacterController(CC)组件,带有基本的 Collide and Slide 算法来保证角色在几何体表面的运动。但其实该组件相当Low-Level,没有集成移动平台的附着功能,需要与其他 Rigidbody 进行交互的话则需要进行手动的力的施加。同时该组件拥有一个可以说是致命的缺陷——胶囊碰撞体的竖直方向永远是世界坐标的y轴正方向!也就是说如果我们想要实现类似银河马里奥的星球行走,该组件就完全无法胜任。
Rigidbody在 Kinematic 状态下也可以制作 Kinematic Rigidbody类型的角色控制器,这时候就需要我们自己来写 Collide and Slide 算法来保证 CC 的正确位移(虽然也许对其他物体施加力反而是一种更加方便地操作),但当我们需要对某些 CC 内部已经获得数据进行使用或者对 CC 的判断行为模式需要进行特定修改时,使用 CC 组件则会显得有些力不从心。Unity近些年大力宣传的DOTS也由于核心ECS架构的存在需要对System部分进行与以往OOP不同的编写,也需要我们对于 CC 所使用的算法有所了解。因此选择手写 Collide and Slide 算法来制作 Kinematic Rigidbody CC。

核心思想

通过不断地迭代玩家位移与受到碰撞时调整移动方向最终使玩家达到指定位移。
在单次迭代中,检测角色控制器在当前方向上运动是否会受阻,检测到受阻时将角色移动到被阻挡的位置,并根据阻挡物调整玩家当前的运动方向供下一次迭代。
迭代的次数限制以及完成给定位移都可以作为终止迭代的条件。这里我选择使用原生的 Max Iteration Solver 作为最大迭代次数得限制。

流程

(参考 KCC 和UE4 SafeMoveUpdate 的检测流程总结)
-- Move With Moving Platform():

通过记录的相对位置信息得出当前帧 CC 的位置
-- Safe Move Update()

当剩余位移长度 大于 0 时 (设置最大迭代次数
---- Sweep Test():
通过 Physics.CapsuleCast 获取 CC 移动方向上的碰撞信息
---- Resolve Penetration():
通过 Physics.ComputePenetration 获取穿透信息并解决穿透,若未能解决穿透则再次进行 Overlap 获取碰撞信息,应当设置迭代次数的条件
---- Get Closet Hit:
通过 Overlap 与 SweepTest 获取的信选择与 Character 最近的 hit,若距离相同,则选择 Normal 与      Character 移动方向最相反的 hit(代表最恰当的前方障碍物)
---- Interact Rigidbody():
通过 OverlapCapsule 获取与角色重叠的 Dynamic Rigidbody,并通过计算碰撞后速度进行手动速度调整。Overlap 得到的 Collider 我们无法得知其实际的表面法线和碰撞点,这里我选择使用 ComputePenetration 得到的 ResolveDirection 作为接触面法线的替代。Collider.ClosetPoint 来获取近似的碰撞点。
为了防止可以互动的刚体被当成可以移动的地形,如果我们获取的 ClosetHit.Collider 被判断为刚体碰撞体的的话,我们便终结此次迭代。
---- Try Slope:
将位移方向投射到 Sweep Test 获得最合适的平面上


---- Try Step:
无法完成 Try Slope 时,尝试 Step 逻辑,先将角色上移再前进放下完成 Step 逻辑,期间调用 Sweep Test 逻辑进行检测。SweepTest 调用后依旧找到 ClosetHit, 如果判断这个台阶上有坡度,且坡度超过了我们设置的最大可爬坡度的阈值,我们也将其视为检测失败。


---- Try Slide:
无法完成 Try Step 时,将当前位移方向沿地面和墙面投射,方向通过叉乘计算。特别的,如果我们在迭代开始时记录一个 SlideMode (这里我们可以将其定义为一个 Enum), 我们可以实现一些特殊的效果:例如当沿着第一个墙滑动后遇到第二个墙,根据两堵墙的法线关系判断速度的改变。如果两堵墙之间的夹角较小,我们可以把它们转换成一个新的斜坡,让 CC 可以沿着夹缝移动。


-- Ground Probe:

探测 CC 底面的地面,并在 CC 速度小于临界值的时,将玩家吸附在地面上,防止玩家完成上坡位移过大导致离开地面。在每一个 FIxedUpdate 中只检测一次似乎不太精确,但如果我们每进行一次 SafeMoveUpdate 就调用一次不仅开销过大,还可能得出一些我们意料之外的结果。我的做法是在每一次 SafeMoveUpdate 中 Try Slope 和 Try Step 之后将_isGrounded 状态和缓存的地面信息进行更新。
-- Save Relative Position And Rotation:

保存当前 FixedUpdate 执行完毕后的相对位置和旋转
-- Apply State:

将解算得出的位置通过 MoveRotation 和 MovePosition 函数运用到动力学刚体上。这样的好处是我们不必再手动的处理平滑插值和碰撞穿透的问题。

细节处理


[*]为了更高的适用性,在判断时应该获取 Normal 与当前地面的相对夹角,并通过投射矫正 Normal 方向。
[*]使用 OverlapCapsule 的 NonAlloc版本可以避免内存分配和 GC 带来的额外开销,保证 CC 的空间占用相对稳定。同理也可以使用 CapsuleCastNonAlloc。根据 Unity 的文档我们发现,其实可通过获取的 RaycastHit 的信息来判断是否产生穿透重叠。
[*]Sphere/Capsule   射线检测获得的法线信息与 Raycast有所不同,返回的法线非平面法线而是面向球心的法线,需要额外的工作进行修。
[*]CapsuleCast 当碰撞到比自己更高的平面时,返回的 hit.point 处于 capsule 的中心位置。最初想直接通过碰撞点的高度判断应该抬升的高度可后来发现获取的 hit.point 似乎没有什么可用的规律。
[*]ContactOffset:当两个物体的距离小于两者 Contact Offset 之和的时候被视为碰撞,Overlap 和      ComputePenetration 都会将 Contact Offset 考虑在内,但是射线检测不会考虑 Contact Offset。有可能会出现 射线检测出现了重叠但是实际上物理系统不将其视作重叠的情况,可能会导致在整个迭代过程无法进行。
[*]Kinematic 刚体无法与静态的碰撞体触发碰撞并发送 OnCollision 消息,若要进行碰撞检测 我们需要手动进行额外处理。
参考


[*]^PhysX SDK Character Controllershttps://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/CharacterControllers.html
[*]^Unity DOTS Sample: Character 系统https://zhuanlan.zhihu.com/p/122454803
[*]^Kinematic Character Controllerhttps://assetstore.unity.com/packages/tools/physics/kinematic-character-controller-99131
[*]^UE4的移动碰撞https://zhuanlan.zhihu.com/p/33529865
[*]^Unity CapsuleCast 检测重叠https://docs.unity.cn/cn/2019.4/ScriptReference/Physics.CapsuleCastAll.html
[*]^碰撞检测汇总https://zhuanlan.zhihu.com/p/43703356

Arzie100 发表于 2023-3-6 10:46

最后那个第4点注意是啥意思?没太理解capsule cast这个问题在哪?

七彩极 发表于 2023-3-6 10:46

物理引擎允许两个物体之间有一定的重叠 射线检测的时候 不会有offset 会出现射线有重叠但是computepenetration和overlap都为false,具体的可以看看physx的contact offset和rest offset

maltadirk 发表于 2023-3-6 10:47

不好意思看错问题了。。最开始做台阶判断的时候想直接用碰撞点获取高度,但是后来法线这个碰撞点的返回位置其实实在胶囊体圆柱部分的母线上,并且在高于胶囊体中心是会保持在中心,但别的时候似乎返回的位置比较没有规律
页: [1]
查看完整版本: [ Unity ] Collide and Slide 算法 制作角色控制器