Resource
Reference collision channels, response rules, presets, trace and overlap APIs, query parameters, FHitResult fields, events, and recipes.
Start here
Eight things cover almost all of collision. Skim them, then open the reference below only when you need an exact channel, preset, or function.
Collision Enabled has two parts: Query (traces and overlaps detect it) and Physics (it simulates and blocks). Most gameplay components only need Query.
Each side responds with Block, Overlap, or Ignore, and the weakest response wins. Both sides must Block to produce a block, and any Ignore overrides the other response.
An object channel describes what a component is, such as Pawn. A trace channel describes what a query runs against, such as Visibility. Match the query to the right channel family or it returns nothing.
A preset bundles a collision mode, an object type, and a full response table under one name, such as BlockAll or OverlapOnlyPawn. It's ideal to start from a preset, then adjust individual responses.
A trace casts a ray or shape and returns the first blocking hit. An overlap returns everything inside a shape. Both can query by channel, object type, or profile.
A blocking trace fills an FHitResult with the actor and component hit, the impact point, the surface normal, and the distance.
Begin and End overlap events fire only when both components have Generate Overlap Events enabled. The overlap response on its own is not enough.
A trace that returns nothing is almost always a channel-family mismatch, a custom channel left set to Ignore, or collision that isn't Query-enabled.
Reference
ECollisionEnabled is the master switch on every primitive. It decides which subsystems process the body: spatial queries, physics, or the Chaos probe path. A query never sees a body that lacks query collision, no matter what its responses say.
Gotcha
Queries only see Query-enabled components
Traces, sweeps, and overlaps only see components with CollisionEnabled of QueryOnly or QueryAndPhysics. A mesh set to PhysicsOnly still collides physically but is invisible to every query. When a trace passes through something, check its CollisionEnabled first.
A collision channel is just an ECollisionChannel value. The built-ins split into two families: object channels (what a component is) and trace channels (what a query runs against). Pick the wrong family and the query silently returns nothing. It's the first bug most people hit when they start writing traces.
Channels carry no inherent response. The Default column shows the engine container default (ECR_Block). The effective default for each named channel is set by DefaultChannelResponses in your DefaultEngine.ini, and custom slots are commonly configured to Ignore.
Object channels
Object-type channels answer "what kind of thing am I?" They are assigned as a component's CollisionObjectType. The built-ins are WorldStatic, WorldDynamic, Pawn, PhysicsBody, Vehicle, and the legacy Destructible. Object queries (OverlapMultiByObjectType, the Blueprint "Object Types" array) match against other components' object types, and the physics solver uses them to decide whether two bodies block or overlap.
Trace channels
Trace-type channels work the other way around. Nothing sets a trace channel as its object type. Components instead set a Block, Overlap, or Ignore response toward it. The built-ins are Visibility and Camera. Ray and sweep queries (LineTraceSingleByChannel, the Blueprint LineTraceByChannel node) run against trace channels.
Picking the wrong family silently returns nothing. Object queries ask what something is. Channel queries ask something different: whether a component blocks, overlaps, or ignores a given trace. A custom GameTraceChannel slot can be declared as either family in Project Settings, and that choice is metadata: the underlying integer value is the same packed channel.
Declaring custom channels
Custom channels live in Config/DefaultEngine.ini under [/Script/Engine.CollisionProfile]. Each entry becomes the next ECC_GameTraceChannelN slot in creation order.
; Config/DefaultEngine.ini
[/Script/Engine.CollisionProfile]
; First custom entry -> ECC_GameTraceChannel1 (value 14): an OBJECT channel
+DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,DefaultResponse=ECR_Block,bTraceType=False,bStaticObject=False,Name="Projectile")
; Second custom entry -> ECC_GameTraceChannel2 (value 15): a TRACE channel
+DefaultChannelResponses=(Channel=ECC_GameTraceChannel2,DefaultResponse=ECR_Ignore,bTraceType=True,bStaticObject=False,Name="Weapon")
; A preset that uses the custom Projectile object channel
+Profiles=(Name="Projectile",CollisionEnabled=QueryAndPhysics,bCanModify=True,ObjectTypeName="Projectile",CustomResponses=((Channel="Pawn",Response=ECR_Block),(Channel="WorldStatic",Response=ECR_Block),(Channel="Projectile",Response=ECR_Ignore)),HelpMessage="Fast-moving projectile collision")Gotcha
Custom channels start out ignored
A new custom channel's DefaultResponse is whatever you set in Project Settings, usually Ignore. Existing presets do not respond to it until you add a CustomResponse to each one. A fresh custom-channel trace that hits nothing almost always means no preset blocks it yet.
Every component responds to each channel with Block, Overlap, or Ignore. Two responses are always involved in a pair, and the engine resolves them to a single outcome. Getting this rule wrong is why overlaps don't fire and traces pass through.
The enum also defines ECR_MAX (3) as a non-usable sentinel.
How a pair resolves
Resolution matrix
| A ↓ / B → | Block | Overlap | Ignore |
|---|---|---|---|
| Block | Block | Overlap | Ignore |
| Overlap | Overlap | Overlap | Ignore |
| Ignore | Ignore | Ignore | Ignore |
Gotcha
Overlap needs Query collision and a non-Ignore response
A component must have CollisionEnabled set to QueryOnly or QueryAndPhysics, and its response to the other channel must not be Ignore. Both Block and Overlap responses generate overlap events; Block additionally blocks movement. NoCollision and PhysicsOnly produce zero overlap participation.
Presets bundle a CollisionEnabled mode, an object type, and a full response table under one name. These are the engine defaults from BaseEngine.ini. Expand any preset to see its exact per-channel responses and when to reach for it.
18 of 18 presets
The UWorld query functions come in three families. Query by Channel checks each component's response, by Object Type checks what a thing is, and by Profile uses a named response set you defined elsewhere. Every family then ships in Single and Multi forms, plus a boolean Test. Expand a row for the full signature and a copyable example.
18 of 18 functions
Collision shapes
FCollisionShape::MakeSphere(float Radius)A single radius in cm. The cheapest swept and overlap shape. Rotation is irrelevant to a sphere, but the FQuat parameter is still required, so pass FQuat::Identity.
FCollisionShape::MakeCapsule(float Radius, float HalfHeight)HalfHeight is half the total height, so 88 means 176 total. If HalfHeight is less than or equal to Radius the capsule degenerates toward a sphere. MakeCapsule does not validate the values, so pass geometrically valid inputs.
FCollisionShape::MakeBox(const FVector& HalfExtent)Takes half-extents, not full size. Oriented by the FQuat Rot argument of the query.
Use the LineTrace* functions (no shape argument)You do not pass a shape to LineTrace* functions. A zero-extent shape passed to a Sweep* call degenerates to a raycast internally.
Gotcha
Trace channel vs object channel mismatch
ByChannel queries treat the channel as a trace channel and check each actor's response to it. ByObjectType queries check what each actor is. Passing an object channel to a ByChannel query, or the reverse, silently returns wrong or empty results.
Gotcha
Simple vs complex collision and bTraceComplex
Traces hit simple collision by default. Per-poly accuracy needs bTraceComplex = true and the mesh having complex collision data. A static mesh with no simple collision and bTraceComplex false will not be hit by a trace at all.
Gotcha
The pawn capsule blocks traces and steals hits
The Character capsule blocks the Camera and Pawn channels and is larger than the visible mesh. Its response to Visibility is preset-dependent: the stock Pawn profile ignores Visibility. A camera-channel trace can hit your own capsule before reaching the target, so AddIgnoredActor(GetPawn()).
Three structs configure a query. FCollisionQueryParams controls complexity, ignore lists, and what gets reported. FCollisionObjectQueryParams holds the object types for a ByObjectType query. FCollisionResponseParams overrides responses for a single ByChannel call.
FCollisionQueryParamsComplexity, ignore lists, and what a query reports.bTraceComplexbooldefault falseTrace against complex per-poly collision instead of simple geometry. Required to get a meaningful FaceIndex.
bReturnPhysicalMaterialbooldefault falsePopulates FHitResult::PhysMaterial at the hit. Off by default for performance.
bReturnFaceIndexbooldefault falseReturns the triangle FaceIndex. Needs bTraceComplex and "Support UV From Hit Results" enabled in project settings to be reliable.
bIgnoreTouchesbooldefault falseIgnores Overlap (touch) results and reports only blocks. Useful for multi queries where you want blocks alone.
bIgnoreBlocksbooldefault falseReports only overlaps and touches. A blocking hit still terminates a sweep's distance, this controls whether it gets reported.
bFindInitialOverlapsbooldefault trueFor sweeps, reports objects the shape already penetrates at the start (returned with bStartPenetrating = true, Time = 0).
TraceTagFNamedefault NAME_NoneTags the query for the console TraceTag / TraceTagAll debug system, which visualizes this specific trace.
OwnerTagFNamedefault NAME_NoneTags the owning system issuing the query, for debug grouping.
MobilityTypeEQueryMobilityTypedefault AnyFilter by mobility: Any (0), Static (1), or Dynamic (2). Lets a query hit only static or only movable primitives.
AddIgnoredActor()Adds an actor and all its components to the ignore list. AddIgnoredActor(GetOwner()) is the standard way to skip self.
AddIgnoredActors()Adds many actors at once from a TArray.
AddIgnoredComponent()Ignores a single primitive component. Very large ignore lists fall back to slower paths.
FCollisionObjectQueryParamsThe object types a ByObjectType query matches.AddObjectTypesToQuery()Adds an object type to match in a ByObjectType query. Construct directly from one channel. For example, FCollisionObjectQueryParams(ECC_Pawn).
FCollisionResponseParamsResponse overrides for a single ByChannel call.CollisionResponseFCollisionResponseContainerdefault DefaultResponseParamOverrides the per-channel response table for a ByChannel query without changing any component. Passed as the final argument to ByChannel traces.
14 of 14 parameters
A blocking trace always fills an FHitResult. The fields that trip people up are Location versus ImpactPoint and Normal versus ImpactNormal. In both pairs, the first value tracks the swept shape and the second tracks the surface it hit.
// Read a hit result after a single trace.
FHitResult Hit;
if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, Params))
{
AActor* HitActor = Hit.GetActor();
UPrimitiveComponent* Comp = Hit.GetComponent();
const FVector Surface = Hit.ImpactPoint; // point on the geometry
const FVector ShapeCenter = Hit.Location; // swept shape origin at contact
const FVector SurfaceNorm = Hit.ImpactNormal; // for decals / reflection
const FName Bone = Hit.BoneName; // skeletal hit
if (Hit.bStartPenetrating)
{
// Began inside geometry: Normal is the depenetration direction.
const FVector Fix = Hit.Normal * Hit.PenetrationDepth;
}
}Gotcha
Sweeps that start penetrating give an MTD normal
When a swept shape begins inside geometry, Hit.bStartPenetrating is true and Hit.Normal is a depenetration (minimum translation) direction, not a surface normal. Check bStartPenetrating before trusting ImpactPoint or Normal.
Overlap events and hit events are separate systems with separate prerequisites. Overlaps need query collision and GenerateOverlapEvents on both components. Physics hit events need a simulating body with Simulation Generates Hit Events.
Setting collision in C++
Gotcha
Overlap events need GenerateOverlapEvents on both components
Begin and End overlap fire only when both the overlapping and overlapped primitive have SetGenerateOverlapEvents(true). The flag is independent of the collision preset: it defaults to false on StaticMeshComponent and true on trigger volumes. Set it explicitly on both ends in BeginPlay.
Gotcha
Physics hit events need Simulation Generates Hit Events
OnComponentHit fires for simulating bodies only when SetNotifyRigidBodyCollision(true) is set. Non-simulated movement (a swept SetActorLocation) reports the block through the move's FHitResult, not OnComponentHit. Do not confuse Hit with Overlap.
Copy-ready C++ for the collision tasks that come up constantly. Each one leans on the rules above. Skip self, match the query family to the question, and don't misread the hit that comes back.
Fire a line trace forward from the player camera. Ignore the owning pawn so the trace doesn't hit yourself.
void AMyPlayerController::TraceFromCrosshair()
{
FVector CamLoc;
FRotator CamRot;
GetPlayerViewPoint(CamLoc, CamRot);
const FVector Start = CamLoc;
const FVector End = Start + CamRot.Vector() * 10000.f;
FHitResult Hit;
FCollisionQueryParams Params(SCENE_QUERY_STAT(CrosshairTrace), /*bTraceComplex=*/false);
Params.AddIgnoredActor(GetPawn());
if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, Params) && Hit.GetActor())
{
UE_LOG(LogTemp, Log, TEXT("Hit %s at %s"),
*Hit.GetActor()->GetName(), *Hit.ImpactPoint.ToString());
}
}Gather everything inside a sphere for AoE damage. ECC_Pawn is an object channel here, so the overlap collects components whose response to it is Overlap or Block. To strictly gather pawns, prefer OverlapMultiByObjectType.
void AMyActor::ApplyRadialAoE(const FVector& Center, float Radius)
{
TArray<FOverlapResult> Overlaps;
FCollisionShape Sphere = FCollisionShape::MakeSphere(Radius);
FCollisionQueryParams Params(SCENE_QUERY_STAT(AoEOverlap), /*bTraceComplex=*/false);
Params.AddIgnoredActor(this);
GetWorld()->OverlapMultiByChannel(
Overlaps, Center, FQuat::Identity, ECC_Pawn, Sphere, Params);
TSet<AActor*> Damaged; // OverlapMulti reports one result per component
for (const FOverlapResult& O : Overlaps)
{
if (AActor* A = O.GetActor(); A && !Damaged.Contains(A))
{
Damaged.Add(A);
// ApplyDamage(A, ...);
}
}
}Exclude both the firing actor and its instigator, the common case for weapons spawned by a pawn.
FHitResult AWeapon::FireTrace(const FVector& Start, const FVector& End)
{
FCollisionQueryParams Params(SCENE_QUERY_STAT(WeaponFire), /*bTraceComplex=*/true);
Params.AddIgnoredActor(this);
Params.AddIgnoredActor(GetOwner());
if (APawn* P = GetInstigator())
{
Params.AddIgnoredActor(P);
}
Params.bReturnPhysicalMaterial = true; // for surface-type and decals
FHitResult Hit;
GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, Params);
return Hit;
}Cast a short trace down from the capsule base to detect ground. Uses an object-type query so it matches static world geometry only, not pawns or dynamic props.
bool AMyCharacter::IsOnGround(float ProbeDistance) const
{
const UCapsuleComponent* Capsule = GetCapsuleComponent();
const float HalfHeight = Capsule->GetScaledCapsuleHalfHeight();
const FVector FootLoc = GetActorLocation() - FVector(0.f, 0.f, HalfHeight);
const FVector End = FootLoc - FVector(0.f, 0.f, ProbeDistance);
FCollisionObjectQueryParams ObjParams(ECC_WorldStatic);
FCollisionQueryParams Params(SCENE_QUERY_STAT(GroundCheck), false, this);
FHitResult Hit;
return GetWorld()->LineTraceSingleByObjectType(Hit, FootLoc, End, ObjParams, Params);
}Trace against a project-defined trace channel. Only actors whose preset blocks or overlaps that channel respond. See the custom-channel recipe for the alias.
// In a shared header, alias the engine-generated slot for readability:
// #define ECC_Interactable ECC_GameTraceChannel1
void AMyCharacter::TraceForInteractable()
{
const FVector Start = GetActorLocation();
const FVector End = Start + GetControlRotation().Vector() * 250.f;
FCollisionQueryParams Params(SCENE_QUERY_STAT(Interact), false, this);
FHitResult Hit;
if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Interactable, Params))
{
if (Hit.GetActor() && Hit.GetActor()->Implements<UInteractable>())
{
IInteractable::Execute_OnFocus(Hit.GetActor(), this);
}
}
}Sweep a sphere along the projectile's movement this frame to catch fast movers that would tunnel through thin geometry.
void AProjectile::SweepStep(float DeltaTime)
{
const FVector Start = GetActorLocation();
const FVector End = Start + Velocity * DeltaTime;
FCollisionQueryParams Params(SCENE_QUERY_STAT(ProjSweep), false, this);
Params.AddIgnoredActor(GetInstigator());
FHitResult Hit;
const bool bHit = GetWorld()->SweepSingleByChannel(
Hit, Start, End, FQuat::Identity, ECC_Visibility,
FCollisionShape::MakeSphere(8.f), Params);
if (bHit)
{
SetActorLocation(Hit.Location); // Location = swept shape center at contact
OnProjectileImpact(Hit);
}
else
{
SetActorLocation(End);
}
}Object queries key off the component type, not the per-channel response. Use this to find every pawn and physics body along a ray.
void AMyActor::ObjectTrace(const FVector& Start, const FVector& End)
{
FCollisionObjectQueryParams ObjParams;
ObjParams.AddObjectTypesToQuery(ECC_Pawn);
ObjParams.AddObjectTypesToQuery(ECC_PhysicsBody);
ObjParams.AddObjectTypesToQuery(ECC_WorldDynamic);
FCollisionQueryParams Params(SCENE_QUERY_STAT(ObjTrace), false, this);
TArray<FHitResult> Hits;
GetWorld()->LineTraceMultiByObjectType(Hits, Start, End, ObjParams, Params);
for (const FHitResult& H : Hits)
{
UE_LOG(LogTemp, Log, TEXT("Object hit: %s"), *GetNameSafe(H.GetActor()));
}
}Wire OnComponentBeginOverlap to a UFUNCTION handler. The signature must match the delegate exactly or AddDynamic fails to compile. Both components need overlap events enabled.
// Header
UFUNCTION()
void OnBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult);
// Cpp
void ATriggerActor::BeginPlay()
{
Super::BeginPlay();
if (TriggerVolume)
{
TriggerVolume->SetGenerateOverlapEvents(true);
TriggerVolume->OnComponentBeginOverlap.AddDynamic(this, &ATriggerActor::OnBeginOverlap);
}
}
void ATriggerActor::OnBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor && OtherActor != this)
{
UE_LOG(LogTemp, Log, TEXT("Entered trigger: %s"), *OtherActor->GetName());
}
}Visualize a trace with the low-level DrawDebug helpers. Green is a clear path, red is blocked, the sphere marks the impact. Needs DrawDebugHelpers.h.
#include "DrawDebugHelpers.h"
void AMyActor::DebugTrace(const FVector& Start, const FVector& End)
{
FHitResult Hit;
FCollisionQueryParams Params(SCENE_QUERY_STAT(DbgTrace), false, this);
const bool bHit = GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, Params);
const FVector DrawEnd = bHit ? Hit.ImpactPoint : End;
DrawDebugLine(GetWorld(), Start, DrawEnd, bHit ? FColor::Red : FColor::Green,
/*bPersistent=*/false, /*LifeTime=*/2.f, /*DepthPriority=*/0, /*Thickness=*/1.f);
if (bHit)
{
DrawDebugSphere(GetWorld(), Hit.ImpactPoint, 12.f, 12, FColor::Red, false, 2.f);
}
}The Kismet trace nodes take an EDrawDebugTrace, so you get Blueprint-style debug drawing from C++ for free.
#include "Kismet/KismetSystemLibrary.h"
void AMyActor::KismetDebugTrace(const FVector& Start, const FVector& End)
{
TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes;
ObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECC_WorldStatic));
ObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECC_Pawn));
TArray<AActor*> ActorsToIgnore = { this };
FHitResult Hit;
UKismetSystemLibrary::LineTraceSingleForObjects(
this, Start, End, ObjectTypes, /*bTraceComplex=*/false, ActorsToIgnore,
EDrawDebugTrace::ForDuration, Hit, /*bIgnoreSelf=*/true,
FLinearColor::Red, FLinearColor::Green, /*DrawTime=*/2.f);
}Define a custom trace channel and a profile that uses it, then query the named profile. The profile bundles a full response table, so one query honors all of its blocks, overlaps, and ignores.
// DefaultEngine.ini, custom trace channel + a profile that uses it:
// [/Script/Engine.CollisionProfile]
// +DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,DefaultResponse=ECR_Ignore,bTraceType=True,Name="Interactable")
// +Profiles=(Name="InteractionTrace",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",
// CustomResponses=((Channel="Interactable",Response=ECR_Block)))
void AMyActor::ProfileTrace(const FVector& Start, const FVector& End)
{
static const FName ProfileName(TEXT("InteractionTrace"));
FCollisionQueryParams Params(SCENE_QUERY_STAT(ProfileTrace), false, this);
FHitResult Hit;
if (GetWorld()->LineTraceSingleByProfile(Hit, Start, End, ProfileName, Params))
{
UE_LOG(LogTemp, Log, TEXT("Profile trace hit %s"), *GetNameSafe(Hit.GetActor()));
}
}Gotcha
Overlap Multi reports the same actor more than once
OverlapMultiByChannel returns one FOverlapResult per overlapping component, so a multi-component actor appears several times. De-duplicate by actor before applying AoE damage.
When a query returns nothing or an overlap won't fire, these show you what the collision system is actually doing. The Collision Analyzer records every scene query, and the Chaos debug-draw cvars visualize shapes and contacts live.
show CollisionConsoleViewport show flag that renders simple collision hulls on visible primitives. Also in the viewport Show menu.
Collision view mode (Player / Visibility)ToolViewport View Mode > Collision. Player Collision shows what blocks the Pawn channel; Visibility Collision shows what blocks the Visibility trace channel.
show CollisionPawn / show CollisionVisibilityConsoleShow flags isolating Pawn-blocking and Visibility-blocking geometry, mirroring the collision view-mode submodes.
Collision Analyzer (Window > Developer Tools)ToolRecords every scene query with channel, params, timing, and result. Hit Record, run gameplay, then filter to find why a query missed. Reach for it first when a trace returns nothing and you need to know why.
TraceTag / TraceTagAllConsoleVisualizes traces tagged with a matching FName TraceTag in FCollisionQueryParams. TraceTagAll draws every query.
p.Chaos.DebugDraw.Enabled 1CVarMaster switch for Chaos debug drawing. Combine with the specific ShowShapes / ShowContacts cvars. Needs a Development or DebugGame build.
p.Chaos.DebugDraw.ShowShapes / ShowContactsCVarDraws collision shapes and contact points. Exact sub-cvar names shift across 5.x point releases, so check help p.Chaos.DebugDraw.
Chaos Visual Debugger (ChaosVD)ToolThe UE5.3+ standalone tool that records solver state to a file for frame-by-frame inspection. The modern replacement for pxvis.
DrawDebugLine / Sphere / Capsule / BoxAPIC++ helpers in DrawDebugHelpers.h for ad-hoc visualization. Compiled out in Shipping unless ENABLE_DRAW_DEBUG is forced.
EDrawDebugTrace::TypeAPIEnum (None, ForOneFrame, ForDuration, Persistent) passed to KismetSystemLibrary trace functions to draw the trace and hit markers.
stat CollisionConsoleOn-screen stat group for scene-query counts and timings. Use it to spot excessive or expensive queries per frame.
stat PhysicsConsoleOn-screen stat group for the Chaos solver: simulation, broadphase, narrowphase, and collision-detection cost.
pxvis collisionConsoleLegacy PhysX visualization. With Chaos default it is a no-op or unavailable. Use the p.Chaos.DebugDraw cvars and ChaosVD instead.
13 of 13 commands
Related Content
Unreal Directive is free and ad-free.
If it saved you time, you can help keep it that way.