NiceTry12138 d875732660 feat: 添加移动组件的一些说明 未完成 6 ماه پیش
..
Image d875732660 feat: 添加移动组件的一些说明 未完成 6 ماه پیش
README.md d875732660 feat: 添加移动组件的一些说明 未完成 6 ماه پیش

README.md

移动组件

collide and slide 算法

https://zhuanlan.zhihu.com/p/685714685

https://news.16p.com/842025.html

https://www.cnblogs.com/walterwhiteJNU/p/15720810.html

https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/CharacterControllers.html#moving-a-character-controller

角色移动是一个复杂的计算过程,为了让游戏操作更加顺畅,往往需要非常多的细节处理,这些特殊的移动处理逻辑叫做 collide and slide

正如 Phys-x 物理引擎的 SDK 文档中所说,如果使用物理引擎来控制角色移动存在很多问题

  • 缺乏连续碰撞检测,经典物理引擎使用离散碰撞检测,因此会出现tunneling effect
    • tunneling effect 当角色移动过快的时候,可能会穿过墙壁,因此需要限制角色最大速度
    • 即使没有 tunneling,当角色在角落向前推时,会出现角色抖动的情况,因为物理引擎不断将其前后移动到略有不同的位置
  • 无法直接控制,刚体通常由脉冲控制,无法直接将角色移动到最终位置
  • 摩擦问题,当角色在斜坡上时,通常不应该滑动,因此会设置无限大的摩擦力,当这也导致角色无法向前移动
  • 不受控制的跳跃,当角色快速移动并于物体碰撞时,角色不应该被弹开,比如角色从高空落下时,落地双腿完全,并且不会弹跳。但是由于 restitution 的存在,会产生回弹效果

由于以上这些问题的存在,角色移动不能直接使用 物理引擎 来控制

碰撞查询

虚幻引擎将碰撞检测的信息封装成 FHitResult,虽然下面这些理论知识与虚幻引擎无关,为了方便参数解释,还是提前说明

FHitResult 的属性 解释
bBlockingHit 是否发生碰撞
Time 碰撞后实际移动距离除以检测移动距离
Distance 碰撞后实际移动距离
Location 碰撞后最终位置
ImpactPoint 碰撞接触点
Normal 碰撞切面法向量
ImpactNormal 碰撞切面法向量(非胶囊体和球体检测与Normal不同)
TraceStart 检测开始位置
TraceEnd 检测结束位置
bStartPenetrating 是否在检测开始就有渗透情况
PenetrationDepth 渗透深度

Sweep 检测

Sweep 扫过、掠过、大范围伸展

https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/3.3.4/Manual/GeometryQueries.html#sweeps

以当前坐标为起点,以当前 速度Delta 计算下一帧的理论坐标为终点,进行检测

如果中间检测到碰撞,则无法移动到终点坐标,从碰撞点计算角色下一帧真实坐标

  • TraceStart 表示 检测起点,值为当前坐标
  • TraceEnd 表示 检测终点,值为理论下一帧坐标
  • ImpactPoint 表示 碰撞接触点
  • Location 表示 下一帧的实际坐标
  • Distance 表示 下一帧的实际坐标与当前坐标之间的距离
  • Time 表示 $\frac{Distance}{TraceEnd-TraceStart}$,是一个 0~1 之间的值

InitialOverlaps 检测

也就是在开始位置就检测到了重叠

这个时候 bStartPenetrating 值为 true,表示检测到了 InitialOverlaps

Penetrating 表示重叠深度

Penetrating: 渗透、贯穿、穿过


UE 的移动组件

https://zhuanlan.zhihu.com/p/650314172

角色移动组件分层设计,各自负责不同的职责

组件 作用
UMovementComponent 基础移动
UNavMovementComponent 导航集成
UPawnMovementComponent 输入响应
UCharacterMovementComponent 角色物理
  • MovementComponent 为基类,提供基本的更新坐标逻辑。通常用于 可推动的物理道具
    • 设置需要更新的组件(UpdateComponent),通常是根组件,比如 ACharacter 的根胶囊体组件
    • 设置需要更新的组件(UpdatedPrimitive),用于物理交互,如果 UpdateComponent 能转换成 UPrimitiveComponent 会直接使用 UpdateComponent
    • 设置 MoveComponentFlags,用于控制更新行为的精细开关
    • Velocity 存储实时移动速度向量,是驱动组件运动的核心数据
    • PlaneConstraintNormal 定义移动约束平面的法线方向((0,1,1) 限制 Y 轴移动)
    • PlaneConstraintOrigin 定义移动约束平面的原点坐标,用于计算组件与平面的空间关系
UpdatedComponent = IsValid(NewUpdatedComponent) ? NewUpdatedComponent : NULL;
UpdatedPrimitive = Cast<UPrimitiveComponent>(UpdatedComponent);
  • NavMovementComponent 提供了AI寻路用的一些接口。通常用于 AI 控制的非人形物体

    • NavAgentProps 定义导航代理的物理特性和移动能力(如:半径、高度、最大速度、加速度等),用于路径计算和碰撞检测
    • FixedPathBrakingDistance 定义减速到停止的距离,由 bUseFixedBrakingDistanceForPaths 控制是否启用
    • PathFollowingComp 处理路径跟随逻辑,通常是 UPathFollowingComponent
  • PawnMovementComponent 定义了接受输入的接口。其Owner必须为APawn子类。通常用于自定义载具等

MoveUpdateComponent

UMovementComponent::MoveUpdateComponent 是真正执行物体移动逻辑的函数接口,并且该函数不是虚函数,子类无法重写

FORCEINLINE_DEBUGGABLE bool UMovementComponent::MoveUpdatedComponent(const FVector& Delta, const FRotator& NewRotation, bool bSweep, FHitResult* OutHit, ETeleportType Teleport)
{
	return MoveUpdatedComponentImpl(Delta, NewRotation.Quaternion(), bSweep, OutHit, Teleport);
}

bool UMovementComponent::MoveUpdatedComponentImpl( const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult* OutHit, ETeleportType Teleport)
{
	if (UpdatedComponent)
	{
		const FVector NewDelta = ConstrainDirectionToPlane(Delta);
		return UpdatedComponent->MoveComponent(NewDelta, NewRotation, bSweep, OutHit, MoveComponentFlags, Teleport);
	}

	return false;
}

对的,该函数直接调用 UpdatedComponentMoveComponent,让组件自己更新自己

不过 USceneComponent::MoveComponentImpl 是虚函数,可以被子类重写,所以说根据设置的 UpdatedComponent 的不同,最后执行的移动逻辑也不相同

USceneComponent::MoveComponentImpl

对于 USceneComponent::MoveComponentImpl 的实现逻辑是比较简单的

  1. 检查能否移动,组件的 Mobility 必须是 Movable
  2. 调用 ConditionalUpdateComponentToWorld 确保组件的 Transform 已经更新
  3. 检测是否是零位移 Delta.IsZero(),零位移表示不用移动,也就无需后续计算
  4. 更新坐标和旋转 InternalSetWorldLocationAndRotation
  5. 如果更新成功,则进行重叠检测 UpdateOverlaps,主要是递归更新 AttachedChild,并且更新自己的 PhysicsVolume 信息

InternalSetWorldLocationAndRotation 逻辑相对简单

  1. 传入新的坐标、旋转参数
  2. 如果存在父节点,基于父节点的坐标信息,更新传入的坐标和旋转信息
  3. 判断是否存在坐标修改、旋转修改
  4. 如果存在 坐标 或者 旋转 修改,更新 RelativeLocationRelativeRotation 的值
  5. 根据 bCanEverAffectNavigation 更新导航网格的信息

UPrimitiveComponent::MoveComponentImpl

前面说过 MoveComponentImpl 是一个虚函数

UMovementComponent 中的 UpdatedComponent 通常被设置为对象的根组件,而 ACharacter 的根组件是 UCapsuleComponent

UCapsuleComponent -> UShapeComponent -> UPrimitiveComponent

所以对于 ACharacter 来说,更新坐标执行的是 UPrimitiveComponent::MoveComponentImpl