├── .gitignore ├── Images ├── image01.PNG ├── image02.PNG ├── image03.PNG ├── image04.PNG ├── image05.PNG ├── image06.PNG ├── image07.PNG └── image08.PNG ├── LICENSE ├── README.md └── Source ├── CollisionQueryDrawDebugHelpers.cpp ├── CollisionQueryDrawDebugHelpers.h ├── CollisionQueryTestActor.cpp └── CollisionQueryTestActor.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.xcworkspace 37 | *.sln 38 | *.suo 39 | *.opensdf 40 | *.sdf 41 | *.VC.db 42 | *.VC.opendb 43 | 44 | # Precompiled Assets 45 | SourceArt/**/*.png 46 | SourceArt/**/*.tga 47 | 48 | # Binary Files 49 | Binaries/* 50 | Plugins/*/Binaries/* 51 | 52 | # Builds 53 | Build/* 54 | 55 | # Whitelist PakBlacklist-.txt files 56 | !Build/*/ 57 | Build/*/** 58 | !Build/*/PakBlacklist*.txt 59 | 60 | # Don't ignore icon files in Build 61 | !Build/**/*.ico 62 | 63 | # Built data for maps 64 | *_BuiltData.uasset 65 | 66 | # Configuration files generated by the Editor 67 | Saved/* 68 | 69 | # Compiled source files for the engine to use 70 | Intermediate/* 71 | Plugins/*/Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | -------------------------------------------------------------------------------- /Images/image01.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StudioGobo/UECollisionQueryTools/3386c4221f8d0c578b5d6cfe59c2c372093b78ca/Images/image01.PNG -------------------------------------------------------------------------------- /Images/image02.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StudioGobo/UECollisionQueryTools/3386c4221f8d0c578b5d6cfe59c2c372093b78ca/Images/image02.PNG -------------------------------------------------------------------------------- /Images/image03.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StudioGobo/UECollisionQueryTools/3386c4221f8d0c578b5d6cfe59c2c372093b78ca/Images/image03.PNG -------------------------------------------------------------------------------- /Images/image04.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StudioGobo/UECollisionQueryTools/3386c4221f8d0c578b5d6cfe59c2c372093b78ca/Images/image04.PNG -------------------------------------------------------------------------------- /Images/image05.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StudioGobo/UECollisionQueryTools/3386c4221f8d0c578b5d6cfe59c2c372093b78ca/Images/image05.PNG -------------------------------------------------------------------------------- /Images/image06.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StudioGobo/UECollisionQueryTools/3386c4221f8d0c578b5d6cfe59c2c372093b78ca/Images/image06.PNG -------------------------------------------------------------------------------- /Images/image07.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StudioGobo/UECollisionQueryTools/3386c4221f8d0c578b5d6cfe59c2c372093b78ca/Images/image07.PNG -------------------------------------------------------------------------------- /Images/image08.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StudioGobo/UECollisionQueryTools/3386c4221f8d0c578b5d6cfe59c2c372093b78ca/Images/image08.PNG -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Studio Gobo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UECollisionQueryTools 2 | This repo contains code for the CollisionQueryTestActor tool, mentioned in the Unreal Fest 2023 talk _Making Sense Of Collision Data_. A recording of the talk is available here: https://www.youtube.com/watch?v=xIQI6nXFygA 3 | 4 | This is a simple actor to help with testing collision queries and understanding collision data in levels in Unreal Engine. The actor can be configured to perform any collision query on tick. 5 | 6 | Additionally several debug draw helper functions are included for drawing swept shapes, which the tool makes use of. 7 | 8 | ## Compatibility 9 | The tool has been tested in Unreal Engine 5.1 and 5.2 10 | 11 | ## How do I use it? 12 | 1. Copy the code files into a suitable directory in your project. Compile the editor. 13 | 2. Place a CollisionQueryTestActor in your level using the Place Actors window. 14 | 15 | ![CollisionQueryTestActor in Place Actors window](/Images/image01.PNG) ![CollisionQueryTestActor in level](/Images/image02.PNG) 16 | 17 | 4. Start Play In Editor (PIE) or start Simulating. 18 | * The CollisionQueryTestActor should be drawing a red (or green) debug line. 19 | 20 | ![Start Play in Editor](/Images/image03.PNG) 21 | 22 | 5. Move and rotate the actor to define the start transform of the query. 23 | * You can also move the actor's EndComponent to separately define the end transform of the query. 24 | 25 | ![Line trace with no hit](/Images/image04.PNG) ![Line trace with successful hit](/Images/image05.PNG) 26 | 27 | 6. Configure the query via the Details Panel with the actor selected 28 | * Switch between line trace, sweep, and overlap test 29 | * Switch between tracing/sweeping by channel, profile, and object type 30 | * Switch between single/multi trace/sweep 31 | * Configure the shape of a sweep / overlap test 32 | * Configure the collision channel and responses 33 | * Configure other query parameters (eg. bTraceComplex) 34 | 35 | ![Actor properties in Details Panel](/Images/image07.PNG) 36 | 37 | 7. See the result of the query update live via the debug draw 38 | * Green = hit, Red = no hit, Blue = overlap 39 | * Traces / sweeps will be drawn to where they stopped 40 | * Points will be drawn to indicate hit / overlap locations 41 | 42 | ![Capsule sweep debug draw](/Images/image08.PNG) 43 | -------------------------------------------------------------------------------- /Source/CollisionQueryDrawDebugHelpers.cpp: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Copyright (c) Studio Gobo Ltd 2023 3 | // Licensed under the MIT license. 4 | // See LICENSE.TXT in the project root for license information. 5 | // ---------------------------------------------------------------------------- 6 | // File -> CollisionQueryDrawDebugHelpers.cpp 7 | // Created -> October 2023 8 | // Author -> George Prosser (Studio Gobo) 9 | 10 | #include "CollisionQueryDrawDebugHelpers.h" 11 | 12 | #if ENABLE_DRAW_DEBUG 13 | 14 | void DrawDebugSweptBox(const UWorld* World, const FVector& Start, const FVector& End, const FQuat& Rotation, const FVector& HalfSize, const FColor& Color, bool bPersistentLines, float LifeTime, uint8 DepthPriority, float Thickness) 15 | { 16 | DrawDebugBox(World, Start, HalfSize, Rotation, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 17 | DrawDebugBox(World, End, HalfSize, Rotation, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 18 | 19 | FVector Vertices[8]; 20 | Vertices[0] = Start + Rotation.RotateVector(FVector(-HalfSize.X, -HalfSize.Y, -HalfSize.Z)); 21 | Vertices[1] = Start + Rotation.RotateVector(FVector(-HalfSize.X, HalfSize.Y, -HalfSize.Z)); 22 | Vertices[2] = Start + Rotation.RotateVector(FVector(-HalfSize.X, -HalfSize.Y, HalfSize.Z)); 23 | Vertices[3] = Start + Rotation.RotateVector(FVector(-HalfSize.X, HalfSize.Y, HalfSize.Z)); 24 | Vertices[4] = Start + Rotation.RotateVector(FVector(HalfSize.X, -HalfSize.Y, -HalfSize.Z)); 25 | Vertices[5] = Start + Rotation.RotateVector(FVector(HalfSize.X, HalfSize.Y, -HalfSize.Z)); 26 | Vertices[6] = Start + Rotation.RotateVector(FVector(HalfSize.X, -HalfSize.Y, HalfSize.Z)); 27 | Vertices[7] = Start + Rotation.RotateVector(FVector(HalfSize.X, HalfSize.Y, HalfSize.Z)); 28 | 29 | const FVector TraceVec = End - Start; 30 | for (int32 VertexIdx = 0; VertexIdx < 8; ++VertexIdx) 31 | { 32 | DrawDebugLine(World, Vertices[VertexIdx], Vertices[VertexIdx] + TraceVec, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 33 | } 34 | } 35 | 36 | void DrawDebugSweptSphere(const UWorld* World, const FVector& Start, const FVector& End, float Radius, const FColor& Color, bool bPersistentLines, float LifeTime, uint8 DepthPriority, float Thickness) 37 | { 38 | const FVector TraceVec = End - Start; 39 | const float Dist = TraceVec.Size(); 40 | 41 | // draw the sweep of the sphere as a capsule 42 | const FVector Center = Start + TraceVec * 0.5f; 43 | const float CapsuleHalfHeight = (Dist * 0.5f) + Radius; 44 | const FQuat CapsuleRot = FRotationMatrix::MakeFromZ(TraceVec).ToQuat(); 45 | DrawDebugCapsule(World, Center, CapsuleHalfHeight, Radius, CapsuleRot, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 46 | 47 | // draw additional circles for the spheres at each end of the capsule 48 | DrawCircle(World, Start, CapsuleRot.GetAxisY(), CapsuleRot.GetAxisZ(), Color, Radius, 16, bPersistentLines, LifeTime, DepthPriority, Thickness); 49 | DrawCircle(World, Start, CapsuleRot.GetAxisX(), CapsuleRot.GetAxisZ(), Color, Radius, 16, bPersistentLines, LifeTime, DepthPriority, Thickness); 50 | DrawCircle(World, End, CapsuleRot.GetAxisY(), -CapsuleRot.GetAxisZ(), Color, Radius, 16, bPersistentLines, LifeTime, DepthPriority, Thickness); 51 | DrawCircle(World, End, CapsuleRot.GetAxisX(), -CapsuleRot.GetAxisZ(), Color, Radius, 16, bPersistentLines, LifeTime, DepthPriority, Thickness); 52 | } 53 | 54 | void DrawDebugSweptCapsule(const UWorld* World, const FVector& Start, const FVector& End, const FQuat& Rotation, float HalfHeight, float Radius, const FColor& Color, bool bPersistentLines, float LifeTime, uint8 DepthPriority, float Thickness) 55 | { 56 | DrawDebugCapsule(World, Start, HalfHeight, Radius, Rotation, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 57 | DrawDebugCapsule(World, End, HalfHeight, Radius, Rotation, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 58 | 59 | const FVector Up = Rotation.GetUpVector(); 60 | 61 | const FMatrix Mat = FRotationMatrix::MakeFromZX(End - Start, Up); 62 | const FVector YAxis = Mat.GetUnitAxis(EAxis::Y); 63 | const FVector XAxis = Mat.GetUnitAxis(EAxis::X); 64 | 65 | const float HalfLength = HalfHeight - Radius; 66 | const FVector StartTop = Start + HalfLength*Up; 67 | const FVector EndTop = End + HalfLength*Up; 68 | 69 | DrawDebugLine(World, StartTop + Radius*XAxis, EndTop + Radius*XAxis, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 70 | DrawDebugLine(World, StartTop + Radius*YAxis, EndTop + Radius*YAxis, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 71 | DrawDebugLine(World, StartTop - Radius*YAxis, EndTop - Radius*YAxis, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 72 | 73 | const FVector StartBottom = Start - HalfLength*Up; 74 | const FVector EndBottom = End - HalfLength*Up; 75 | 76 | DrawDebugLine(World, StartBottom - Radius*XAxis, EndBottom - Radius*XAxis, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 77 | DrawDebugLine(World, StartBottom + Radius*YAxis, EndBottom + Radius*YAxis, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 78 | DrawDebugLine(World, StartBottom - Radius*YAxis, EndBottom - Radius*YAxis, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 79 | } 80 | 81 | void DrawDebugSweptCollisionShape(const UWorld* World, const FVector& Start, const FVector& End, const FQuat& Rotation, const FCollisionShape& Shape, const FColor& Color, bool bPersistentLines, float LifeTime, uint8 DepthPriority, float Thickness) 82 | { 83 | switch (Shape.ShapeType) 84 | { 85 | case ECollisionShape::Line: 86 | DrawDebugLine(World, Start, End, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 87 | break; 88 | case ECollisionShape::Box: 89 | DrawDebugSweptBox(World, Start, End, Rotation, Shape.GetBox(), Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 90 | break; 91 | case ECollisionShape::Sphere: 92 | DrawDebugSweptSphere(World, Start, End, Shape.GetSphereRadius(), Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 93 | break; 94 | case ECollisionShape::Capsule: 95 | DrawDebugSweptCapsule(World, Start, End, Rotation, Shape.GetCapsuleHalfHeight(), Shape.GetCapsuleRadius(), Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 96 | break; 97 | } 98 | } 99 | 100 | void DrawDebugCollisionShape(const UWorld* World, const FVector& Pos, const FQuat& Rotation, const FCollisionShape& Shape, const FColor& Color, bool bPersistentLines, float LifeTime, uint8 DepthPriority, float Thickness) 101 | { 102 | switch (Shape.ShapeType) 103 | { 104 | case ECollisionShape::Line: 105 | // don't do anything 106 | break; 107 | case ECollisionShape::Box: 108 | DrawDebugBox(World, Pos, Shape.GetBox(), Rotation, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 109 | break; 110 | case ECollisionShape::Sphere: 111 | DrawCircle(World, Pos, Rotation.GetAxisX(), Rotation.GetAxisY(), Color, Shape.GetSphereRadius(), 16, bPersistentLines, LifeTime, DepthPriority, Thickness); 112 | DrawCircle(World, Pos, Rotation.GetAxisX(), Rotation.GetAxisZ(), Color, Shape.GetSphereRadius(), 16, bPersistentLines, LifeTime, DepthPriority, Thickness); 113 | break; 114 | case ECollisionShape::Capsule: 115 | DrawDebugCapsule(World, Pos, Shape.GetCapsuleHalfHeight(), Shape.GetCapsuleRadius(), Rotation, Color, bPersistentLines, LifeTime, DepthPriority, Thickness); 116 | break; 117 | } 118 | } 119 | 120 | #endif // ENABLE_DRAW_DEBUG -------------------------------------------------------------------------------- /Source/CollisionQueryDrawDebugHelpers.h: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Copyright (c) Studio Gobo Ltd 2023 3 | // Licensed under the MIT license. 4 | // See LICENSE.TXT in the project root for license information. 5 | // ---------------------------------------------------------------------------- 6 | // File -> CollisionQueryDrawDebugHelpers.h 7 | // Created -> October 2023 8 | // Author -> George Prosser (Studio Gobo) 9 | 10 | #pragma once 11 | 12 | #include "DrawDebugHelpers.h" 13 | #include "CollisionShape.h" 14 | 15 | #if ENABLE_DRAW_DEBUG 16 | 17 | void DrawDebugSweptBox(const UWorld* World, const FVector& Start, const FVector& End, const FQuat& Rotation, const FVector& HalfSize, const FColor& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0.f); 18 | void DrawDebugSweptSphere(const UWorld* World, const FVector& Start, const FVector& End, float Radius, const FColor& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0); 19 | void DrawDebugSweptCapsule(const UWorld* World, const FVector& Start, const FVector& End, const FQuat& Rotation, float HalfHeight, float Radius, const FColor& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0.f); 20 | void DrawDebugSweptCollisionShape(const UWorld* World, const FVector& Start, const FVector& End, const FQuat& Rotation, const FCollisionShape& Shape, const FColor& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0.f); 21 | void DrawDebugCollisionShape(const UWorld* World, const FVector& Pos, const FQuat& Rotation, const FCollisionShape& Shape, const FColor& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0.f); 22 | 23 | #endif // ENABLE_DRAW_DEBUG -------------------------------------------------------------------------------- /Source/CollisionQueryTestActor.cpp: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Copyright (c) Studio Gobo Ltd 2023 3 | // Licensed under the MIT license. 4 | // See LICENSE.TXT in the project root for license information. 5 | // ---------------------------------------------------------------------------- 6 | // File -> CollisionQueryTestActor.cpp 7 | // Created -> October 2023 8 | // Author -> George Prosser (Studio Gobo) 9 | 10 | #include "CollisionQueryTestActor.h" 11 | #include "CollisionQueryDrawDebugHelpers.h" 12 | #include "Engine/World.h" 13 | 14 | ACollisionQueryTestActor::ACollisionQueryTestActor(const FObjectInitializer& ObjectInitializer) 15 | : Super(ObjectInitializer) 16 | { 17 | PrimaryActorTick.bCanEverTick = true; 18 | PrimaryActorTick.bStartWithTickEnabled = true; 19 | 20 | StartComponent = ObjectInitializer.CreateDefaultSubobject(this, TEXT("Start")); 21 | #if WITH_EDITORONLY_DATA 22 | StartComponent->bVisualizeComponent = true; 23 | #endif 24 | RootComponent = StartComponent; 25 | 26 | EndComponent = ObjectInitializer.CreateDefaultSubobject(this, TEXT("End")); 27 | EndComponent->SetupAttachment(StartComponent); 28 | EndComponent->SetRelativeLocation(FVector(200.f, 0.f, 0.f)); 29 | #if WITH_EDITORONLY_DATA 30 | EndComponent->bVisualizeComponent = true; 31 | #endif 32 | } 33 | 34 | void ACollisionQueryTestActor::Tick(float DeltaSeconds) 35 | { 36 | const float LineThickness = 0.f; 37 | const float PointSize = 16.f; 38 | 39 | UWorld* World = GetWorld(); 40 | 41 | const FVector Pos = GetActorLocation(); 42 | const FQuat Rot = GetActorQuat(); 43 | 44 | const FVector Start = Pos; 45 | const FVector End = EndComponent->GetComponentLocation(); 46 | 47 | FCollisionQueryParams QueryParams; 48 | QueryParams.bTraceComplex = bTraceComplex; 49 | QueryParams.bFindInitialOverlaps = bFindInitialOverlaps; 50 | QueryParams.bIgnoreBlocks = bIgnoreBlocks; 51 | QueryParams.bIgnoreTouches = bIgnoreTouches; 52 | QueryParams.bSkipNarrowPhase = bSkipNarrowPhase; 53 | QueryParams.MobilityType = ConvertToQueryMobilityType(MobilityType); 54 | 55 | FCollisionResponseParams ResponseParams; 56 | ResponseParams.CollisionResponse = CollisionResponses; 57 | 58 | FCollisionObjectQueryParams ObjectQueryParams; 59 | ObjectQueryParams = FCollisionObjectQueryParams(UEngineTypes::ConvertToCollisionChannel(ObjectType)); 60 | 61 | FCollisionShape CollisionShape; 62 | if (Shape == ECollisionQueryTestShape::Box) 63 | { 64 | CollisionShape = FCollisionShape::MakeBox(BoxHalfExtent); 65 | } 66 | else if (Shape == ECollisionQueryTestShape::Sphere) 67 | { 68 | CollisionShape = FCollisionShape::MakeSphere(SphereRadius); 69 | } 70 | else if (Shape == ECollisionQueryTestShape::Capsule) 71 | { 72 | CollisionShape = FCollisionShape::MakeCapsule(CapsuleRadius, CapsuleHalfHeight); 73 | } 74 | 75 | bool bResult = false; 76 | FHitResult Hit; 77 | TArray Hits; 78 | TArray Overlaps; 79 | 80 | if (Query == ECollisionQueryTestType::LineTrace) 81 | { 82 | if (SingleMultiOrTest == ECollisionQueryTestSingleMultiOrTest::Single) 83 | { 84 | if (By == ECollisionQueryTestBy::Channel) 85 | { 86 | bResult = World->LineTraceSingleByChannel(Hit, Start, End, Channel, QueryParams, ResponseParams); 87 | } 88 | else if (By == ECollisionQueryTestBy::ObjectType) 89 | { 90 | bResult = World->LineTraceSingleByObjectType(Hit, Start, End, ObjectQueryParams, QueryParams); 91 | } 92 | else if (By == ECollisionQueryTestBy::Profile) 93 | { 94 | bResult = World->LineTraceSingleByProfile(Hit, Start, End, CollisionProfileName, QueryParams); 95 | } 96 | 97 | #if ENABLE_DRAW_DEBUG 98 | if (bResult) 99 | { 100 | DrawDebugLine(World, Start, Hit.Location, FColor::Green, false, 0.f, 0, LineThickness); 101 | DrawDebugPoint(World, Hit.ImpactPoint, PointSize, FColor::Green, false, 0.f, 0); 102 | } 103 | else 104 | { 105 | DrawDebugLine(World, Start, End, FColor::Red, false, 0.f, 0, LineThickness); 106 | } 107 | #endif // ENABLE_DRAW_DEBUG 108 | } 109 | else if (SingleMultiOrTest == ECollisionQueryTestSingleMultiOrTest::Multi) 110 | { 111 | if (By == ECollisionQueryTestBy::Channel) 112 | { 113 | bResult = World->LineTraceMultiByChannel(Hits, Start, End, Channel, QueryParams, ResponseParams); 114 | } 115 | else if (By == ECollisionQueryTestBy::ObjectType) 116 | { 117 | bResult = World->LineTraceMultiByObjectType(Hits, Start, End, ObjectQueryParams, QueryParams); 118 | } 119 | else if (By == ECollisionQueryTestBy::Profile) 120 | { 121 | bResult = World->LineTraceMultiByProfile(Hits, Start, End, CollisionProfileName, QueryParams); 122 | } 123 | 124 | #if ENABLE_DRAW_DEBUG 125 | if (bResult) 126 | { 127 | FVector TraceEnd = Hits.Last().Location; 128 | if (By == ECollisionQueryTestBy::ObjectType) 129 | { 130 | TraceEnd = End; // trace by object type does not stop on first blocking hit 131 | } 132 | 133 | DrawDebugLine(World, Start, TraceEnd, FColor::Green, false, 0.f, 0, LineThickness); 134 | } 135 | else 136 | { 137 | DrawDebugLine(World, Start, End, Hits.Num() > 0 ? FColor::Blue : FColor::Red, false, 0.f, 0, LineThickness); 138 | } 139 | 140 | for (const FHitResult& HitResult : Hits) 141 | { 142 | DrawDebugPoint(World, HitResult.ImpactPoint, PointSize, HitResult.bBlockingHit ? FColor::Green : FColor::Blue, false, 0.f, 0); 143 | } 144 | #endif // ENABLE_DRAW_DEBUG 145 | } 146 | else if (SingleMultiOrTest == ECollisionQueryTestSingleMultiOrTest::Test) 147 | { 148 | if (By == ECollisionQueryTestBy::Channel) 149 | { 150 | bResult = World->LineTraceTestByChannel(Start, End, Channel, QueryParams, ResponseParams); 151 | } 152 | else if (By == ECollisionQueryTestBy::ObjectType) 153 | { 154 | bResult = World->LineTraceTestByObjectType(Start, End, ObjectQueryParams, QueryParams); 155 | } 156 | else if (By == ECollisionQueryTestBy::Profile) 157 | { 158 | bResult = World->LineTraceTestByProfile(Start, End, CollisionProfileName, QueryParams); 159 | } 160 | 161 | #if ENABLE_DRAW_DEBUG 162 | DrawDebugLine(World, Start, End, bResult ? FColor::Green : FColor::Red, false, 0.f, 0, LineThickness); 163 | #endif // ENABLE_DRAW_DEBUG 164 | } 165 | } 166 | else if (Query == ECollisionQueryTestType::Sweep) 167 | { 168 | if (SingleMultiOrTest == ECollisionQueryTestSingleMultiOrTest::Single) 169 | { 170 | if (By == ECollisionQueryTestBy::Channel) 171 | { 172 | bResult = World->SweepSingleByChannel(Hit, Start, End, Rot, Channel, CollisionShape, QueryParams, ResponseParams); 173 | } 174 | else if (By == ECollisionQueryTestBy::ObjectType) 175 | { 176 | bResult = World->SweepSingleByObjectType(Hit, Start, End, Rot, ObjectQueryParams, CollisionShape, QueryParams); 177 | } 178 | else if (By == ECollisionQueryTestBy::Profile) 179 | { 180 | bResult = World->SweepSingleByProfile(Hit, Start, End, Rot, CollisionProfileName, CollisionShape, QueryParams); 181 | } 182 | 183 | #if ENABLE_DRAW_DEBUG 184 | if (bResult) 185 | { 186 | DrawDebugPoint(World, Hit.ImpactPoint, PointSize, FColor::Green, false, 0.f, 0); 187 | DrawDebugSweptCollisionShape(World, Start, Hit.Location, Rot, CollisionShape, FColor::Green, false, 0.f, 0, LineThickness); 188 | } 189 | else 190 | { 191 | DrawDebugSweptCollisionShape(World, Start, End, Rot, CollisionShape, FColor::Red, false, 0.f, 0, LineThickness); 192 | } 193 | #endif // ENABLE_DRAW_DEBUG 194 | } 195 | else if (SingleMultiOrTest == ECollisionQueryTestSingleMultiOrTest::Multi) 196 | { 197 | if (By == ECollisionQueryTestBy::Channel) 198 | { 199 | bResult = World->SweepMultiByChannel(Hits, Start, End, Rot, Channel, CollisionShape, QueryParams, ResponseParams); 200 | } 201 | else if (By == ECollisionQueryTestBy::ObjectType) 202 | { 203 | bResult = World->SweepMultiByObjectType(Hits, Start, End, Rot, ObjectQueryParams, CollisionShape, QueryParams); 204 | } 205 | else if (By == ECollisionQueryTestBy::Profile) 206 | { 207 | bResult = World->SweepMultiByProfile(Hits, Start, End, Rot, CollisionProfileName, CollisionShape, QueryParams); 208 | } 209 | 210 | #if ENABLE_DRAW_DEBUG 211 | if (bResult) 212 | { 213 | FVector SweepEnd = Hits.Last().Location; 214 | if (By == ECollisionQueryTestBy::ObjectType) 215 | { 216 | SweepEnd = End; // sweep by object type does not stop on first blocking hit 217 | } 218 | else if (Hits.Last().bStartPenetrating) 219 | { 220 | SweepEnd = End; // sweeps do not stop on initial blocking overlaps 221 | } 222 | 223 | DrawDebugSweptCollisionShape(World, Start, SweepEnd, Rot, CollisionShape, FColor::Green, false, 0.f, 0, LineThickness); 224 | } 225 | else 226 | { 227 | DrawDebugSweptCollisionShape(World, Start, End, Rot, CollisionShape, Hits.Num() > 0 ? FColor::Blue : FColor::Red, false, 0.f, 0, LineThickness); 228 | } 229 | 230 | for (const FHitResult& HitResult : Hits) 231 | { 232 | DrawDebugPoint(World, HitResult.ImpactPoint, PointSize, HitResult.bBlockingHit ? FColor::Green : FColor::Blue, false, 0.f, 0); 233 | } 234 | #endif // ENABLE_DRAW_DEBUG 235 | } 236 | else if (SingleMultiOrTest == ECollisionQueryTestSingleMultiOrTest::Test) 237 | { 238 | if (By == ECollisionQueryTestBy::Channel) 239 | { 240 | bResult = World->SweepTestByChannel(Start, End, Rot, Channel, CollisionShape, QueryParams, ResponseParams); 241 | } 242 | else if (By == ECollisionQueryTestBy::ObjectType) 243 | { 244 | bResult = World->SweepTestByObjectType(Start, End, Rot, ObjectQueryParams, CollisionShape, QueryParams); 245 | } 246 | else if (By == ECollisionQueryTestBy::Profile) 247 | { 248 | bResult = World->SweepTestByProfile(Start, End, Rot, CollisionProfileName, CollisionShape, QueryParams); 249 | } 250 | 251 | #if ENABLE_DRAW_DEBUG 252 | DrawDebugSweptCollisionShape(World, Start, End, Rot, CollisionShape, bResult ? FColor::Green : FColor::Red, false, 0.f, 0, LineThickness); 253 | #endif // ENABLE_DRAW_DEBUG 254 | } 255 | } 256 | else if (Query == ECollisionQueryTestType::Overlap) 257 | { 258 | if (BlockingAnyOrMulti == ECollisionQueryTestBlockingAnyOrMulti::BlockingTest) 259 | { 260 | if (By == ECollisionQueryTestBy::Channel) 261 | { 262 | bResult = World->OverlapBlockingTestByChannel(Pos, Rot, Channel, CollisionShape, QueryParams, ResponseParams); 263 | } 264 | else if (By == ECollisionQueryTestBy::ObjectType) 265 | { 266 | // NB: there is no OverlapBlockingTestByObjectType function because hits/overlaps from queries by object type are always considered blocking 267 | bResult = World->OverlapAnyTestByObjectType(Pos, Rot, ObjectQueryParams, CollisionShape, QueryParams); 268 | } 269 | else if (By == ECollisionQueryTestBy::Profile) 270 | { 271 | bResult = World->OverlapBlockingTestByProfile(Pos, Rot, CollisionProfileName, CollisionShape, QueryParams); 272 | } 273 | } 274 | else if (BlockingAnyOrMulti == ECollisionQueryTestBlockingAnyOrMulti::AnyTest) 275 | { 276 | if (By == ECollisionQueryTestBy::Channel) 277 | { 278 | bResult = World->OverlapAnyTestByChannel(Pos, Rot, Channel, CollisionShape, QueryParams, ResponseParams); 279 | } 280 | else if (By == ECollisionQueryTestBy::ObjectType) 281 | { 282 | bResult = World->OverlapAnyTestByObjectType(Pos, Rot, ObjectQueryParams, CollisionShape, QueryParams); 283 | } 284 | else if (By == ECollisionQueryTestBy::Profile) 285 | { 286 | bResult = World->OverlapAnyTestByProfile(Pos, Rot, CollisionProfileName, CollisionShape, QueryParams); 287 | } 288 | } 289 | else if (BlockingAnyOrMulti == ECollisionQueryTestBlockingAnyOrMulti::Multi) 290 | { 291 | if (By == ECollisionQueryTestBy::Channel) 292 | { 293 | bResult = World->OverlapMultiByChannel(Overlaps, Pos, Rot, Channel, CollisionShape, QueryParams, ResponseParams); 294 | } 295 | else if (By == ECollisionQueryTestBy::ObjectType) 296 | { 297 | bResult = World->OverlapMultiByObjectType(Overlaps, Pos, Rot, ObjectQueryParams, CollisionShape, QueryParams); 298 | } 299 | else if (By == ECollisionQueryTestBy::Profile) 300 | { 301 | bResult = World->OverlapMultiByProfile(Overlaps, Pos, Rot, CollisionProfileName, CollisionShape, QueryParams); 302 | } 303 | } 304 | 305 | #if ENABLE_DRAW_DEBUG 306 | DrawDebugCollisionShape(World, Pos, Rot, CollisionShape, bResult ? FColor::Green : FColor::Red, false, 0.f, 0, LineThickness); 307 | #endif // ENABLE_DRAW_DEBUG 308 | } 309 | } 310 | 311 | TArray ACollisionQueryTestActor::GetCollisionProfileOptions() const 312 | { 313 | TArray OutOptions; 314 | 315 | const UCollisionProfile* CollisionProfile = UCollisionProfile::Get(); 316 | const int32 NumProfiles = CollisionProfile->GetNumOfProfiles(); 317 | 318 | OutOptions.Reserve(NumProfiles); 319 | 320 | for (int32 ProfileIdx = 0; ProfileIdx < NumProfiles; ++ProfileIdx) 321 | { 322 | const FCollisionResponseTemplate* ProfileTemplate = CollisionProfile->GetProfileByIndex(ProfileIdx); 323 | OutOptions.Add(ProfileTemplate->Name); 324 | } 325 | 326 | return OutOptions; 327 | } 328 | 329 | EQueryMobilityType ACollisionQueryTestActor::ConvertToQueryMobilityType(ECollisionQueryTestMobilityType TestType) 330 | { 331 | switch (TestType) 332 | { 333 | case ECollisionQueryTestMobilityType::Any: return EQueryMobilityType::Any; 334 | case ECollisionQueryTestMobilityType::Static: return EQueryMobilityType::Static; 335 | case ECollisionQueryTestMobilityType::Dynamic: return EQueryMobilityType::Dynamic; 336 | default: return EQueryMobilityType::Any; 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /Source/CollisionQueryTestActor.h: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Copyright (c) Studio Gobo Ltd 2023 3 | // Licensed under the MIT license. 4 | // See LICENSE.TXT in the project root for license information. 5 | // ---------------------------------------------------------------------------- 6 | // File -> CollisionQueryTestActor.h 7 | // Created -> October 2023 8 | // Author -> George Prosser (Studio Gobo) 9 | 10 | #pragma once 11 | 12 | #include "CoreMinimal.h" 13 | #include "GameFramework/Actor.h" 14 | #include "CollisionQueryParams.h" 15 | 16 | #include "CollisionQueryTestActor.generated.h" 17 | 18 | UENUM() 19 | enum class ECollisionQueryTestType : uint8 20 | { 21 | LineTrace, 22 | Sweep, 23 | Overlap 24 | }; 25 | 26 | UENUM() 27 | enum class ECollisionQueryTestSingleMultiOrTest : uint8 28 | { 29 | Single, 30 | Multi, 31 | Test 32 | }; 33 | 34 | UENUM() 35 | enum class ECollisionQueryTestBlockingAnyOrMulti : uint8 36 | { 37 | BlockingTest, 38 | AnyTest, 39 | Multi 40 | }; 41 | 42 | UENUM() 43 | enum class ECollisionQueryTestShape : uint8 44 | { 45 | Box, 46 | Sphere, 47 | Capsule 48 | }; 49 | 50 | UENUM() 51 | enum class ECollisionQueryTestBy : uint8 52 | { 53 | Channel, 54 | ObjectType, 55 | Profile 56 | }; 57 | 58 | UENUM() 59 | enum class ECollisionQueryTestMobilityType : uint8 60 | { 61 | Any, 62 | Static, 63 | Dynamic 64 | }; 65 | 66 | /** 67 | * Test actor that performs a custom line trace/sweep/overlap test on tick and draws the result. 68 | */ 69 | UCLASS(HideCategories=(Rendering, Replication, Collision, HLOD, Physics, Networking, Input, Actor, Cooking)) 70 | class ACollisionQueryTestActor : public AActor 71 | { 72 | GENERATED_BODY() 73 | 74 | public: 75 | ACollisionQueryTestActor(const FObjectInitializer& ObjectInitializer); 76 | 77 | virtual void Tick(float DeltaSeconds) override; 78 | 79 | 80 | UPROPERTY(EditAnywhere) 81 | ECollisionQueryTestType Query = ECollisionQueryTestType::LineTrace; 82 | 83 | UPROPERTY(EditAnywhere, meta=(EditCondition="Query!=ECollisionQueryTestType::Overlap", EditConditionHides)) 84 | ECollisionQueryTestSingleMultiOrTest SingleMultiOrTest = ECollisionQueryTestSingleMultiOrTest::Single; 85 | 86 | UPROPERTY(EditAnywhere, meta=(EditCondition="Query==ECollisionQueryTestType::Overlap", EditConditionHides)) 87 | ECollisionQueryTestBlockingAnyOrMulti BlockingAnyOrMulti = ECollisionQueryTestBlockingAnyOrMulti::Multi; 88 | 89 | UPROPERTY(EditAnywhere) 90 | ECollisionQueryTestBy By = ECollisionQueryTestBy::Channel; 91 | 92 | 93 | UPROPERTY(EditAnywhere, meta=(EditCondition="Query!=ECollisionQueryTestType::LineTrace", EditConditionHides)) 94 | ECollisionQueryTestShape Shape = ECollisionQueryTestShape::Capsule; 95 | 96 | UPROPERTY(EditAnywhere, meta=(EditCondition="Query!=ECollisionQueryTestType::LineTrace&&Shape==ECollisionQueryTestShape::Box", EditConditionHides)) 97 | FVector BoxHalfExtent = { 25, 25, 50.f }; 98 | 99 | UPROPERTY(EditAnywhere, meta=(EditCondition="Query!=ECollisionQueryTestType::LineTrace&&Shape==ECollisionQueryTestShape::Sphere", EditConditionHides)) 100 | float SphereRadius = 40.f; 101 | 102 | UPROPERTY(EditAnywhere, meta=(EditCondition="Query!=ECollisionQueryTestType::LineTrace&&Shape==ECollisionQueryTestShape::Capsule", EditConditionHides)) 103 | float CapsuleRadius = 40.f; 104 | 105 | UPROPERTY(EditAnywhere, meta=(EditCondition="Query!=ECollisionQueryTestType::LineTrace&&Shape==ECollisionQueryTestShape::Capsule", EditConditionHides)) 106 | float CapsuleHalfHeight = 80.f; 107 | 108 | UPROPERTY(EditAnywhere, meta=(EditCondition="By==ECollisionQueryTestBy::Channel", EditConditionHides)) 109 | TEnumAsByte Channel = ECollisionChannel::ECC_Pawn; 110 | 111 | UPROPERTY(EditAnywhere, meta=(EditCondition="By==ECollisionQueryTestBy::ObjectType", EditConditionHides)) 112 | TEnumAsByte ObjectType = EObjectTypeQuery::ObjectTypeQuery1; 113 | 114 | UPROPERTY(EditAnywhere, meta=(EditCondition="By==ECollisionQueryTestBy::Profile", EditConditionHides, GetOptions="GetCollisionProfileOptions")) 115 | FName CollisionProfileName = TEXT("BlockAll"); 116 | 117 | UPROPERTY(EditAnywhere, meta=(EditCondition="By==ECollisionQueryTestBy::Channel", EditConditionHides)) 118 | FCollisionResponseContainer CollisionResponses; 119 | 120 | 121 | UPROPERTY(EditAnywhere, AdvancedDisplay) 122 | bool bTraceComplex = false; 123 | 124 | UPROPERTY(EditAnywhere, AdvancedDisplay) 125 | bool bFindInitialOverlaps = true; 126 | 127 | UPROPERTY(EditAnywhere, AdvancedDisplay) 128 | bool bIgnoreBlocks = false; 129 | 130 | UPROPERTY(EditAnywhere, AdvancedDisplay) 131 | bool bIgnoreTouches = false; 132 | 133 | UPROPERTY(EditAnywhere, AdvancedDisplay) 134 | bool bSkipNarrowPhase = false; 135 | 136 | UPROPERTY(EditAnywhere, AdvancedDisplay) 137 | ECollisionQueryTestMobilityType MobilityType = ECollisionQueryTestMobilityType::Any; 138 | 139 | 140 | UPROPERTY(VisibleDefaultsOnly) 141 | TObjectPtr StartComponent = nullptr; 142 | 143 | UPROPERTY(VisibleDefaultsOnly) 144 | TObjectPtr EndComponent = nullptr; 145 | 146 | 147 | UFUNCTION() 148 | TArray GetCollisionProfileOptions() const; 149 | 150 | static EQueryMobilityType ConvertToQueryMobilityType(ECollisionQueryTestMobilityType TestType); 151 | 152 | }; --------------------------------------------------------------------------------