上古时代的游戏并不会使用例如PhysX等物理引擎,例如Quake和Doom,开发者们都会本身编写简单的碰撞检测模块来完成角色移动逻辑。虽然碰撞检测需要的物理算法很简单,但想让游戏操作起来更加顺畅,往往需要非常多的细节处理逻辑。这些特殊的移动处理逻辑叫做collide and slide算法,经过了10多年的积累沉淀,这套逻辑已经非常成熟,被应用到各种类型的游戏上。 好奇的同学可能会问,既然有了PhysX物理引擎,为什么不直接用它来完成角色移动呢? 原因有很多,这里列举几个比力典型的
以上这些情况如果使用物理引擎几乎是无法避免的,所以目前几乎所有的游戏都会自定义本身的移动模块,模块的复杂程度按照游戏的类型规模有着天壤之别,运动类游戏和第一人称射击游戏的移动模块往往是最复杂的。而第一人称射击类游戏的移动模块更具有通用性,经过多年发展,已经比力成熟,所以本文参考UE4中的代码,抽取其中核心逻辑,向大家介绍collide and slide算法。 在了解UE4的移动逻辑之前,我们先熟悉下碰撞的基础接口 UE4移动中碰撞检测主要使用PhysX的Geometry Queries(几何查询)功能
UE4把查询后返回的hit封装成了FHitResult FHitResult的结构如下 bBlockingHit是否发生碰撞 bStartPenetrating是否在检测开始就有渗透情况 Time碰撞后实际移动距离除以检测移动距离 Distance碰撞后实际移动距离 Location碰撞后最终位置 ImpactPoint碰撞接触点 Normal碰撞切面法向量 ImpactNormal碰撞切面法向量(非胶囊体和球体检测与Normal不同) TraceStart检测开始位置 TraceEnd检测结束位置 PenetrationDepth渗透深度 我们可以借助以下两种移动中常见的情况熟悉一下这些参数, 第一种是常见的胶囊体Sweep查询 查询开始结束别离是TraceStart和TraceEnd两个位置,如果碰到了障碍,bBlockingHit就是true,胶囊体最终会停在Location位置,它移动的距离是Distance,Time是一个0到1的值,暗示实际移动距离比查询距离。还有一些可能会用的参数,比如碰撞接触点ImpacePoint,碰撞切面法向量Normal和ImpactNormal 第二种常见的情况通常是InitialOverlaps,开始位置检测到了重叠 这时候bStartPenetrating是true,通过渗透深度计算可以获得PenetrationDepth,这个参数对于处理移动中穿透的情况非常重要 仔细观察的话可以发现上面胶囊体的Sweep就是一次简单的移动过程,UE4将这个过程进一步封装成了SafeMoveUpdatedComponent,它是UE4移动最关键的函数,几乎所有的移动都要靠它来完成。它的主要功能有以下几点
下面别离介绍一下这些功能,注意下面的符号▽△用于暗示函数的开始和结束 SafeMoveUpdatedComponent UPrimitiveComponent::MoveComponentImpl 调用SweepMulti获取合理的Hit 调用SweepMulti得到的所有Hit需要拉回微小的距离(缩小hit.time),避免因为浮点数精度的问题导致跟碰撞物重叠 如果检测到多个block hit,优先选择不是在初始位置就检测到block的hit,不然的话拔取跟运动标的目的最相反的hit 如上图是俯视图,圆形是胶囊体,方形是碰撞物,红色箭头是运动标的目的,胶囊体同时跟3个障碍物发生的碰撞,得到了3个hit,也就是图中的3个绿色剪头,按照筛选规则,拔取跟红色箭头标的目的最相反的,也就是中间的绿色箭头的hit。 SetPosition以及相关操作
调用UpdateOverlap更新重叠状态
Overlap Components列表
UPrimitiveComponent::MoveComponentImpl 如果调用MoveComponentImpl返回的hit结果bStartPenetrating是true,需要调用ResolvePenetration解决穿透的问题 ResolvePenetration 上图是俯视图,圆形代表胶囊体,方形是障碍物,胶囊体跟左边的障碍物穿透了,比力直观的解决方法是将它按照左边重叠的绿色箭头拉回,拉回的距离就是上面提到的PenetrationDepth变量,如果拉回过程中又跟右边的障碍物穿透了,这时候会得到右边的绿色箭头,摆布两边的箭头叠加,也就是向量相加,会得到中间向下的箭头,按着这个标的目的拉回,就会避免穿透问题。如果调整位置成功了,还需要再次尝试最开始的移动。 ResolvePenetration SafeMoveUpdatedComponent SafeMoveUpdateComponent可以看做是底层碰撞检测和上层移动逻辑的中间层,是基础的移动单元,接下来我们要介绍的移动逻辑,看似复杂,其实都是由这些移动单元构成的。整个移动逻辑的主函数是PerformMovement,我们还是按照函数的调用挨次梳理一遍它的主要逻辑。 PerformMovement 1.按照输入向量InputVector计算加速度向量Acceleration 2.随着被骑乘物MovementBase(比如电梯,载具)移动 3.将冲力Impulse和推力Force作用于速度Velocity,一般用于击退和径向运动 4.按照不同的运动状态运动
先看下MOVE_Walking PhysWalking 首先将速度和加速度的垂直标的目的分量设为0,标的目的始终保持在水平面上 CalcVelocity 1.计算速度,先设置为RequestedVelocity(寻路组件PathFollowingComponent按照路径不竭设置该速度) 2.加速度是0的时候,将受到减速度BrakingDeceleration和摩擦力的影响而减速 3.加速度不是0的时候,摩擦力将会影响速度标的目的改变快慢 4.计算速度向量Velocity+=Acceleration*DeltaTime 5.最后,如果支持RVOAvoidance,将会按照RVO重新计算速度,避免跟其他角色重叠在一起,效果就像被弹回来。 CalcVelocity MoveAlongFloor 计算移动向量Delta=Velocity*DeltaTime 按照地面坡度调整移动向量标的目的,如上图需要改为沿着面1坡度的标的目的,也就是红色箭头的标的目的,调用SafeMoveUpdatedComponent 如果返回Hit结果是block,如上图碰到了面2,通过返回的Hit的Normal参数检测到面2的斜面坡度较缓,这时可以将剩下的移动向量改为沿着面2移动,再次调用SafeMoveUpdatedComponent,如果返回的Hit结果还是block或者面2非常陡峭(如下图所示),可以开始尝试调用StepUp上楼的逻辑 StepUp 抱负情况下的上楼梯过程如图所示,它是由3次移动构成,首先向上移动MaxStepHeight高度,然后向前移动(向前移动过程中如果检测到block,需要调用SlideAlongSurface),最后向下移动,落到面2上面。当然,存在很多情况会导致StepUp失败,比如移动过程中检测到穿透Penetration,最终无法落到一个合理的落脚点(比如面2比力陡峭),都会导致调用StepUp失败,在这种情况下,我们需要调用SlideAlongSurface,贴着面走 StepUp 在调用SlideAlongSurface贴着面走之前,需要调用HandleImpact,处理碰撞发生后带来的副作用 HandleImpact 发送MoveBlockedBy事件,如果开启bEnablePhysicsInteraction,可以给与刚体一个反推力 HandleImpact SlideAlongSurface 二维的图示并不能很好暗示贴墙走的情况,我们看下上面这个截图,红色箭头暗示最开始移动标的目的,撞到面2后,我们调用StepUp失败,尝试SlideAlongSurface,于是移动标的目的变为贴着面2的黄色箭头,如果按照黄色箭头的移动过程中很不幸又碰到了一个面,我们需要调用TwoWallAdjust TwoWallAdjust 利用两个面法向量计算面2和面3的夹角,如果夹角大于90度,我们可以将移动标的目的变为沿着面3的绿色箭头 如果面2和面3的夹角小于90度,我们可以沿着面2和面3的夹缝(如下图的绿色向量)继续移动,这个夹缝向量可以通过面2和面3的法向量的叉乘结果计算出来,当然这个夹缝向量的倾斜角度不能过于陡峭,不然角色也是不能按照这个标的目的移动的。 TwoWallAdjust SlideAlongSurface MoveAlongFloor 到这里MoveAlongFloor就执行完了,然后还需要调用FindFloor,检测地面,调整纵坐标,保证角色始终贴着地表 FindFloor FindFloor返回的结果也是个比力重要的结构,我们看下它的参数 FFindFloorResult bBlockHit是否跟地面有碰撞 bWalkableFloor可以行走的地面 bLineTrace是否是通过line trace检测出来的结果 FloorDist Sweep查询到地面的距离 LineDist LineTrace查询到地面的距离 HitResult跟地面的FHitResult ComputeFloorDist 一般情况下,比如下图中的情况,我们只需要一次垂直向下Sweep检测就可以计算出FloorDist,注意检测的距离是之前StepUp向上检测的距离。这时候FloorDist等于返回Hit的Distance 如果返回的的Hit是bStartPenetration是true话则需要用一个缩小的胶囊体来重新向下Sweep,算出来的FloorDist减去缩水的高度就是原胶囊体跟地面的距离 如果用缩小胶囊体Sweep还是有穿透情况,这时候需要改用line trace,从胶囊体的中心向下trace胶囊体的半个身高,如果检测到了hit,则可以计算出陷入到地面以下的高度 注意无论是sweep还是line trace设置胶囊体向上抬的调整高度MaxPenetrationAdjust最大只能是胶囊体的半径,如果陷入地下的深度大于调整高度,一次调整是无法将胶囊体从地面抬出来的,往往需要多帧处理才可以。 ComputeFloorDist FindFloor 通过调用AdjustFloorHeight按照之前计算的FloorDist来调整角色的垂直坐标 如果FindFloorResult的bWalkableFloor是false,需要调用CheckFall,切换成MOVE_Falling状态 PhysWalking 其他几种运动状态这里不做具体说明,大体逻辑是基本相似的,区别在于计算速度和对返回Hit的特殊处理上。 Physx也提供了CharacterController移动库,有兴趣的可以参考下。 作者:李雪峰 专栏地址:https://zhuanlan.zhihu.com/p/33529865 |
小黑屋|在路上 ( 蜀ICP备15035742号-1 )
GMT+8, 2024-11-25 18:26
Copyright 2015-2024 djqfx