Skip to content

Instantly share code, notes, and snippets.

@windopper
Created October 25, 2025 05:06
Show Gist options
  • Save windopper/063de331667cae326587a5909aff9bdd to your computer and use it in GitHub Desktop.
Save windopper/063de331667cae326587a5909aff9bdd to your computer and use it in GitHub Desktop.
ue5 cone-shaped trace multi
// reference https://dev.epicgames.com/community/learning/tutorials/pBv0/how-to-perform-cone-shaped-traces-in-unreal-engine
// CollisionLibrary.h
public:
UFUNCTION(BlueprintCallable, Category = "Collision", Meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", AutoCreateRefTerm = "ActorsToIgnore", DisplayName = "Multi Cone Trace By Channel", AdvancedDisplay = "TraceColor, TraceHitColor, DrawTime", Keywords = "sweep"))
static bool ConeTraceMulti(const UObject* WorldContextObject, const FVector Start, const FRotator Direction, float ConeHeight, float ConeHalfAngle, ETraceTypeQuery TraceChannel, bool bTraceComplex, const TArray<AActor*>& ActorsToIgnore, EDrawDebugTrace::Type DrawDebugType, TArray<FHitResult>& OutHits, bool bIgnoreSelf, FLinearColor TraceColor = FLinearColor::Red, FLinearColor TraceHitColor = FLinearColor::Green, float DrawTime = 5.0f);
// CollisionLibrary.cpp
bool UCollisionLibrary::ConeTraceMulti(
const UObject* WorldContextObject,
const FVector Start,
const FRotator Direction,
float ConeHeight,
float ConeHalfAngle,
ETraceTypeQuery TraceChannel,
bool bTraceComplex,
const TArray<AActor*>& ActorsToIgnore,
EDrawDebugTrace::Type DrawDebugType,
TArray<FHitResult>& OutHits,
FLinearColor TraceColor,
FLinearColor TraceHitColor,
float DrawTime)
{
OutHits.Reset();
ECollisionChannel CollisionChannel = UEngineTypes::ConvertToCollisionChannel(TraceChannel);
FCollisionQueryParams Params(SCENE_QUERY_STAT(ConeTraceMulti), bTraceComplex);
Params.bReturnPhysicalMaterial = true;
Params.AddIgnoredActors(ActorsToIgnore);
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
if (!World)
{
return false;
}
TArray<FHitResult> TempHitResults;
const FVector End = Start + (Direction.Vector() * ConeHeight);
const double ConeHalfAngleRad = FMath::DegreesToRadians(ConeHalfAngle);
// r = h * tan(theta / 2)
const double ConeBaseRadius = ConeHeight * tan(ConeHalfAngleRad);
const FCollisionShape SphereSweep = FCollisionShape::MakeSphere(ConeBaseRadius);
// Perform a sweep encompassing an imaginary cone.
World->SweepMultiByChannel(TempHitResults, Start, End, Direction.Quaternion(), CollisionChannel, SphereSweep, Params);
// Filter for hits that would be inside the cone.
for (FHitResult& HitResult : TempHitResults)
{
const FVector HitDirection = (HitResult.ImpactPoint - Start).GetSafeNormal();
const double Dot = FVector::DotProduct(Direction.Vector(), HitDirection);
// theta = arccos((A • B) / (|A|*|B|)). |A|*|B| = 1 because A and B are unit vectors.
const double DeltaAngle = FMath::Acos(Dot);
// Hit is outside the angle of the cone.
if (DeltaAngle > ConeHalfAngleRad)
{
continue;
}
const double Distance = (HitResult.ImpactPoint - Start).Length();
// Hypotenuse = adjacent / cos(theta)
const double LengthAtAngle = ConeHeight / cos(DeltaAngle);
// Hit is beyond the cone. This can happen because we sweep with spheres, which results in a cap at the end of the sweep.
if (Distance > LengthAtAngle)
{
continue;
}
OutHits.Add(HitResult);
}
#if ENABLE_DRAW_DEBUG
if (DrawDebugType != EDrawDebugTrace::None)
{
// Cone trace.
const double ConeSlantHeight = FMath::Sqrt((ConeBaseRadius * ConeBaseRadius) + (ConeHeight * ConeHeight)); // s = sqrt(r^2 + h^2)
DrawDebugCone(World, Start, Direction.Vector(), ConeSlantHeight, ConeHalfAngleRad, ConeHalfAngleRad, 32, TraceColor.ToFColor(true), (DrawDebugType == EDrawDebugTrace::Persistent), DrawTime);
// Uncomment to see the trace we're actually performing.
// DrawDebugSweptSphere(World, Start, End, ConeBaseRadius, TraceColor.ToFColor(true), (DrawDebugType == EDrawDebugTrace::Persistent), DrawTime);
// Successful hits.
for (const FHitResult& Hit : OutHits)
{
DrawDebugLineTraceSingle(World, Hit.TraceStart, Hit.ImpactPoint, DrawDebugType, true, Hit, TraceHitColor, TraceHitColor, DrawTime);
}
// Uncomment to see hits from the sphere sweep that were filtered out.
// for (const FHitResult& Hit : TempHitResults)
// {
// if (!OutHits.ContainsByPredicate([Hit](const FHitResult& Other)
// {
// return (Hit.GetActor() == Other.GetActor()) &&
// (Hit.ImpactPoint == Other.ImpactPoint) &&
// (Hit.ImpactNormal == Other.ImpactNormal);
// }))
// {
// DrawDebugLineTraceSingle(World, Hit.TraceStart, Hit.ImpactPoint, DrawDebugType, false, Hit, FColor::Red, FColor::Red, DrawTime);
// }
// }
}
#endif // ENABLE_DRAW_DEBUG
return (OutHits.Num() > 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment