虚幻引擎源码-剖析与改写Actor源码中的扫掠检测机制-避免物体移动穿墙

Actor自带的移动函数:
Add类:

AddActorWorldOffset(FVector DeltaLocation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport);
AddActorWorldRotation(FRotator DeltaRotation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
AddActorWorldRotation(const FQuat& DeltaRotation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
AddActorWorldTransform(const FTransform& DeltaTransform, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
AddActorWorldTransformKeepScale(const FTransform& DeltaTransform, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
AddActorLocalOffset(FVector DeltaLocation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
AddActorLocalRotation(FRotator DeltaRotation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
AddActorLocalRotation(const FQuat& DeltaRotation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
AddActorLocalTransform(const FTransform& NewTransform, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)

Set类:

SetActorLocation(const FVector& NewLocation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
SetActorRotation(FRotator NewRotation, ETeleportType Teleport)
SetActorRotation(const FQuat& NewRotation, ETeleportType Teleport)
SetActorLocationAndRotation(FVector NewLocation, FRotator NewRotation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
SetActorLocationAndRotation(FVector NewLocation, const FQuat& NewRotation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
SetActorTransform(const FTransform& NewTransform, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
SetActorRelativeLocation(FVector NewRelativeLocation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
SetActorRelativeRotation(FRotator NewRelativeRotation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
SetActorRelativeRotation(const FQuat& NewRelativeRotation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
SetActorRelativeTransform(const FTransform& NewRelativeTransform, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)

相关参数:
bool bSweep : true:检测 Actor 移动 / 旋转路径上的碰撞,碰撞则停止并填充OutSweepHitResult;
false:仅检测最终位置碰撞(或直接跳过),OutSweepHitResult为空;
FHitResult* OutSweepHitResult : 输出碰撞结果(如碰撞点、碰撞对象、法向量),bSweep=false时无有效数据。
ETeleportType Teleport : TeleportPhysics 瞬移模式 跳过扫掠 / 物理检测,直接瞬移到目标位置(忽略碰撞);
None(默认)执行正常物理 / 扫掠逻辑。

Add类型各函数的差别:
全局坐标系
AddActorWorldOffset 在世界坐标系下,给 Actor 叠加位置增量(DeltaLocation),仅修改位置。旋转、缩放完全保留当前值。最简单的世界位移,比如 “让 Actor 朝世界东移动 100 单位”。
AddActorWorldRotation(FRotator) 在世界坐标系下,给 Actor 叠加欧拉角旋转增量(DeltaRotation),仅修改旋转。位置、缩放完全保留当前值。 直观(Pitch/Yaw/Roll),但有万向锁风险(如 Pitch=90° 时 Yaw/Roll 混淆),适合简单旋转。
AddActorWorldRotation(FQuat) 逻辑同 FRotator 版,但旋转增量是四元数(FQuat) 位置、缩放保留当前值。无万向锁,精度高,适合物理模拟、相机旋转等高精度场景。
AddActorWorldTransform 在世界坐标系下,给 Actor 叠加全变换增量(DeltaTransform),同时修改「位置 + 旋转 + 缩放」 位置 / 旋转 / 缩放均按 Delta 叠加(如当前 Scale=2,DeltaScale=1.5 → 新 Scale=3) 一站式修改所有变换属性,需注意缩放会被修改
AddActorWorldTransformKeepScale 逻辑同AddActorWorldTransform,但强制保留当前缩放,忽略 DeltaTransform 中的 Scale 增量。仅修改位置 + 旋转,缩放不变。 想同时改位置 + 旋转,但不想动缩放时用(比如角色移动 + 转向,保留模型大小)。
本地坐标系(Local)
AddActorLocalOffset 在 Actor 本地坐标系下,叠加位置增量(DeltaLocation),仅修改位置。 旋转、缩放保留当前值。 实现 “Actor 朝自身前方移动”(比如角色前进 / 后退),最常用的本地位移。
AddActorLocalRotation(FRotator) 在 Actor 本地坐标系下,叠加欧拉角旋转增量(DeltaRotation),仅修改旋转。位置、缩放保留当前值。 实现 “Actor 相对自身右转 90°”,有万向锁风险。
AddActorLocalRotation(FQuat) 逻辑同 FRotator 版,旋转增量为四元数。位置、缩放保留当前值。 无万向锁,适合 Actor 自身高精度旋转(如飞机翻滚)。
AddActorLocalTransform 在 Actor 本地坐标系下,叠加全变换增量(DeltaTransform),同时修改「位置 + 旋转 + 缩放」。 位置 / 旋转 / 缩放均按 Delta 叠加。 本地坐标系的全变换修改,比如 “Actor 朝自身前方移动 + 自身旋转 + 放大模型”。

核心差异对比(关键维度)
坐标系(World vs Local) World:位移 / 旋转方向固定(如 World X = 东)
Local:位移 / 旋转方向随 Actor 朝向变(如 Local X=Actor 前方)。
旋转表示(FRotator vs FQuat) FRotator:直观,有万向锁;
FQuat:无万向锁,精度高,适合程序计算。
瞬移(跳过碰撞) 任意函数 + Teleport=ETeleportType::TeleportPhysics

以AddActorWorldOffset为例,剖析底层机制
层级关系:

最上层AddActorWorldOffset(FVector DeltaLocation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport);
第二层->RootComponent->AddWorldOffset(DeltaLocation, bSweep, OutSweepHitResult, Teleport);
  第三层->SetWorldLocation(NewWorldLocation, bSweep, OutSweepHitResult, Teleport);
    第四层->SetRelativeLocation(NewRelLocation, bSweep, OutSweepHitResult, Teleport);
       第五层->SetRelativeLocationAndRotation(NewLocation, RelativeRotationCache.RotatorToQuat(GetRelativeRotation()), bSweep, OutSweepHitResult, Teleport);
        第六层 ->SetRelativeLocation_Direct(NewLocation);
           SetRelativeRotation_Direct(RelativeRotationCache.QuatToRotator(NewRotation));
           MoveComponent(DesiredDelta, DesiredWorldTransform.GetRotation(), bSweep, OutSweepHitResult, MOVECOMP_NoFlags, Teleport);
           最底层->MoveComponentImpl(Delta, NewRotation, bSweep, Hit, MoveFlags, Teleport);

最上层:AActor::AddActorWorldOffset(Actor 层封装)

void AActor::AddActorWorldOffset(...) {
	if (RootComponent) {
		RootComponent->AddWorldOffset(...); // 核心:转发给根组件
	} else if (OutSweepHitResult) {
		*OutSweepHitResult = FHitResult(); // 无组件则清空碰撞结果
	}
}

核心职责:
Actor 本身无变换逻辑,仅作为 “入口封装”,将位移请求转发给 Actor 的RootComponent(根组件是 Actor 变换的载体);
边界处理:
若 Actor 无 RootComponent(罕见),则清空碰撞结果,避免空指针 / 无效数据;
设计意图:
Actor 是 “逻辑容器”,所有空间变换最终由 SceneComponent 实现,符合 UE “组件化” 设计。

第二层:USceneComponent::AddWorldOffset(组件层计算目标世界位置)

void USceneComponent::AddWorldOffset(...) {
	const FVector NewWorldLocation = DeltaLocation + GetComponentTransform().GetTranslation();
	SetWorldLocation(NewWorldLocation, ...); // 计算新位置,调用世界位置设置
}

核心职责:
将 “位移增量(DeltaLocation)” 转换为 “目标世界位置(NewWorldLocation)”,把 “增量操作” 转为 “绝对位置设置”;
关键细节:
GetComponentTransform().GetTranslation() 获取组件当前世界位置,叠加 Delta 后得到最终要移动到的世界坐标;
设计意图:
统一 “增量位移” 和 “绝对位置设置” 的底层逻辑,减少重复代码。

第三层:USceneComponent::SetWorldLocation(世界坐标→相对坐标转换)

void USceneComponent::SetWorldLocation(...) {
	FVector NewRelLocation = NewLocation;
	// 关键:若组件附着到父组件,将世界坐标转为父组件的相对坐标
	if (GetAttachParent() != nullptr && !IsUsingAbsoluteLocation()) {
		FTransform ParentToWorld = GetAttachParent()->GetSocketTransform(GetAttachSocketName());
		NewRelLocation = ParentToWorld.InverseTransformPosition(NewLocation);
	}
	SetRelativeLocation(NewRelLocation, ...); // 转发到相对位置设置
}

核心职责:UE 组件变换的核心 ——所有世界坐标最终都会转为 “相对父组件的坐标”(组件树设计);
关键分支:若组件无父组件 / 使用绝对位置:直接用世界坐标作为相对坐标;
若组件附着到父组件(如子 Mesh 挂在角色根组件):通过父组件的世界变换矩阵,将目标世界坐标逆变换为父组件的本地坐标;
设计意图:UE 组件树的核心规则是 “子组件相对父组件定位”,世界坐标仅为 “对外接口”,底层始终用相对坐标存储,保证组件树变换的一致性。

第四层:USceneComponent::SetRelativeLocation(复用旋转逻辑)

FORCEINLINE_DEBUGGABLE void USceneComponent::SetRelativeLocation(...) {
	SetRelativeLocationAndRotation(NewLocation, RelativeRotationCache.RotatorToQuat(GetRelativeRotation()), ...);
}

核心职责:将 “仅设置位置” 的请求转发给 “设置位置 + 旋转” 的接口,复用旋转相关逻辑;
关键细节:GetRelativeRotation() 获取组件当前相对旋转,转为四元数(避免欧拉角万向锁);
FORCEINLINE_DEBUGGABLE:Debug 模式下内联 + 调试信息,Release 模式仅内联,兼顾调试和性能;
设计意图:位置和旋转的设置逻辑高度耦合(比如移动时可能需要同步更新旋转缓存),合并为一个接口减少冗余。

第五层:USceneComponent::SetRelativeLocationAndRotation(核心中转 + 合法性校验)
这是整个调用链的核心中转层,承上启下,包含多个关键分支:

void USceneComponent::SetRelativeLocationAndRotation(...) {
	// 分支1:组件未初始化 → 直接设置变换(无移动/碰撞逻辑)
	if (UNLIKELY(NeedsInitialization() || OwnerNeedsInitialization())) {
		SetRelativeLocation_Direct(NewLocation);
		SetRelativeRotation_Direct(...);
		return;
	}

	ConditionalUpdateComponentToWorld(); // 确保当前变换矩阵是最新的

	// 分支2:NaN检测(防止非法旋转值导致崩溃)
#if ENABLE_NAN_DIAGNOSTIC
	const bool bNaN = NewRotation.ContainsNaN();
	if (bNaN) { /* 日志/报错 */ }
	if (GEnsureOnNANDiagnostic && !NewRotation.IsNormalized()) { /* 警告未归一化四元数 */ }
#endif

	// 核心:计算目标变换和位移增量
	const FTransform DesiredRelTransform(NewRotation, NewLocation);
	const FTransform DesiredWorldTransform = CalcNewComponentToWorld(DesiredRelTransform);
	const FVector DesiredDelta = FTransform::SubtractTranslations(DesiredWorldTransform, GetComponentTransform());

	MoveComponent(DesiredDelta, DesiredWorldTransform.GetRotation(), ...); // 调用移动组件逻辑
}

核心职责:
1.初始化分支:组件未初始化(如刚创建未注册)时,直接调用_Direct函数设置变换(跳过移动 / 碰撞 / 组件树更新,避免初始化阶段的逻辑冲突);
2.合法性校验:检测旋转值是否为 NaN、是否归一化(四元数必须归一化,否则旋转会异常);
3.变换计算:将目标相对变换(位置 + 旋转)转为世界变换,计算 “需要移动的增量(DesiredDelta)”;
4.转发移动:调用MoveComponent执行最终的移动逻辑;
关键函数:
CalcNewComponentToWorld:根据相对变换计算组件新的世界变换矩阵;
FTransform::SubtractTranslations:计算新旧世界位置的差值(即需要移动的 Delta);
设计意图:集中处理变换前的所有校验和计算,保证底层移动逻辑的输入是 “合法且最新的”。

第六层:USceneComponent::MoveComponent(纯转发)

FORCEINLINE_DEBUGGABLE bool USceneComponent::MoveComponent(...) {
	return MoveComponentImpl(...);
}

核心职责:仅做内联转发,无业务逻辑;
设计意图:分离 “接口(MoveComponent)” 和 “实现(MoveComponentImpl)”,便于子类(如 PrimitiveComponent)重写实现,符合 C++“接口与实现分离” 的设计原则。

第七层:USceneComponent::MoveComponentImpl(底层最终实现)

bool USceneComponent::MoveComponentImpl(...) {
	// 分支1:静态组件/无效组件 → 返回false,禁止移动
	if (!IsValid(this) || CheckStaticMobilityAndWarn(...)) { /* 清空Hit,返回false */ }

	// 分支2:SceneComponent无碰撞 → 清空Hit结果(bSweep无效)
	if (OutHit) { *OutHit = FHitResult(1.f); }

	ConditionalUpdateComponentToWorld();

	// 分支3:无位移增量且旋转无变化 → Early Out,直接返回true(性能优化)
	if( Delta.IsZero() && NewRotation.Equals(...) ) { return true; }

	// 核心:更新位置+旋转(强制禁用bSweep)
	const bool bMoved = InternalSetWorldLocationAndRotation(GetComponentLocation() + Delta, NewRotation, false, Teleport);

	// 分支4:移动成功 → 更新重叠检测(针对附着的PrimitiveComponent)
	if (bMoved && !IsDeferringMovementUpdates()) { UpdateOverlaps(); }

	return true;
}

核心职责:执行最终的变换更新,包含 4 个关键保护 / 优化逻辑;
关键设计(和调用链强相关):
1.静态组件保护:注册后的静态组件(Mobility=Static)禁止移动,触发警告并返回 false;
2.bSweep 无效化:SceneComponent 无碰撞体,因此无论上层传bSweep=true/false,都强制禁用扫掠(InternalSetWorldLocationAndRotation第三个参数传 false);
3.Early Out:无位移且旋转无变化时直接返回,避免无效计算(高频调用场景的性能优化);
4.重叠更新:若移动成功,更新附着的 PrimitiveComponent(有碰撞)的重叠检测,保证碰撞逻辑的一致性;
设计意图:SceneComponent 仅处理 “纯变换更新”,碰撞 / 扫掠逻辑完全下放给子类(PrimitiveComponent),符合 “基类做通用逻辑,子类做特化逻辑” 的继承设计。

Teleport 是有效参数(会影响 InternalSetWorldLocationAndRotation 的行为),但这段代码中无 “正常物理 / 扫掠逻辑” 可执行—— 因为 USceneComponent 本身不关联物理引擎、无碰撞体,“正常物理 / 扫掠” 仅在子类 UPrimitiveComponent 中存在。
为什么无 “正常物理 / 扫掠逻辑”?
物理 / 扫掠逻辑的前提是:组件有碰撞体、关联 PhysX 物理引擎、调用物理检测接口(如 SweepSingle/OverlapTest)。但这段代码中:
无任何物理引擎接口调用;
无碰撞体相关的参数 / 逻辑;
仅做 “纯数学变换更新”,因此 “正常物理 / 扫掠” 无从谈起
bSweep 参数:完全无效,无任何扫掠检测机制
bSweep 是 “是否执行扫掠检测(路径碰撞)” 的开关,但在这段代码中被彻底无视,无任何扫掠检测逻辑:
底层调用强制禁用扫掠
InternalSetWorldLocationAndRotation(..., false, Teleport);
InternalSetWorldLocationAndRotation 的第三个参数是 “是否执行扫掠检测”,这里强制传递 false—— 哪怕外部调用时传入 bSweep=true,底层也会直接禁用扫掠,这是 bSweep 无效的核心依据。
无扫掠检测的核心特征
扫掠检测的核心是 “检测组件从起点到终点的移动路径上的碰撞”,需要满足:
调用 SweepSingle/SweepMulti(组件级扫掠)或 PhysX::PxSweep(物理引擎级扫掠);
填充有效的 FHitResult(碰撞点、碰撞对象、法向量等);
有 “路径采样”“碰撞体相交检测” 等逻辑。
但这段代码中:

if (OutHit)
{
    *OutHit = FHitResult(1.f);
}

OutHit 仅填充一个无意义的默认值(FHitResult(1.f) 中 1.f 是时间戳占位符,碰撞对象、碰撞点等核心字段均为空);
终位置碰撞机制:自身不存在,仅为子类兼容

碰撞扫掠检测机制
UPrimitiveComponent(USceneComponent 的子类)重写了MoveComponentImpl函数实现了扫掠检测(路径碰撞)。
扫掠检测机制(路径碰撞):逐行解析伪代码

bool MoveComponentImpl(..., bool bSweep, FHitResult* OutHit, ...) {
    // 1. 扫掠检测的触发条件(双条件必须同时满足)
    if (bSweep && Teleport != ETeleportType::TeleportPhysics) {
        // 2. 获取扫掠检测的“形状标的”——组件自身的碰撞体
        FCollisionShape SweepShape = GetCollisionShape();
        /*
         * GetCollisionShape() 逻辑:
         * - 若组件是BoxComponent:返回盒子碰撞体(SetBoxExtent设置的尺寸);
         * - 若组件是SphereComponent:返回球形碰撞体(SetSphereRadius设置的半径);
         * - 若组件是StaticMeshComponent:返回简化碰撞体(如凸包),而非精细三角面(除非开启bUseComplexCollisionForSweep);
         * 核心:扫掠检测的形状和组件的碰撞体完全一致,保证检测贴合组件实际尺寸。
         */
        
        // 3. 执行底层扫掠检测(UE封装的PhysX引擎接口)
        bool bHit = GetWorld()->SweepSingleByShape(
            *OutHit,                  // 输出:碰撞结果(碰撞点/对象/法向量等)
            GetComponentLocation(),   // 起点:组件当前世界位置
            GetComponentLocation() + Delta, // 终点:组件要移动到的目标位置
            NewRotation,              // 扫掠过程中组件的旋转(保证检测形状随组件旋转)
            GetCollisionObjectType(), // 碰撞通道:组件自身的碰撞类型(如ECC_Pawn/ECC_WorldStatic)
            SweepShape                // 检测形状:步骤2获取的组件碰撞体
        );
        /*
         * SweepSingleByShape 底层逻辑(PhysX::PxSweep):
         * - 以SweepShape为检测体,从起点到终点生成“连续移动路径”;
         * - 逐段采样路径,检测是否与场景中“碰撞响应为Block/Overlap”的碰撞体相交;
         * - 若检测到碰撞:立即停止采样,返回第一个碰撞点(最接近起点的碰撞),并填充OutHit;
         * - 若未检测到碰撞:返回false,OutHit为空;
         * 性能优化:默认用“离散扫掠”(路径采样N个点),开启bTraceComplex后用“连续扫掠”(精准但性能高)。
         */

        // 4. 扫掠检测到碰撞:响应逻辑(停止穿墙)
        if (bHit) {
            // 4.1 移动到“碰撞点”(而非目标位置),避免穿墙
            InternalSetWorldLocationAndRotation(OutHit->Location, NewRotation, false, Teleport);
            // 4.2 返回false:标识“移动因碰撞未完成”
            return false;
        }
    }
    // ... 后续逻辑
}

扫掠检测的关键特征:
触发可控:bSweep=false 或 Teleport=TeleportPhysics(瞬移)时,直接跳过扫掠检测;
精准性:检测形状与组件碰撞体一致,旋转同步组件旋转,保证检测贴合实际移动状态;
终止性:检测到碰撞后,组件仅移动到 “碰撞点”(而非目标位置),且返回 false 标识移动未完成;
结果输出:OutHit 会填充完整的碰撞信息(如碰撞的 Actor/Component、碰撞点坐标、碰撞面法向量),便于上层逻辑处理(如反弹、伤害判定)。

修改源代码实现可选的扫掠检测(路径碰撞) 和最终位置碰撞(重叠 / 阻挡) 机制
实现该方式的核心前提是:
放弃无碰撞的 USceneComponent,基于其子类 UPrimitiveComponent(有碰撞体的组件)实现(如 BoxComponent、SphereComponent、StaticMeshComponent)—— 只有持有碰撞体的组件才能关联物理引擎、执行碰撞检测。
选择带碰撞体的组件
| UBoxComponent 角色、道具、简单几何体 | 轴对齐盒子,性能最优
| USphereComponent | 球形角色、子弹、触发区域 | 球形碰撞,适合旋转体
| UStaticMeshComponent | 复杂模型(如场景、角色) | 支持简化碰撞体 / 精细三角面

配置碰撞规则
碰撞体形状 / 尺寸:比如 BoxComponent 设置 SetBoxExtent(FVector(50,50,50)),匹配组件实际尺寸;
碰撞通道(Collision Channel):指定检测的目标类型(如 ECC_Pawn 检测角色、ECC_WorldStatic 检测静态场景);
碰撞响应(Collision Response):设置对不同通道的响应规则(如 “阻挡 Pawn、重叠 Trigger、忽略自身”);
启用碰撞:确保组件开启 SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics)(同时支持查询 + 物理碰撞)。

分机制实现(支持 “可选开启”)
扫掠检测(路径碰撞):检测移动路径上的碰撞
扫掠检测的核心是「检测组件从起点到终点的移动路径上是否有碰撞体阻挡」,返回第一个碰撞点并停止移动(可选跳过)。
核心函数(UE 原生接口)
UPrimitiveComponent::SweepSingle:检测路径上的第一个碰撞(最常用);
UPrimitiveComponent::SweepMulti:检测路径上的所有碰撞(适合需要多碰撞结果的场景);
底层依赖 PhysX 引擎的 PxSweep,保证精度和性能。

// 核心:执行扫掠检测,返回是否碰撞,若碰撞则输出碰撞点
bool SweepCheck(const FVector& StartPos, const FVector& EndPos, const FQuat& Rotation, FHitResult& OutHit, bool bEnableSweep)
{
    if (!bEnableSweep) return false; // 可选关闭扫掠,直接返回无碰撞

    UPrimitiveComponent* RootComp = Cast<UPrimitiveComponent>(GetRootComponent());
    if (!RootComp) return false;

    // 1. 配置扫掠参数
    FCollisionQueryParams QueryParams;
    QueryParams.AddIgnoredActor(this); // 忽略自身碰撞
    QueryParams.bReturnPhysicalMaterial = false; // 无需物理材质,提升性能

    // 2. 获取组件碰撞体(扫掠的检测形状)
    FCollisionShape SweepShape;
    SweepShape.SetBox(RootComp->GetScaledBoxExtent()); // 适配BoxComponent,其他组件需对应修改(如SetSphere)

    // 3. 执行扫掠检测(路径碰撞)
    bool bHit = GetWorld()->SweepSingleByShape(
        OutHit,
        StartPos, EndPos, // 起点、终点
        Rotation,         // 扫掠过程中的旋转
        ECC_Pawn,         // 碰撞通道(按需修改)
        SweepShape,       // 检测形状(组件碰撞体)
        QueryParams       // 查询参数
    );

    return bHit;
}

最终位置碰撞(重叠 / 阻挡):检测最终位置的碰撞
最终位置碰撞分两种场景(可选实现):
阻挡检测:最终位置是否被碰撞体阻挡(无法移动到该位置);
重叠检测:最终位置是否与其他碰撞体重叠(如触发机关)。
核心函数(UE 原生接口)
UPrimitiveComponent::OverlapTest:检测最终位置是否与指定通道的碰撞体重叠;
UPrimitiveComponent::CollisionTest:检测最终位置是否被阻挡;
UGameplayStatics::OverlapMultiByChannel:检测最终位置的所有重叠对象。

实现逻辑(可选开关)

// 核心:检测最终位置的碰撞(重叠/阻挡),返回是否碰撞
bool FinalPosCollisionCheck(const FVector& FinalPos, const FQuat& Rotation, bool bEnableFinalCollision, bool bCheckOverlap = true)
{
    if (!bEnableFinalCollision) return false; // 可选关闭最终位置碰撞

    UPrimitiveComponent* RootComp = Cast<UPrimitiveComponent>(GetRootComponent());
    if (!RootComp) return false;

    FCollisionQueryParams QueryParams;
    QueryParams.AddIgnoredActor(this);

    // 方式1:重叠检测(检测是否与其他碰撞体重叠)
    if (bCheckOverlap)
    {
        TArray<FOverlapResult> OverlapResults;
        FCollisionShape CollisionShape;
        CollisionShape.SetBox(RootComp->GetScaledBoxExtent());

        bool bOverlap = GetWorld()->OverlapMultiByShape(
            OverlapResults,
            FinalPos, Rotation,
            ECC_Pawn,
            CollisionShape,
            QueryParams
        );
        return bOverlap;
    }

    // 方式2:阻挡检测(检测是否被碰撞体阻挡)
    FHitResult HitResult;
    bool bBlocked = GetWorld()->CollisionTest(
        FinalPos, Rotation,
        ECC_Pawn,
        CollisionShape,
        QueryParams,
        HitResult
    );
    return bBlocked;
}

整合到 Actor 移动逻辑(完整示例)
将扫掠检测、最终位置碰撞整合到 Actor 的移动函数中,通过参数控制是否开启两种机制:

// Actor头文件声明
UCLASS()
class YOUR_PROJECT_API AMyActor : public AActor
{
    GENERATED_BODY()

public:
    // 核心移动函数:支持可选开启扫掠/最终位置碰撞
    UFUNCTION(BlueprintCallable, Category = "Movement")
    bool MoveActorWithCollision(
        const FVector& DeltaLocation, // 位移增量
        bool bEnableSweep = true,     // 可选:是否开启扫掠检测
        bool bEnableFinalCollision = true, // 可选:是否开启最终位置碰撞
        ETeleportType Teleport = ETeleportType::None // 瞬移开关
    );

private:
    UPROPERTY(EditAnywhere, Category = "Collision")
    UBoxComponent* RootBoxComp; // 根组件(Box碰撞体)
};

// Actor源文件实现
AMyActor::AMyActor()
{
    // 初始化根组件(Box碰撞体)
    RootBoxComp = CreateDefaultSubobject<UBoxComponent>(TEXT("RootBoxComp"));
    RootComponent = RootBoxComp;
    RootBoxComp->SetBoxExtent(FVector(50,50,50)); // 设置碰撞体尺寸
    RootBoxComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); // 启用碰撞查询+物理
    RootBoxComp->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block); // 阻挡Pawn
    RootBoxComp->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block); // 阻挡静态场景
}

bool AMyActor::MoveActorWithCollision(const FVector& DeltaLocation, bool bEnableSweep, bool bEnableFinalCollision, ETeleportType Teleport)
{
    // 瞬移模式:跳过所有碰撞检测,直接移动
    if (Teleport == ETeleportType::TeleportPhysics)
    {
        AddActorWorldOffset(DeltaLocation, false, nullptr, Teleport);
        return true;
    }

    UPrimitiveComponent* RootComp = Cast<UPrimitiveComponent>(GetRootComponent());
    if (!RootComp) return false;

    // 1. 计算起点和目标终点
    FVector StartPos = RootComp->GetComponentLocation();
    FVector TargetPos = StartPos + DeltaLocation;
    FQuat TargetRot = RootComp->GetComponentQuat();
    FHitResult SweepHitResult;

    // 2. 可选:执行扫掠检测(路径碰撞)
    bool bSweepHit = SweepCheck(StartPos, TargetPos, TargetRot, SweepHitResult, bEnableSweep);
    if (bSweepHit)
    {
        // 扫掠检测到碰撞:移动到碰撞点(停止穿墙)
        AddActorWorldOffset(SweepHitResult.Location - StartPos, false, nullptr, Teleport);
        UE_LOG(LogTemp, Warning, TEXT("扫掠检测到碰撞,停止在碰撞点:%s"), *SweepHitResult.Location.ToString());
        return false;
    }

    // 3. 可选:执行最终位置碰撞检测
    bool bFinalHit = FinalPosCollisionCheck(TargetPos, TargetRot, bEnableFinalCollision);
    if (bFinalHit)
    {
        // 最终位置有碰撞:放弃移动
        UE_LOG(LogTemp, Warning, TEXT("最终位置有碰撞,放弃移动"));
        return false;
    }

    // 4. 无碰撞:执行移动
    AddActorWorldOffset(DeltaLocation, bEnableSweep, &SweepHitResult, Teleport);
    return true;

关键注意事项(避坑 + 优化)
性能优化
扫掠检测:优先用 SweepSingle(只检测第一个碰撞)而非 SweepMulti(检测所有),减少计算量;
碰撞体:优先用简化碰撞体(Box/Sphere)而非精细三角面(bUseComplexCollisionForSweep=false);
忽略列表:通过 QueryParams.AddIgnoredActor/AddIgnoredComponent 忽略无需检测的对象(如自身、友方单位)。
2. 精度控制
扫掠容差:可通过 FCollisionQueryParams::SetTraceComplex(true) 开启精细检测(代价是性能);
最终位置容差:添加微小偏移(如 TargetPos + FVector(0,0,1)),避免因浮点误差误判碰撞。
3. Teleport 参数的影响
若 Teleport=TeleportPhysics:必须跳过所有碰撞检测(扫掠 + 最终位置),直接强制移动;
若 Teleport=None:按配置执行碰撞检测。
4. 与 UE 原生移动接口的结合
也可直接复用 UE 原生接口(无需手动实现扫掠):

// 直接调用AddActorWorldOffset,通过bSweep控制是否开启扫掠(底层已实现路径检测)
AddActorWorldOffset(DeltaLocation, bEnableSweep, &HitResult, Teleport);
// 最终位置重叠检测需额外调用OverlapTest

总结
实现可选的扫掠 + 最终位置碰撞的核心步骤:
换组件:用 UPrimitiveComponent 子类(有碰撞体)替代 USceneComponent;
配碰撞:设置碰撞体、通道、响应规则;
加开关:通过 bool 参数控制是否执行扫掠 / 最终位置检测;
调接口:复用 UE 原生扫掠 / 重叠接口,或手动实现定制化检测。
这种方案既兼容 UE 的原生物理逻辑,又能灵活控制碰撞机制的开启 / 关闭,适配不同场景(如 “正常移动检测碰撞、瞬移跳过碰撞”)

posted @ 2025-12-13 22:41  D大人  阅读(8)  评论(0)    收藏  举报