news 2026/9/29 17:13:21

UE5 C++射线检测:ECC_Visibility与碰撞对象查询参数详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
UE5 C++射线检测:ECC_Visibility与碰撞对象查询参数详解

UE5 C++(44-3):把射线检测的枚举整明白,ECC_Visibility 和 FCollisionObjectQueryParams 别再混用

做 UE5 的 C++ 开发,只要你碰过追踪类射击、AI 视线判定、物理探测,就一定绕不开射线检测。而射线检测里有两个东西是新手最容易卡住的地方:一个是ECC_Visibility这种碰撞通道,另一个是FCollisionObjectQueryParams::InitType这个枚举。很多人写LineTraceSingleByObjectType或者LineTraceSingleByChannel的时候,参数一头雾水,复制粘贴别人代码能跑,但一旦改需求就翻车。

这篇文章我基于项目UE5 C++(44-3):射线检测时需要的枚举类ECollisionChannel,ECC_Visibility。enum FCollisionObjectQueryParams::InitType完整梳理一遍交叉通道、物体类型查询参数背后的机制。不讲虚的,直接讲清楚他们分别是什么、怎么选、哪些坑我踩过,以及一份你能直接抄的完整 C++ 示例。

适合正在学 UE5 C++ 的初学者,也适合被LineTraceSingleByObjectType的FCollisionObjectQueryParams搞晕的老哥们。你要是愿意花十分钟把这篇文章看完,以后遇到射线检测的参数配置,基本不会再犯错。

1. 碰撞通道的本质:先理解 UE5 碰撞系统怎么分层

1.1 为什么 UE5 要搞出 ECollisionChannel 这一层枚举

很多人第一次看到ECollisionChannel会被一大堆ECC_WorldStatic、ECC_WorldDynamic、ECC_Pawn、ECC_Visibility、ECC_Camera吓到,觉得没必要搞这么复杂。等我解释完,你就明白了。

UE5 的碰撞系统本质是一张矩阵。横轴和纵轴都是碰撞通道,矩阵里填的是ECollisionResponse(三个值:Ignore、Overlap、Block)。任何一个 Actor 要参与碰撞检测,至少要指定两个信息:它自己属于哪个通道(Object Type),以及它和其他通道之间怎么响应(Response)。这就是ICollisionDataProvider背后那套经典模型。

ECollisionChannel就是给这条矩阵的行和列起的名字。它是uint8枚举,引擎默认定义了二十多个通道,前几个是引擎固定通道,后面从ECC_GameTraceChannel1开始是留给游戏项目自定义的。你可以在 Project Settings 的 Collision 面板里改它们的显示名,但 C++ 里仍然通过枚举值来引用。

这里的关键理解是:通道不仅仅是一种标签,它决定了碰撞查询的性能路径和过滤方式。物理引擎做射线检测时,如果只用某种类型的通道查询,就能提前跳过大量无需检测的 Actor,这正是ECollisionChannel存在的意义。

1.2 常用内建通道的区分:Visibility、Camera、Pawn、WorldStatic

内建通道里最常用的是几个:

  • ECC_WorldStatic:静态几何体,大部分场景静态网格底座都在这个通道。
  • ECC_WorldDynamic:动态物体,比如可以被推动的箱子、移门。
  • ECC_Pawn:玩家角色和 AIController 控制的 Pawn 默认属于这个通道。
  • ECC_PhysicsBody:带物理模拟的物体。
  • ECC_Visibility:专门用来做可见性检测的追踪通道。
  • ECC_Camera:专门用来做相机防遮挡检测的追踪通道。

这里面特别容易困惑的是ECC_Visibility和ECC_Camera。它们不是实际的物体通道,而是“查询用的追踪通道”。什么意思呢?就是这条通道不是让你把一个 Actor 的 Object Type 设成 Visibility,而是让你在发起射线时把TraceChannel设成ECC_Visibility,然后检测这条射线碰到的物体。

引擎里默认的碰撞响应矩阵中,ECC_Visibility对ECC_WorldStatic是 Block,对ECC_Pawn是 Ignore。这个设计是故意的:Visibility 射线用来判断“我能不能看见这个位置”,而不希望被角色自己挡住。而ECC_Camera的默认矩阵则会把角色设成 Block,专门用于第三人称相机防止穿墙。

所以如果你希望射线能检测到角色,就不能默认用ECC_Visibility,得要么修改碰撞响应,要么使用FCollisionObjectQueryParams针对ECC_Pawn做对象查询。

1.3 Object Type 和 Trace Channel 的差别

碰撞通道里还有一对隐含概念:Object Type(对象类型)和 Trace Channel(追踪通道)。

Object Type 描述的是 Actor 本身的分类。每个可碰撞对象在创建时指定自己是哪个 Object Type,比如ECC_WorldDynamic。Trace Channel 描述的是查询方发起的追踪所使用的通道。一个射线检测函数会传入 Trace Channel,物理引擎拿这个通道去和场景里每个 Actor 的 Object Type 比对,再查询碰撞响应矩阵,得出这次检测是否命中。

换句话说,一个碰撞体有两个身份:它是什么(Object Type),以及别人发起的查询是否对它生效(由矩阵决定)。大多数新手犯的错误是把它们混为一谈,设置完一个 Actor 的 Object Channel 之后,却忘了检查查询时用的 Trace Channel 在矩阵里对这个 Object Channel 是不是 Block。

提示:修改 Project Settings 里的 Collision 面板可以自定义各通道之间的响应关系,但 C++ 查询里的通道枚举值不会变,修改的是矩阵行为,不是通道数量。

2. FCollisionObjectQueryParams 和它的 InitType 枚举

2.1 FCollisionObjectQueryParams 解决什么问题

刚才说的都是按 Trace Channel 查询。但实际开发里你常常会面临这样的需求:不想关心 Trace Channel 的矩阵配置,只想检测“射线碰到了哪些指定类型”。比如我想让射线只命中ECC_Pawn和ECC_WorldDynamic,不检测地面和静态网格;或者我又想检测静态,又不想检测 Pawn。

这时如果还通过 Trace Channel 和矩阵来做,你得在 Project Settings 里反复调矩阵,很容易影响到其他系统。UE5 提供了另一条路:Object Query。也就是直接指定需要检测的 Object Type 列表,物理引擎只对这些类型做检测。这个查询参数正是FCollisionObjectQueryParams。

这个结构体里保存了一个内部掩码,表示需要检测的 Object Type 集合。射线检测函数拿到它之后,会跳过不需要检测的类型,效率更高,也更灵活。

FCollisionObjectQueryParams的默认构造函数生成一个“空查询”,不会检测任何类型,因为内部的ObjectTypesToQuery值为0。所以如果你直接FCollisionObjectQueryParams QueryParams;然后传给LineTraceSingleByObjectType,大概率什么也检测不到。

2.2 enum InitType 的三种取值详解

这是FCollisionObjectQueryParams的核心构造逻辑。它有一个专门控制初始化方式的枚举:

enum InitType { AllObjects, AllStaticObjects, AllDynamicObjects };

这个枚举不是用来直接赋值的普通枚举,而是给构造函数的第二个参数。构造函数原型大致如下:

FCollisionObjectQueryParams(enum InitType InType, EObjectTypeQuery InObjectType = EObjectTypeQuery::ObjectTypeQuery_MAX);

也就是说你调用时是这么用的:

FCollisionObjectQueryParams(FClollisionObjectQueryParams::AllObjects); FCollisionObjectQueryParams(FClollisionObjectQueryParams::AllStaticObjects); FCollisionObjectQueryParams(FClollisionObjectQueryParams::AllDynamicObjects);
  • AllObjects:对引擎里所有 Object Type 都进行检测。
  • AllStaticObjects:只检测所有标记为静态的 Object Type,常见的是ECC_WorldStatic。
  • AllDynamicObjects:只检测所有动态类型,比如 Pawn、WorldDynamic、PhysicsBody。

这三个选项背后对应的是FCollisionObjectQueryParams内部ObjectTypesToQuery掩码的预设值。选AllObjects时掩码全部置 1,选AllStaticObjects时只把静态相关类型的位打开,选AllDynamicObjects时只打开动态相关。

使用的时候要注意,EObjectTypeQuery参数是配合InitType使用的。如果你用了AllObjects,后面跟的InObjectType通常会忽略,因为所有类型都已经纳入了。如果你用AllStaticObjects或者AllDynamicObjects再配一个具体类型,实际上也是用预设的模式。更常见的是,直接用另一个构造函数重载来指定单类型或多类型组合:

FCollisionObjectQueryParams(EObjectTypeQuery InObjectType); FCollisionObjectQueryParams(const TArray<EObjectTypeQuery>& ObjectTypes);

菜鸟常见误区是把InitType当成了独立枚举来用,比如写if (InitType == AllObjects),这是错的。它只是构造函数参数的一个标记,告诉结构体从哪种预设模式开始。

2.3 EObjectTypeQuery 和 ECollisionChannel 的关系

EObjectTypeQuery这是一个 UHT(Unreal Header Tool)支持的枚举,跟运行时引擎枚举ECollisionChannel是两个不同的东西。FCollisionObjectQueryParams的构造函数并不直接接收ECollisionChannel的通道值,而是接收EObjectTypeQuery。

你可能会觉得很绕,为什么不能直接传ECC_WorldDynamic?因为EObjectTypeQuery是蓝图可用的枚举,被标记了UENUM(BlueprintType),而ECollisionChannel是引擎底层广泛使用的枚举,两者数值定义不同。UE5 提供了一个转换函数:

UEngineTypes::ConvertToObjectType(ECollisionChannel Channel);

例如:

EObjectTypeQuery MyQueryType = UEngineTypes::ConvertToObjectType(ECC_Pawn);

拿到EObjectTypeQuery才能丢进FCollisionObjectQueryParams。同样也有反方向:

ECollisionChannel MyChannel = UEngineTypes::ConvertToCollisionChannel(MyQueryType);

所以你看,其实整个系统是两套表述方式:

  • 通道视角:ECollisionChannel+ 碰撞响应矩阵。
  • 对象查询视角:EObjectTypeQuery+FCollisionObjectQueryParams掩码。

这两套方式最终面向的是同一个物理场景数据,只是查询引擎的路径不一样。后面实践部分我会分别展示。

3. 实战配置:Raycast 函数选型与代码完整示例

3.1 三条常用射线检测函数怎么选

UE5 的UKismetSystemLibrary和UWorld都提供了一系列射线检测。C++ 里通常会直接调UWorld的方法,常见的有这几个:

bool LineTraceSingleByChannel(const FVector& Start, const FVector& End, ECollisionChannel TraceChannel, const FCollisionQueryParams& Params, FHitResult& OutHit); bool LineTraceSingleByObjectType(const FVector& Start, const FVector& End, const FCollisionObjectQueryParams& ObjectQueryParams, const FCollisionQueryParams& Params, FHitResult& OutHit); bool LineTraceSingleByProfile(const FVector& Start, const FVector& End, FName ProfileName, const FCollisionQueryParams& Params, FHitResult& OutHit);

它们的区别很好理解:

  • LineTraceSingleByChannel用 Trace Channel 发起查询,适合符合引擎默认碰撞设计、需要依赖配置矩阵的通用检测。比如你想要一次纯粹“符合项目碰撞规则”的检测,就用它。
  • LineTraceSingleByObjectType用 Object Query 发起查询,适合只关心某种类型的场景,例如只检测Pawn,不检测墙壁。
  • LineTraceSingleByProfile用碰撞配置文件(Collision Profile)发起查询,适合在 Project Settings 的 Profile 里定义好一批规则后按名字使用。

标题里提到的ECC_Visibility和FCollisionObjectQueryParams正好对应前两种。很多 AI 视觉、抓取判定、命中判定的逻辑,都需要这两条路协同。

3.2 完整示例:同时用 Visibility 通道和 Object Query 做双层检测

下面这段是我在项目里常用的一套逻辑:先做ECC_Visibility通道射线检测,排除遮挡物;然后用FCollisionObjectQueryParams只对ECC_Pawn做对象查询,判断目标是否是角色。

// RaycastUtils.h #pragma once #include "CoreMinimal.h" #include "Engine/World.h" #include "CollisionQueryParams.h" #include "Engine/EngineTypes.h" #include "GameFramework/Actor.h" #include "RaycastUtils.generated.h" UCLASS() class MYGAME_API URaycastUtils : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UFUNCTION(BlueprintCallable, Category = "Raycast|Utils") static bool VisibilityCheckAndFindPawn( UWorld* World, const FVector& Start, const FVector& End, AActor* IgnoreActor, FHitResult& OutHitResult, APawn*& OutPawn) { if (!World) { return false; } FCollisionQueryParams Params; Params.bTraceComplex = false; Params.AddIgnoredActor(IgnoreActor); // 第一次检测:ECC_Visibility FHitResult VisibilityHit; bool bVisibilityBlocked = World->LineTraceSingleByChannel( VisibilityHit, Start, End, ECC_Visibility, Params ); if (bVisibilityBlocked) { // 说明中间有东西挡住了 OutHitResult = VisibilityHit; return false; } // 第二次检测:ObjectType 只查 Pawn FCollisionObjectQueryParams PawnQuery; PawnQuery.AddObjectTypesToQuery(ECC_Pawn); // 等价写法: // PawnQuery.AddObjectTypesToQuery(UEngineTypes::ConvertToObjectType(ECC_Pawn)); // 或者 FCollisionObjectQueryParams PawnQuery(TArray<EObjectTypeQuery>{ EObjectTypeQuery::ObjectTypeQuery_Pawn }); FHitResult PawnHit; bool bHitPawn = World->LineTraceSingleByObjectType( PawnHit, Start, End, PawnQuery, Params ); if (bHitPawn) { OutHitResult = PawnHit; OutPawn = Cast<APawn>(PawnHit.GetActor()); return true; } return false; } };

这段逻辑非常典型:先用ECC_Visibility判断可见性,避免穿过墙壁直接命中墙壁后面的 Pawn;再用 Object Query 精确只命中角色。这在第三人称射击的准星检测、AI 索敌中都很实用。

注意FCollisionObjectQueryParams的AddObjectTypesToQuery方法,它接收ECollisionChannel参数,内部做转换。所以你也可以直接传ECC_Pawn,少写一次转换。

3.3 参数细节:bTraceComplex、IgnoreActor 和对象掩码的取舍

上面代码里Params.bTraceComplex = false是很多人忽略的细节。它表示射线检测只对简单碰撞体(Primitive Component 的简单碰撞形状)进行测试,而不是对每个三角形的复杂碰撞体做测试。绝大多数场景用 false 就够了,性能更好,而且结果是稳定的。

Params.AddIgnoredActor(IgnoreActor)用来忽略发起者自身或某个指定 Actor。在 FPS 里如果不忽略射手自己,射线很可能会命中自己的胶囊体,或者命中自己手上的武器。这里有一个容易遇到的问题:忽略 Actor 只会忽略这个 Actor 上所有 Primitive 组件,但如果你有多个组件被同一个物理查询命中,你需要FHitResult.Component来做进一步区分。

关于对象掩码,FCollisionObjectQueryParams支持一次性添加多个类型:

FCollisionObjectQueryParams QueryParams; QueryParams.AddObjectTypesToQuery(ECC_WorldDynamic); QueryParams.AddObjectTypesToQuery(ECC_Pawn); QueryParams.AddObjectTypesToQuery(ECC_PhysicsBody);

这种写法比构造TArray<EObjectTypeQuery>再传入更直观。

3.4 InitType 的实际使用场景演示

InitType虽然构造参数简单,但在某些场景下很省事。例如做破坏效果检测时,我们可能只想访问所有动态物体:

FCollisionObjectQueryParams DynamicQuery(FCollisionObjectQueryParams::AllDynamicObjects); FHitResult Hit; bool bHitDynamic = World->LineTraceSingleByObjectType( Hit, Start, End, DynamicQuery, Params );

或者做全场景扫描,在编辑器工具中统计所有可碰撞物体:

FCollisionObjectQueryParams AllQuery(FCollisionObjectQueryParams::AllObjects); TArray<FHitResult> Hits; World->LineTraceMultiByObjectType( Hits, Start, End, AllQuery, Params );

这里最需要注意的是LineTraceMultiByObjectType返回的所有命中数组是未排序的,如果你想按距离排序,可以用FHitResult.Time或者手动重排。

4. 碰撞矩阵配置:Project Settings 里的坑与建议

4.1 默认矩阵下 ECC_Visibility 对 Pawn 是 Ignore

前面说过,ECC_Visibility默认对ECC_Pawn是 Ignore。这意味着你用LineTraceSingleByChannel(..., ECC_Visibility, ...)检测:

  • 能命中地面、静态网格、部分动态障碍。
  • 打不到场景里的其他角色(除非角色被修改过碰撞响应)。

这会在联机对战或 AI 索敌时造成很妖的问题:射线明明对准了敌人,可就是没命中。这不是射线写错,而是碰撞通道矩阵默认行为。

解决方案有三种:

  1. 在 Project Settings 的Collision面板里,找到Visibility那一行,把对Pawn的响应从 Ignore 改为 Block。
  2. 用FCollisionObjectQueryParams绕开矩阵,直接检测ECC_Pawn。
  3. 给 Pawn 单独挂一个额外碰撞体,放在自定义通道里,并让 Visibility 对它 Block。

方案 2 是最干净的,不污染全局配置,推荐优先使用。方案 1 适合整个项目统一“能看见Pawn”的语义,但要小心可能影响所有 Visibility 检测。

我碰到过一个真实翻车案例:项目早期发现墙壁后面的敌人能被狙到,就是因为美术同学在设置碰撞时顺便把 Visibility 对 Pawn 改成了 Block,导致射线穿透了墙体。所以全局矩阵的修改影响面很大,改之前一定要列一份所有使用该通道的系统清单。

4.2 自定义通道怎么加、怎么用、怎么命名

如果你需要新的碰撞通道,在 Project Settings 里往下拉,能看到Game Trace Channel 1到Game Trace Channel 18。默认情况下这些通道没有名字,只在数值上占用ECC_GameTraceChannel1到ECC_GameTraceChannel18。你可以双击它们的显示名称,改成Interaction、Bullet、View等等。

改完名字后,C++ 里可以直接用枚举常量吗?不行。ECC_GameTraceChannel1这种枚举值不变,但你可以用:

ECollisionChannel MyChannel = ECollisionChannel::ECC_GameTraceChannel1;

虽然名字还是 Channel1,但你可以在注释里标明这代表Interaction通道。Blueprint 里使用时会显示你已经改好的名字,更方便。

如果要更语义化,项目里通常会封装一层全局函数:

static ECollisionChannel GetInteractionChannel() { return ECollisionChannel::ECC_GameTraceChannel1; }

这样做的好处是,万一编辑器里通道顺序调整(比如从 Channel1 变成 Channel2),只需要改这一处封装。

4.3 多通道组合查询的注意事项

当你用FCollisionObjectQueryParams添加多个类型时,物理引擎会做按位与过滤。它的内部掩码最高支持 64 个通道(取决于 64 位机器上的FCollisionQueryFlag),所以通常不用担心位数不够。

但是有一个性能隐患:添加的类型越多,物理引擎需要测试的碰撞体越多。尤其是在射击游戏的子弹检测里,如果一次LineTraceMulti同时检测 Pawn、WorldDynamic、PhysicsBody、Destructible,命中数量会显著增多。你需要在逻辑层及时过滤掉无关结果。

我还建议:对于高频检测(例如每秒 60 次的准星射线),尽量使用 Object Query 而不是 Channel 查询,因为 Object Query 在内部可以直接跳过不匹配类型的物理体,实际性能表现通常更好。

另一个注意点是FCollisionObjectQueryParams::AllObjects不代表“能穿透一切障碍”,它代表“对所有 Object Type 都进行检测”。射线仍然会被第一个 Block 的碰撞体挡住,因为这是碰撞响应矩阵决定的。要穿透多个物体,你需要用LineTraceMultiByObjectType,然后自己从结果数组里取你需要的那一个。

5. 常见报错和诡异现象的排查实录

5.1 一直检测不到任何对象

如果你用FCollisionObjectQueryParams但怎么都检测不到,八成是构造函数用错了:

FCollisionObjectQueryParams WrongParams; // 默认掩码是 0

这时候LineTraceSingleByObjectType大概率直接返回 false 或者命中无效结果。解决办法:初始化时传入AllObjects,或者调用AddObjectTypesToQuery。

还有一个很不起眼但经常翻车的点:你在调用LineTraceSingleByObjectType时把ECC_Visibility当成参数传给了FCollisionObjectQueryParams的构造函数。看一下构造函数签名,发现根本不匹配,编译器报错一长串。看仔细点,构造函数接收EObjectTypeQuery,不是ECollisionChannel。

5.2 初始化了 AllObjects 还是检测不到

有个人碰到过:FCollisionObjectQueryParams(AllObjects)配LineTraceSingleByObjectType依然什么都检测不到。最后发现是FCollisionQueryParams里设置了bTraceComplex为 true,且碰撞组件只开了复杂碰撞,而复杂碰撞体本身有问题导致实际几何体为空。

这个场景比较少见,但也提醒我们:当射线检测异常时,先逐步排查:

  1. 确认碰撞体存在且可见(可能被隐藏了但碰撞还在)。
  2. 确认CollisionEnabled是QueryOnly或QueryAndPhysics。
  3. 确认bGenerateOverlapEvents不直接影响射线,但检查bCallRigidBodySleepEvents这类选项没有副作用。
  4. 确认Visibility通道不是被整体关闭了。

在引擎里临时开一下Show Collision命令(PIE界面按0,或者控制台vis collision相关指令),可以看到实际碰撞体,排查效率极高。

5.3 射线命中不准确、穿过缝隙或边缘抖动

有些地形是多个静态网格拼接的,加上碰撞体间隙,会导致射线在缝隙处穿透。解决办法:

  • 用bTraceComplex时对静态网格做复杂检测,能减少缝隙穿透,但性能开销大。
  • 给关键地形块加上Async物理体,或者使用Chaos的Accurate模式。
  • 更实际的做法是:检测到命中后,对FHitResult.ImpactPoint做一次小范围球体检测,确认周围接触面。

对于高速移动的子弹射线,抖动多半是因为使用了LineTraceSingleByChannel但碰撞体本身有细微的物理更新延迟。可以给子弹做SweepSingleByChannel用胶囊体或球体做扫掠,而不是纯射线。

5.4 多阶段查询的顺序陷阱

当你要先做ECC_Visibility检测,再对命中点做对象查询时,顺序很容易搞反。我的建议是:

  1. 先 Visibility 通道检测,得到遮挡信息。
  2. 如果被遮挡,再根据业务决定是否继续检测(比如墙壁后面的人也要报警)。
  3. 如果没被遮挡,再 Object Query 精确找 Pawn。

有些系统反而要先 Object Query 找到目标,再 Visibility 确认可见。这两种顺序在不同游戏交互里结果相似,但因为返回的FHitResult不一样,后续逻辑分支也会有差异。写代码前想清楚你要的是“可见且可命中”,还是“最近的目标是否可见”。

6. 性能优化与常用技巧

6.1 射线检测的频率控制

射线检测本身不贵,但高频跑(比如每一 tick 都做多次)依然可能吃性能。我通常给 AIController 或玩家准星射线加一个最小间隔:

float Timer = 0.0f; const float RayInterval = 0.05f; void Tick(float DeltaTime) { Timer += DeltaTime; if (Timer >= RayInterval) { Timer = 0.0f; PerformRaycast(); } }

或者用FTimerManager设置定时器,避免每个 tick 都算。对竞技类射击这种要求极低延迟的,可以每帧跑,但要做好采样点合并。

6.2 使用 FCollisionQueryParams 的 bReturnPhysicalMaterial

如果你要做不同材质反馈,比如子弹打在金属上火花、木头上木屑,就需要bReturnPhysicalMaterial = true:

Params.bReturnPhysicalMaterial = true;

拿到的FHitResult.PhysMaterial可以获取UPhysicalMaterial,再查它的属性来做音效和特效切换。这个开关会轻微增加开销,只在需要时打开。

6.3 使用 UWorld:: LineTraceSingleByChannel 和 AsyncTrace 的取舍

这里额外提一句异步追踪。UE5 提供:

FSingleLinearSweepAsyncUserData

和各种AsyncTrace 支持,适合在物理模拟复杂、需要避免主线程卡顿的场景。但异步会引入回调时机问题,尤其是销毁对象时要特别小心回调里访问已失效的 Actor。我个人建议,除非你真的在 profiler 里看到红线,否则用同步射线就够了,简单可靠。

7. 从源码角度看一下 FCollisionObjectQueryParams 的实现机制

7.1 构造函数和掩码

打开引擎源码Engine/Source/Runtime/Engine/Classes/Engine/EngineTypes.h,能看到FCollisionObjectQueryParams定义。关键成员是:

FCollisionQueryFlag ObjectTypesToQuery;

FCollisionQueryFlag本质上是一个可以和 64 位掩码互转的封装类型。构造函数:

FCollisionObjectQueryParams(enum InitType InType, EObjectTypeQuery InObjectType) { switch (InType) { case AllObjects: ObjectTypesToQuery = FCollisionQueryFlag::GetAllObjectsMask(); break; case AllStaticObjects: ObjectTypesToQuery = FCollisionQueryFlag::GetAllStaticObjectsMask(); break; case AllDynamicObjects: ObjectTypesToQuery = FCollisionQueryFlag::GetAllDynamicObjectsMask(); break; } }

所以AddObjectTypesToQuery其实就是对ObjectTypesToQuery进行按位或操作。

7.2 从掩码到实际碰撞检测

物理引擎拿到ObjectTypesToQuery后,在FPhysScene的碰撞查询里使用QueryFilter进行过滤。简单说来就是:

  • 对每个潜在碰撞体,先查它的 Object Type 是否在掩码位里。
  • 不在则直接跳过,不参与后续碰撞响应检测。
  • 在则继续做碰撞形状的相交检测。

这个机制让你可以放心地在高频率查询里指定少量类型,减少无效的几何体相交测试。

7.3 为什么要区分 UEngineTypes::ConvertToObjectType

EObjectTypeQuery在蓝图里被序列化为名字,和编辑器里的 Object Type 名称对应。而ECollisionChannel是完整矩阵通道。两者数值表示不一定相同,所以转换函数的存在是为了弥合 BP 和 C++ 两套体系的鸿沟。

一个有意思的细节是,EObjectTypeQuery的默认值对应ECC_WorldStatic的 “Object Type” 位,而ObjectTypeQuery_MAX常用来表示未指定。

8. 一些来自项目里的经验总结

8.1 关于 ECC_Visibility 的默认语义

在多人游戏里,玩家角色的 Pawn 默认对 Visibility 是 Ignore,这确实容易造成“能穿过队友狙击”的错觉。但如果你把 Visibility 对 Pawn 改成 Block,又要防止队友挡子弹,需要在子弹检测时单独忽略队友的通道或 Actor。这种博弈很常见,没有全局最优解,关键是把设计意图在代码注释里写清楚。

8.2 阅读FCollisionObjectQueryParams源码的收益

我建议每个 UE C++ 开发者都花一点时间读一遍EngineTypes.h里ECollisionChannel、ECollisionResponse、FCollisionObjectQueryParams这三个核心定义。读完你会发现:

  • 碰撞系统不是魔法,是一张可预测的矩阵。
  • 对象查询不是另一个物理系统,而是对同一套物理体的过滤视图。
  • 所有复杂 bug 几乎都能归因到“矩阵没配好”或“掩码没设对”这两类。

8.3 写一个自己的调试工具

最后分享一个提升效率的小技巧:写一个DrawDebugLine封装的调试函数,在射线检测前把起点终点画出来,命中点画一个红点。这样你能在 PIE 里直观看到射线实际路径,排错效率翻倍。

void DrawDebugRay(UWorld* World, const FVector& Start, const FVector& End, const FHitResult* Hit, float Duration = 5.0f) { if (!World) { return; } DrawDebugLine(World, Start, End, FColor::Cyan, false, Duration, 0, 2.0f); if (Hit && Hit->bBlockingHit) { DrawDebugSphere(World, Hit->ImpactPoint, 8.0f, 12, FColor::Red, false, Duration, 0, 2.0f); } }

把这类函数放在你的工具库中,任何射线问题都能快速可视化。

最后再纠正一个很多教程没讲透的概念:拿ECollisionChannel去推敲FCollisionObjectQueryParams::InitType的枚举值是不对路的。两者本质是不同查询路径的入口参数,InitType负责预设掩码,ECollisionChannel负责矩阵索引。把它们理解成两套并行的“碰撞口味选择器”,你的射线代码才能真正稳定可控。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 17:13:21

降AI率工具实测:8款改写方案破解AIGC检测全解析

我有个直系学弟&#xff0c;毕业论文查重已经压到8%&#xff0c;高兴没两天&#xff0c;学院通知里多了一项AIGC检测结果——系统显示AI率46%&#xff0c;直接被学院拎进二次修改名单。这两年很多学校把AIGC检测当作毕业论文、课程报告、竞赛论文的隐性门槛&#xff0c;AI率不合…

作者头像 李华
网站建设 2026/9/29 17:13:09

MCP生产落地三关:权限、超时与审计的工程实践

我最早接触 MCP 是帮人调试本地工具服务&#xff0c;一个 server 把文件目录暴露出去&#xff0c;敲一句“帮我把桌面上那份 PDF 整理一下”&#xff0c;模型真的动了。那一刻确实有“这个东西能落地了”的感觉。但感动只持续到第二批需求进来&#xff1a;要接公司内部的 Excel…

作者头像 李华
网站建设 2026/9/29 17:13:06

Questa-Intel FPGA Starter Edition免费许可申请与配置避坑指南

1. 为什么我建议你改用Questa-Intel FPGA Starter Edition做FPGA仿真的朋友应该都绕不开仿真工具选型这个坎。以前我一直在用ModelSim&#xff0c;后来换到Questa-Intel FPGA Starter Edition&#xff0c;体验完全是两个级别。先说结论&#xff0c;Questa-Intel FPGA Starter E…

作者头像 李华
网站建设 2026/9/29 17:12:39

微信聊天记录变AI知识库:Codex+Obsidian完整落地指南

我一直觉得&#xff0c;平时躺在微信对话框里的聊天记录&#xff0c;是被浪费得最严重的一类数据。工作群里确认过的方案、和客户来回掰扯过的需求细节、深夜讨论出来的一版技术选型&#xff0c;事情办完之后就沉底了&#xff0c;再想找出来只能靠手指往上划屏。最近社区里冒出…

作者头像 李华
网站建设 2026/9/29 17:12:10

Claude Code插件体系实战:harness、skills与官方插件完整解析

最近后台问我 Claude 插件的人突然变多了&#xff0c;尤其是一个叫claude-plugins-official的仓库名反复出现。有人把它当普通第三方库&#xff0c;也有人一看到plugins就以为是“挂载几个依赖包”那么简单。其实这个仓库背后是 Claude Code 的整套插件体系&#xff1a;harness…

作者头像 李华
网站建设 2026/9/29 17:12:10

Dify实战:搭建AI复盘助手hindsight,让日志变成决策依据

1. 项目起源&#xff1a;为什么我需要一个"事后复盘"AI 先说清楚这个项目是干嘛的。hindsight 这个词本身就是"事后聪明"的意思&#xff0c;指回头看时对已经发生的事情有了更清晰的理解。我做这个项目的起因特别朴素&#xff1a;身边不少人包括我自己&…

作者头像 李华