라인 트레이스 (Trace) 시스템
이전 절에서 우리는 물리 시뮬레이션과 오브젝트에 힘을 적용하는 방법을 배웠습니다. 이제 직접적인 충돌이나 오버랩 감지 없이도 게임 월드 내의 오브젝트를 "탐색"하고 정보를 얻는 강력한 방법인 라인 트레이스(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
버전이 있습니다.
LineTraceSingleByChannel
(가장 가까운 하나의 히트만 반환)LineTraceMultiByChannel
(경로상의 모든 히트 정보를 배열로 반환)
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" // 디버그 드로잉을 위해
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. 트레이스 시작 지점 결정: 보통 카메라 위치 또는 총구 위치
FVector StartLocation = GetFollowCamera()->GetComponentLocation();
// 또는 ACharacter의 EyeHeight 등을 사용할 수 있습니다.
// FVector StartLocation = GetMesh()->GetSocketLocation(TEXT("MuzzleSocket")); // 총구 소켓이 있다면
// 2. 트레이스 방향 및 종료 지점 결정: 카메라의 정방향으로 트레이스 쏘기
FRotator CameraRotation = GetFollowCamera()->GetComponentRotation();
FVector EndLocation = StartLocation + (CameraRotation.Vector() * TraceDistance);
// 3. 트레이스 파라미터 설정
FCollisionQueryParams TraceParams;
TraceParams.AddIgnoredActor(this); // 트레이스 시 자기 자신(캐릭터)은 무시
TraceParams.bTraceComplex = true; // 복잡한(폴리곤 단위) 충돌 메시를 사용할지 여부 (정확하지만 느림)
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"), *HitResult.Actor->GetName(), *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
: 트레이스의 시작점과 끝점을 정의합니다. 카메라의 위치와 방향을 사용하여 플레이어의 시야 방향으로 트레이스를 쏘는 것이 일반적입니다.FCollisionQueryParams TraceParams
: 트레이스에 대한 추가 설정을 정의합니다.AddIgnoredActor(this)
: 트레이스 시 특정 액터(여기서는 자기 자신)를 무시하여, 자기 자신과 충돌하는 것을 방지합니다.bTraceComplex
:true
로 설정하면 메시의 모든 삼각형과 정확하게 충돌 검사를 수행합니다.false
일 경우 최적화된 단순화된 충돌 메시(Convex Hull, Sphere, Box 등)를 사용합니다. 성능상false
가 좋지만, 정교한 충돌이 필요할 때는true
로 설정합니다.
FHitResult HitResult
: 트레이스 결과가 저장되는 구조체입니다. 충돌이 발생하면 히트한 액터, 컴포넌트, 충돌 위치, 충돌 노멀(법선) 벡터 등 다양한 정보가 채워집니다.World->LineTraceSingleByChannel(...)
: 실제 라인 트레이스를 실행하는 함수입니다.ECollisionChannel::ECC_Visibility
:Project Settings
>Collision
의Trace Channels
에서 정의된Visibility
채널에 대해서만 트레이스를 수행합니다. 이 채널에Block
또는Overlap
반응을 보이는 오브젝트만 감지됩니다.
DrawDebugLine
/DrawDebugSphere
: 개발 및 디버깅 목적으로 뷰포트에 트레이스 선과 히트 지점을 시각적으로 표시해줍니다. 게임 출시 시에는 제거하거나 디버그 빌드에서만 활성화해야 합니다.
충돌 채널과 트레이스
라인 트레이스는 충돌 채널 시스템과 밀접하게 연관되어 있습니다. Project Settings
> Collision
에는 Object Channels
와 Trace Channels
가 있습니다.
Object Channels
: 액터/컴포넌트 자체가 어떤 타입의 오브젝트인지 정의합니다. (예:Pawn
,WorldStatic
,PhysicsBody
)Trace Channels
: 라인 트레이스가 어떤 종류의 가상 선인지를 정의합니다. (예:Visibility
,Camera
)
트레이스를 수행할 때, 트레이스 함수에 지정된 Trace Channel
과, 월드 내 각 오브젝트의 Object Channel
또는 Collision Profile
간의 상호작용 규칙에 따라 충돌 여부가 결정됩니다. 즉, 트레이스 채널이 Block
또는 Overlap
으로 설정된 오브젝트만 감지됩니다.
트레이스 쿼리 타입 (Trace Query Types)
트레이스 함수를 호출하기 전에, 트레이스가 어떤 종류의 오브젝트를 감지할 것인지 설정하는 것이 중요합니다.
ECollisionChannel::ECC_WorldStatic
: 정적 월드 지오메트리만 감지ECollisionChannel::ECC_WorldDynamic
: 동적 월드 오브젝트만 감지ECollisionChannel::ECC_Pawn
: 폰(캐릭터, AI)만 감지ECollisionChannel::ECC_Visibility
:Visibility
채널에 대해Block
또는Overlap
으로 설정된 모든 오브젝트 감지 (가장 일반적인 시야/총알 트레이스)- 커스텀 트레이스 채널:
Project Settings
>Collision
에서 직접 생성한Trace Channels
(예:ECC_GameTraceChannel1
,ECC_GameTraceChannel2
...)
마치며
라인 트레이스 시스템은 언리얼 엔진에서 정교하고 효율적인 월드 쿼리 기능을 제공합니다. 직접적인 물리적 접촉 없이도 원하는 정보를 얻을 수 있으므로, 총알 히트 감지, AI 시야, 상호작용 등 다양한 게임플레이 메커니즘을 구현하는 데 필수적인 도구입니다. 트레이스의 유형(Line, Sphere, Box, Capsule), 채널/오브젝트 타입별 감지, 그리고 결과(FHitResult
) 처리 방법을 숙지하면 강력한 게임플레이 시스템을 구축할 수 있을 것입니다.