라인 트레이스 (Trace) 시스템
시작점·끝점·채널을 지정한 Line Trace로 대상을 검출하고 FHitResult에서 충돌 정보를 읽습니다.
이전 절에서 우리는 물리 시뮬레이션과 오브젝트에 힘을 적용하는 방법을 배웠습니다.
이제 직접적인 충돌이나 오버랩 감지 없이도 게임 월드 내의 오브젝트를 탐색하고 정보를 얻는 강력한 방법인 라인 트레이스(Line Trace) 시스템에 대해 알아보겠습니다.
라인 트레이스는 레이캐스트(Raycast)라고도 불리며, 가상의 선을 쏘아 그 선이 어떤 오브젝트와 부딪혔는지 감지하는 데 사용됩니다.
라인 트레이스란 무엇인가요?
라인 트레이스(Line Trace)는 3D 공간의 시작 지점에서 종료 지점까지 가상의 선(혹은 광선)을 발사하여, 그 선이 월드 내의 어떤 오브젝트와 교차하는지 감지하는 쿼리(Query) 시스템입니다.
충돌 컴포넌트 간의 물리적 접촉이나 겹침을 기다리는 대신, 능동적으로 월드를 "스캔"하여 정보를 얻는 방식입니다.
라인 트레이스는 다음과 같은 다양한 게임플레이 상황에서 활용됩니다.
- 총알/투사체 히트 감지: 발사 순간의 경로를 검사하는 히트스캔 판정에 사용합니다. 실제 투사체의 이동 시간·부피를 재현하지는 않으며, 빠른 투사체는 스윕·연속 충돌 검사 등 별도의 설계가 필요합니다.
- 레이저 시야/타겟팅: 플레이어가 보고 있는 정확한 지점이나 오브젝트를 식별합니다.
- AI 시야: AI 캐릭터가 전방에 장애물이 있는지, 플레이어를 볼 수 있는지 등을 판단합니다.
- 상호작용: 플레이어가 특정 오브젝트(버튼, 문 등)를 바라보고 있을 때만 상호작용 가능하도록 감지합니다.
- 지면 감지: 캐릭터가 점프 후 착지할 때 지면의 특성(재질, 높이)을 파악합니다.
- 벽 타고 움직이기: 캐릭터가 벽에 붙어 있을 때 벽면의 노멀(법선) 벡터를 얻어 벽을 따라 움직이게 합니다.
트레이스의 유형
언리얼 엔진은 다양한 형태의 트레이스 함수를 제공하며, 목적에 따라 적절한 함수를 선택해야 합니다.
- Line Trace (라인 트레이스): 가장 기본적인 형태. 점과 점 사이의 직선을 따라 충돌을 감지합니다.
- Sphere Trace (스피어 트레이스): 점과 점 사이의 경로를 따라 구체를 굴리듯이 충돌을 감지합니다. 총알처럼 얇은 오브젝트가 아닌, 더 넓은 범위의 감지가 필요할 때 유용합니다.
- Box Trace (박스 트레이스): 점과 점 사이의 경로를 따라 직육면체를 움직이듯이 충돌을 감지합니다. 넓은 면적의 장애물 감지에 좋습니다.
- Capsule Trace (캡슐 트레이스): 점과 점 사이의 경로를 따라 캡슐 형태를 움직이듯이 충돌을 감지합니다. 캐릭터 이동 시 장애물이나 지면 감지에 사용하기 좋습니다.
필터는 대표적으로 다음 두 방식으로 선택합니다. 프로파일 기반 함수도 있으므로 사용할 오버로드의 계약을 확인합니다.
- By Channel (채널별 트레이스): 특정 충돌 채널(Collision Channel)에 대해서만 충돌을 감지합니다. (예:
ECC_Visibility또는 커스텀Bullet채널) - By Object Type (오브젝트 타입별 트레이스): 특정 오브젝트 타입(Object Type)에 대해서만 충돌을 감지합니다. (예:
WorldStatic,Pawn,PhysicsBody등)
또한, 단일 히트만 감지하는 Single 버전과 여러 히트를 감지하는 Multi 버전이 있습니다.
함수 계열마다 배열과 반환 bool의 의미가 다릅니다.
| 선택 | 결과 범위 | 읽을 때의 경계 |
|---|---|---|
| Single By Channel | 지정 채널의 첫 Blocking Hit | true는 blocking hit 발견. Overlap만 있는 대상은 이 결과로 얻지 않음 |
| Multi By Channel | 첫 Block까지의 Overlap과 그 Block | Block 뒤를 관통해 모두 수집하지 않음. bool은 Block 유무이므로 false여도 Overlap 배열이 있을 수 있음 |
| Multi By Object Type | 요청한 타입에 속한 쿼리 대상들 | 타입 필터를 명시하며, 채널 Multi의 첫 Block 중단 규칙과 구별 |
- Single By Channel
- 결과 범위: 지정 채널의 첫 Blocking Hit읽을 때의 경계: true는 blocking hit 발견. Overlap만 있는 대상은 이 결과로 얻지 않음
- Multi By Channel
- 결과 범위: 첫 Block까지의 Overlap과 그 Block읽을 때의 경계: Block 뒤를 관통해 모두 수집하지 않음. bool은 Block 유무이므로 false여도 Overlap 배열이 있을 수 있음
- Multi By Object Type
- 결과 범위: 요청한 타입에 속한 쿼리 대상들읽을 때의 경계: 타입 필터를 명시하며, 채널 Multi의 첫 Block 중단 규칙과 구별
아래 예제는 Single By Channel만 호출합니다. 다른 행은 API 계약 비교이며 실제 다중 히트 실행 결과는 아닙니다.
C++에서 라인 트레이스 구현하기
라인 트레이스 함수들은 주로 UWorld 클래스와 UKismetSystemLibrary (블루프린트 함수 라이브러리)에 정의되어 있습니다.
C++에서는 UWorld 포인터를 통해 호출하는 것이 일반적입니다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"
UCLASS()
class MYPROJECT_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMyCharacter();
protected:
virtual void BeginPlay() override;
public:
// 마우스 왼쪽 클릭 시 발사되는 함수 (블루프린트에서 호출 가능하도록)
UFUNCTION(BlueprintCallable, Category = "Combat")
void FireWeapon();
// 트레이스 길이를 에디터에서 조절할 수 있도록 노출
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
float TraceDistance = 10000.0f; // 100미터
};#include "MyCharacter.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/SpringArmComponent.h" // 카메라 암 컴포넌트
#include "Camera/CameraComponent.h" // 카메라 컴포넌트
#include "Kismet/KismetMathLibrary.h" // Rotator To Vector 등을 위해 (선택 사항)
#include "DrawDebugHelpers.h" // 디버그 드로잉을 위해
#include "Engine/World.h"
#include "CollisionQueryParams.h"
AMyCharacter::AMyCharacter()
{
PrimaryActorTick.bCanEverTick = true;
// ... 기존 컴포넌트 설정 (CapsuleComponent, Mesh, SpringArm, Camera 등) ...
}
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
}
void AMyCharacter::FireWeapon()
{
// 월드 포인터 확인
UWorld* World = GetWorld();
if (!World)
{
UE_LOG(LogTemp, Error, TEXT("World is null. Cannot perform trace."));
return;
}
// 1. Pawn의 눈 위치와 시야 방향을 시작 기준으로 사용
FVector StartLocation;
FRotator CameraRotation;
GetActorEyesViewPoint(StartLocation, CameraRotation);
// 또는 ACharacter의 EyeHeight 등을 사용할 수 있습니다.
// FVector StartLocation = GetMesh()->GetSocketLocation(TEXT("MuzzleSocket")); // 총구 소켓이 있다면
// 2. 시야 방향과 사거리로 종료 지점 결정
FVector EndLocation = StartLocation + (CameraRotation.Vector() * TraceDistance);
// 3. 트레이스 파라미터 설정
FCollisionQueryParams TraceParams;
TraceParams.AddIgnoredActor(this); // 트레이스 시 자기 자신(캐릭터)은 무시
TraceParams.bTraceComplex = true; // 복잡 충돌 쿼리 요청; 실제 형상은 에셋의 Collision Complexity에도 따름
TraceParams.bReturnPhysicalMaterial = false; // 물리 재질 정보 반환 여부
// 4. 충돌 결과 정보 구조체
FHitResult HitResult;
// 5. 라인 트레이스 실행!
// LineTraceSingleByChannel(히트 결과, 시작점, 끝점, 충돌 채널, 쿼리 파라미터)
// ECC_Visibility는 Project Settings의 Collision -> Trace Channels에서 기본으로 정의된 채널
bool bHit = World->LineTraceSingleByChannel(HitResult, StartLocation, EndLocation, ECollisionChannel::ECC_Visibility, TraceParams);
// 6. 트레이스 결과 처리
if (bHit)
{
// 뭔가에 맞았다!
UE_LOG(LogTemp, Warning, TEXT("Trace Hit: %s at %s"), *GetNameSafe(HitResult.GetActor()), *HitResult.Location.ToString());
// 히트한 액터, 컴포넌트, 위치, 노멀 벡터 등의 정보에 접근
AActor* HitActor = HitResult.GetActor();
UPrimitiveComponent* HitComponent = HitResult.GetComponent();
FVector HitLocation = HitResult.Location;
FVector HitNormal = HitResult.Normal;
// 여기에 게임플레이 로직 구현 (예: 데미지 적용, 파티클 생성 등)
if (HitActor)
{
// 예시: 맞은 액터에게 데미지 적용 (추후 데미지 시스템 학습)
// UGameplayStatics::ApplyDamage(HitActor, 10.0f, GetController(), this, UDamageType::StaticClass());
}
}
else
{
// 아무것도 맞지 않았다
UE_LOG(LogTemp, Warning, TEXT("Trace Missed."));
}
// 7. 디버그 드로잉 (개발 중에 트레이스 시각화)
DrawDebugLine(World, StartLocation, EndLocation, bHit ? FColor::Red : FColor::Green, false, 5.0f, 0, 3.0f);
if (bHit)
{
DrawDebugSphere(World, HitResult.Location, 25.0f, 12, FColor::Red, false, 5.0f);
}
}FVector StartLocation,FVector EndLocation: 트레이스의 시작점과 끝점을 정의합니다. 이 코드는GetActorEyesViewPoint가 제공하는 Pawn의 눈 위치·시야를 사용합니다. 3인칭 FollowCamera 위치와 같다고 가정하지 않으며, 카메라 기준이 필요하면 실제 카메라 컴포넌트를 연결해야 합니다.FCollisionQueryParams TraceParams: 트레이스에 대한 추가 설정을 정의합니다.AddIgnoredActor(this): 트레이스 시 특정 액터(여기서는 자기 자신)를 무시하여, 자기 자신과 충돌하는 것을 방지합니다.bTraceComplex: 복잡 충돌을 사용할지 요청합니다. 단순·복잡 형상 선택은 에셋의Collision Complexity설정에도 영향을 받으며 비용과 적합성은 대상 형상에 따라 확인합니다.
FHitResult HitResult: 트레이스 결과가 저장되는 구조체입니다. 충돌이 발생하면 히트한 액터, 컴포넌트, 충돌 위치, 충돌 노멀(법선) 벡터 등 다양한 정보가 채워집니다.World->LineTraceSingleByChannel(...): 실제 라인 트레이스를 실행하는 함수입니다.ECollisionChannel::ECC_Visibility:Project Settings>Collision의Trace Channels에서 정의된Visibility채널에 대해서만 트레이스를 수행합니다. 이 Single 호출은 해당 채널에 대한 첫Block결과를 반환합니다. Overlap 결과 배열이 필요한 경우 Multi 계약을 사용합니다.
DrawDebugLine/DrawDebugSphere: 개발 및 디버깅 목적으로 뷰포트에 트레이스 선과 히트 지점을 시각적으로 표시해줍니다. 게임 출시 시에는 제거하거나 디버그 빌드에서만 활성화해야 합니다.
충돌 채널과 트레이스
라인 트레이스는 충돌 채널 시스템과 밀접하게 연관되어 있습니다.
Project Settings > Collision 에는 Object Channels와 Trace Channels가 있습니다.
Object Channels: 액터/컴포넌트 자체가 어떤 타입의 오브젝트인지 정의합니다. (예:Pawn,WorldStatic,PhysicsBody)Trace Channels: 라인 트레이스가 어떤 종류의 가상 선인지를 정의합니다. (예:Visibility,Camera)
트레이스를 수행할 때, 트레이스 함수에 지정된 Trace Channel과, 월드 내 각 오브젝트의 Object Channel 또는 Collision Profile 간의 상호작용 규칙에 따라 충돌 여부가 결정됩니다.
채널의 응답과 Single/Multi 함수의 반환 계약을 함께 해석해야 합니다.
트레이스 쿼리 타입 (Trace Query Types)
트레이스 함수를 호출하기 전에, 트레이스가 어떤 종류의 오브젝트를 감지할 것인지 설정하는 것이 중요합니다.
LineTrace…ByChannel은 대상의 지정 채널에 대한 응답을 검사합니다.Visibility,Camera또는 커스텀 트레이스 채널을 목적에 맞게 고릅니다.WorldStatic,WorldDynamic,Pawn같은 오브젝트 타입만 찾으려면FCollisionObjectQueryParams에 타입을 넣고LineTrace…ByObjectType을 사용합니다. 같은 enum 값을 ByChannel 인자에 넣는 것만으로 타입 필터가 되지는 않습니다.
라인 트레이스 시스템은 언리얼 엔진에서 정교하고 효율적인 월드 쿼리 기능을 제공합니다.
직접적인 물리적 접촉 없이도 원하는 정보를 얻을 수 있으므로, 총알 히트 감지, AI 시야, 상호작용 등 다양한 게임플레이 메커니즘을 구현하는 데 필수적인 도구입니다.
트레이스의 유형(Line, Sphere, Box, Capsule), 채널/오브젝트 타입별 감지, 그리고 결과(FHitResult) 처리 방법을 숙지하면 강력한 게임플레이 시스템을 구축할 수 있을 것입니다.