트리거 박스와 오버랩 이벤트
BoxComponent에 Begin·End Overlap 이벤트를 연결해 대상 진입과 이탈에 반응하는 트리거를 구현합니다.
이전 절에서는 언리얼 엔진의 충돌 시스템 기초인 충돌 채널과 프로파일 설정에 대해 알아보았습니다.
이제 이러한 충돌 시스템을 활용하여 게임플레이에서 빈번하게 사용되는 상호작용 방식인 트리거(Trigger)와 오버랩 이벤트(Overlap Event)에 대해 자세히 살펴보겠습니다.
특히, 특정 영역에 진입했을 때 이벤트를 발생시키는 데 유용한 트리거 박스(Trigger Box)의 활용법에 초점을 맞출 것입니다.
트리거(Trigger)란?
트리거는 게임 월드 내의 특정 영역(Volume)으로, 플레이어나 다른 액터가 이 영역에 진입하거나 이탈했을 때 미리 정의된 게임플레이 이벤트를 발생시키는 역할을 합니다.
트리거는 물리적인 블록킹(Blocking)을 일으키지 않고, 단지 감지(Query)만을 수행한다는 점에서 일반적인 충돌 오브젝트와 차이가 있습니다.
트리거는 다음과 같은 다양한 상황에서 활용됩니다.
- 아이템 획득: 플레이어가 아이템 위에 서면 아이템을 획득하게 하고, 획득한 아이템을 사라지게 합니다.
- 문 열림: 플레이어가 문 앞의 특정 영역에 진입하면 문이 자동으로 열리게 합니다.
- 지역 감지: 플레이어가 새로운 지역에 진입하면 해당 지역의 이름이 화면에 표시되거나, 배경 음악이 바뀌게 합니다.
- 컷씬 발동: 특정 지점에 도달하면 시네마틱 컷씬이 시작되게 합니다.
- 체크포인트: 플레이어가 지나가면 체크포인트가 활성화되게 합니다.
트리거 박스 (Trigger Box) 컴포넌트
언리얼 엔진에서 가장 흔하게 사용되는 트리거 형태는 UBoxComponent를 기반으로 한 트리거 볼륨(Trigger Volume)입니다.
UBoxComponent는 직육면체 형태의 충돌 감지 영역을 제공하며, 이를 트리거 용도로 설정할 수 있습니다.
트리거 박스의 특징
- 시각적 표현 없음: 기본적으로 렌더링되지 않으므로, 게임 내에서는 보이지 않습니다. 에디터에서만 와이어프레임 형태로 표시되어 배치와 크기 조절을 돕습니다.
- 물리적 상호작용 없음:
QueryOnly는 쿼리 참여 설정이고OverlapAll은 프리셋입니다. 트리거는 대상 채널 응답을Overlap으로 맞춰 막힘 없이 감지하도록 구성합니다. - 오버랩 이벤트 발생: 다른 충돌 컴포넌트와 겹쳤을 때
OnComponentBeginOverlap및OnComponentEndOverlap이벤트를 발생시킵니다.
C++에서 트리거 박스 설정하기
이전 절에서 다룬 AMyTriggerVolume 액터 클래스를 다시 살펴보겠습니다.
이 액터는 UBoxComponent를 사용하여 트리거 박스를 구현하는 전형적인 예시입니다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyTriggerVolume.generated.h"
class UBoxComponent; // UBoxComponent를 미리 선언
UCLASS()
class MYPROJECT_API AMyTriggerVolume : public AActor
{
GENERATED_BODY()
public:
AMyTriggerVolume(); // 생성자
protected:
virtual void BeginPlay() override; // 액터 초기화 시 호출
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Collision")
UBoxComponent* TriggerBox; // 트리거 박스 컴포넌트 포인터
// 오버랩 시작 이벤트에 바인딩될 함수
// UFUNCTION 매크로 필수: 델리게이트에 바인딩되기 위해 리플렉션 시스템에 노출되어야 함
UFUNCTION()
void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
// 오버랩 종료 이벤트에 바인딩될 함수
UFUNCTION()
void OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
};#include "MyTriggerVolume.h"
#include "Components/BoxComponent.h" // UBoxComponent 헤더 포함
#include "GameFramework/Pawn.h" // APawn과 제어 상태 검사
AMyTriggerVolume::AMyTriggerVolume()
{
// 액터가 매 프레임 업데이트될 필요가 없다면 틱 비활성화
PrimaryActorTick.bCanEverTick = false;
// UBoxComponent 생성
TriggerBox = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerBox"));
RootComponent = TriggerBox; // 트리거 박스를 액터의 루트 컴포넌트로 설정
// 충돌 설정: 오버랩 전용으로 설정
// Project Settings의 Collision Preset에 "TriggerOnly"와 같은 프로파일이 없다면
// 기본 OverlapAllDynamic을 사용하거나 직접 커스텀 설정을 할 수 있습니다.
TriggerBox->SetCollisionProfileName(TEXT("OverlapAllDynamic"));
// TriggerBox->SetCollisionEnabled(ECollisionEnabled::QueryOnly); // 쿼리만 가능, 물리 없음
// TriggerBox->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic); // 이 트리거의 오브젝트 타입
// 중요: 오버랩 이벤트를 발생시키도록 설정
TriggerBox->SetGenerateOverlapEvents(true);
// (선택 사항) 트리거 박스의 크기 설정
TriggerBox->SetBoxExtent(FVector(100.0f, 100.0f, 100.0f)); // 각 축 반길이 100 cm: 스케일 1에서 전체 크기 200x200x200 cm
}
void AMyTriggerVolume::BeginPlay()
{
Super::BeginPlay();
// 오버랩 이벤트를 C++ 함수에 바인딩
// OnComponentBeginOverlap 델리게이트에 OnOverlapBegin 함수를 연결합니다.
TriggerBox->OnComponentBeginOverlap.AddDynamic(this, &AMyTriggerVolume::OnOverlapBegin);
TriggerBox->OnComponentEndOverlap.AddDynamic(this, &AMyTriggerVolume::OnOverlapEnd);
}
void AMyTriggerVolume::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
// 오버랩된 액터가 유효하고, 자기 자신이 아니며, 플레이어 폰인지 확인하는 것이 일반적입니다.
// 플레이어 폰인지 확인하는 예시:
if (OtherActor && OtherActor != this && OtherActor->IsA(APawn::StaticClass()))
{
APawn* PlayerPawn = Cast<APawn>(OtherActor); // Pawn으로 캐스팅; 아래에서 플레이어 제어 여부 확인
if (PlayerPawn && PlayerPawn->IsPlayerControlled() && PlayerPawn->IsLocallyControlled()) // 로컬 플레이어인지 확인
{
UE_LOG(LogTemp, Warning, TEXT("Local Player entered: %s"), *PlayerPawn->GetName());
// 여기에 플레이어가 트리거에 진입했을 때 실행할 게임플레이 로직을 구현합니다.
// 예: "아이템 획득!" 메시지 표시, 문 열기 함수 호출 등.
}
}
}
void AMyTriggerVolume::OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor && OtherActor != this && OtherActor->IsA(APawn::StaticClass()))
{
APawn* PlayerPawn = Cast<APawn>(OtherActor);
if (PlayerPawn && PlayerPawn->IsPlayerControlled() && PlayerPawn->IsLocallyControlled())
{
UE_LOG(LogTemp, Warning, TEXT("Local Player exited: %s"), *PlayerPawn->GetName());
// 여기에 플레이어가 트리거에서 이탈했을 때 실행할 로직을 구현합니다.
// 예: "아이템 획득 가능" 메시지 숨기기 등.
}
}
}SetCollisionProfileName(TEXT("OverlapAllDynamic")): 기본 정의에서는 자신의 타입을WorldDynamic으로 두고 다른 채널에 Overlap으로 응답합니다. 이름의 Dynamic이 감지 대상을 동적 오브젝트로만 제한하는 것은 아닙니다. 프로젝트 설정에서 더 세분화된 커스텀 프로파일을 만들었다면 해당 이름을 사용하세요.SetGenerateOverlapEvents(true): 실제로 겹치는 양쪽 컴포넌트에서 활성화해야 합니다. 쿼리 참여와 상호 응답도 맞아야 합니다.OnComponentBeginOverlap.AddDynamic(this, &AMyTriggerVolume::OnOverlapBegin):AddDynamic함수는 언리얼 엔진의 델리게이트(Delegate) 시스템에서 이벤트에 함수를 바인딩하는 데 사용됩니다.OnComponentBeginOverlap은UPrimitiveComponent가 제공하는 멀티캐스트 델리게이트로, 오버랩이 시작될 때 호출됩니다.- 첫 번째 인자
this는 이벤트를 받을 오브젝트(액터)의 포인터입니다. - 두 번째 인자
&AMyTriggerVolume::OnOverlapBegin은 바인딩할 멤버 함수의 주소입니다. 이 함수는 반드시UFUNCTION()으로 선언되어야 하며, 델리게이트의 시그니처와 일치하는 파라미터를 가져야 합니다.
이 예제는 BeginPlay에서 구독한 이후의 콜백을 기록합니다. 시작 시 이미 겹친 대상을 상태에 포함해야 한다면 구독과 별도로 현재 겹침 목록을 조회해 초기화해야 합니다.
오버랩 이벤트의 파라미터 이해
OnOverlapBegin (그리고 OnOverlapEnd) 함수가 받는 파라미터들은 오버랩에 대한 중요한 정보를 제공합니다.
UPrimitiveComponent* OverlappedComp: 오버랩을 발생시킨 이 컴포넌트(즉,TriggerBox자체).AActor* OtherActor: 이 트리거 박스와 겹친 다른 액터의 포인터.UPrimitiveComponent* OtherComp:OtherActor내에서OverlappedComp와 실제로 겹친 다른 컴포넌트의 포인터. (예: 플레이어 캐릭터의CapsuleComponent)int32 OtherBodyIndex: 상대 컴포넌트에서 겹친 물리 바디의 인덱스이며, 일반적인 본 인덱스로 단정하지 않습니다.bool bFromSweep: 스윕(Sweep) 검사로 오버랩이 발생했는지 여부. (속도 자체를 뜻하는 플래그는 아닙니다)const FHitResult& SweepResult:bFromSweep이true일 경우, 스윕 검사의 자세한 결과.
이 파라미터들을 통해 우리는 어떤 액터의 어떤 컴포넌트가 우리 트리거 박스와 상호작용했는지 정확히 파악하고, 그에 따른 맞춤형 로직을 구현할 수 있습니다.
블루프린트에서 트리거 박스 및 이벤트 활용
C++로 AMyTriggerVolume을 만들었다면, 언리얼 에디터에서 이를 기반으로 한 블루프린트 클래스(BP_MyTriggerVolume)를 생성하여 레벨에 배치할 수 있습니다.
배치 및 크기 조절: BP_MyTriggerVolume 인스턴스를 레벨에 드래그하여 배치하고, 디테일 패널에서 TriggerBox 컴포넌트의 Box Extent를 조절하여 원하는 트리거 영역 크기로 만듭니다.
이벤트 그래프에서 활용: BP_MyTriggerVolume 블루프린트 에디터를 열고, Event Graph에서 TriggerBox 컴포넌트를 선택한 후, 디테일 패널 하단에 있는 이벤트 목록에서 OnComponentBeginOverlap과 OnComponentEndOverlap 이벤트를 추가할 수 있습니다.
이 이벤트 노드들에서 Other Actor 핀을 뽑아 Cast To ThirdPersonCharacter (혹은 여러분의 캐릭터 클래스)와 같은 노드로 클래스 타입을 확인한 뒤, 플레이어만 처리한다면 Is Player Controlled 등의 조건도 확인합니다. 원하는 블루프린트 로직을 연결하되 같은 동작을 C++ 콜백과 이중 실행하지 않도록 합니다.
예시: 한 Pawn의 두 컴포넌트가 겹친 경우
이 예제의 필터를 통과하는 같은 로컬 플레이어 Pawn에서 캡슐 C와 메시 M이 각각 겹친다고 가정합니다. Begin(C), Begin(M), End(C), End(M) 순서의 콜백이 오면 진입 로그와 이탈 로그가 각각 두 번 남을 수 있습니다. 하지만 End(C) 직후에도 M이 겹쳐 있으므로 플레이어가 완전히 나갔다고 읽으면 UI를 너무 일찍 숨길 수 있습니다.
게임 규칙이 캡슐만 감지하는 것이라면 OtherComp를 그 대상에 한정해 단순하게 만들 수 있습니다. 여러 부품을 허용해야 한다면 액터마다 허용한 컴포넌트·바디의 키 집합을 보관하고, 집합이 비었는지로 점유를 판단하는 방식이 있습니다. 동일한 시작 키를 중복 추가하지 않고, 알고 있는 종료 키만 제거합니다. 파괴·언로드나 필터 변경 뒤의 상태 정리와 시작 시 이미 겹친 대상의 초기화도 함께 설계해야 합니다. 이 집합 정책은 원 로그 예제에 이미 구현되어 있는 기능이 아닙니다.
현재 코드의 IsPlayerControlled와 IsLocallyControlled는 이 실행 인스턴스의 로컬 플레이어를 고르는 조건입니다. 서버에서 모든 접속자의 획득을 확정하는 권한 검사나 원격 클라이언트 동기화까지 구현한 것으로 읽지 않습니다.
점유 상태를 직접 따라가기
처음 집합은 비어 있고 C와 M은 서로 다른 허용 키입니다. Begin(C), Begin(C), Begin(M), End(C) 순서가 왔습니다. 같은 키 중복 추가를 막는 정책에서 마지막 집합과 플레이어 점유 상태는 무엇일까요? 영역 안의 Pawn이 AI라서 원 코드의 제어 필터를 통과하지 못하는 경우도 구별하세요.
콜백 수와 키 집합을 나눈 풀이
집합은 {C}, {C}, {C, M}, {M} 순서이므로 마지막에도 점유 중입니다. Begin 콜백 횟수를 그대로 사람 수로 세면 같은 C를 중복 계산합니다. AI가 필터를 통과하지 못하면 원 코드의 로컬 플레이어 진입 로그는 남지 않지만, 기하학적으로 겹치지 않았다는 뜻은 아닙니다. 필터가 받아들이는 대상과 실제 겹침을 구별합니다.
겹침 콜백 기록과 액터 점유 상태는 다릅니다
예시 BeginC→BeginM→EndC→EndM에서 {}→{C}→{C,M}→{M}→{}가 되어 EndC 뒤 점유가 유지되는 구체 상태이다.
UNREAL C++ · CH5-2
겹침 콜백 기록과 액터 점유 상태는 다릅니다
본문 코드는 플레이어 제어·로컬 제어 Pawn만 진입과 이탈을 로그로 남깁니다. 영역 안에 머무는 액터의 상태를 유지하려면 추가 정책이 필요합니다.
C/M은 같은 Pawn의 서로 다른 허용 컴포넌트·바디 키입니다. 집합의 빈 상태가 액터 점유 해제 조건이며, 중복 시작 키·없는 종료 키·파괴와 초기 상태 정리는 별도 정책으로 처리합니다.
| 받은 사건 | 예제의 처리 | 상태를 유지할 때의 판단 |
|---|---|---|
| BeginOverlap | 로컬 플레이어 Pawn이면 진입 로그 | 캡슐·메시 등 여러 컴포넌트가 겹칠 수 있어 처리 대상 부품을 정함 |
| EndOverlap | 같은 필터를 통과하면 이탈 로그 | 한 컴포넌트의 이탈을 액터 전체의 퇴장으로 단정하지 않음 |
| 영역에 머무름 | 별도 지속 처리 없음 | 필요할 때만 점유 집합이나 타이머로 유지 효과를 관리 |
- BeginOverlap
- 예제의 처리: 로컬 플레이어 Pawn이면 진입 로그상태를 유지할 때의 판단: 캡슐·메시 등 여러 컴포넌트가 겹칠 수 있어 처리 대상 부품을 정함
- EndOverlap
- 예제의 처리: 같은 필터를 통과하면 이탈 로그상태를 유지할 때의 판단: 한 컴포넌트의 이탈을 액터 전체의 퇴장으로 단정하지 않음
- 영역에 머무름
- 예제의 처리: 별도 지속 처리 없음상태를 유지할 때의 판단: 필요할 때만 점유 집합이나 타이머로 유지 효과를 관리
컴포넌트의 콜백 호출 기록을 액터의 단 한 번 진입·퇴장이나 UI 상태 복구로 읽지 않습니다. BeginPlay 구독 전에 이미 겹친 대상을 포함하려면 현재 겹침 목록을 별도로 조회해 초기화합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 본문·주석 | 본문 코드는 플레이어 제어·로컬 제어 Pawn의 진입과 이탈만 로그로 남깁니다. 영역 안에 머무는 액터의 상태를 유지하려면 추가 정책이 필요합니다. |
| 관계·조건 설명 | 같은 허용 Pawn의 캡슐 C와 메시 M이 겹치는 사건 순서 하나를 예로 든다. 비어 있는 집합에서 BeginC, BeginM, EndC, EndM을 받으면 집합은 C, C와 M, M, 비어 있음으로 바뀐다. EndC 뒤에도 M이 남으므로 액터 점유는 유지된다. 실제 모든 콜백의 전역 순서를 보장하거나 원래 로그 예제에 집합이 구현됐다는 뜻은 아니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 두 Begin C와 M 컴포넌트의 겹침이 차례로 등록된다. |
|
|
| EndC C만 나가도 M은 영역에 남는다. |
|
|
| EndM 마지막 컴포넌트가 나가 점유 집합이 빈다. |
|
|
겹침 콜백 기록과 액터 점유 상태는 다릅니다
예시 BeginC→BeginM→EndC→EndM에서 {}→{C}→{C,M}→{M}→{}가 되어 EndC 뒤 점유가 유지되는 구체 상태이다.
UNREAL C++ · CH5-2
겹침 콜백 기록과 액터 점유 상태는 다릅니다
본문 코드는 플레이어 제어·로컬 제어 Pawn만 진입과 이탈을 로그로 남깁니다. 영역 안에 머무는 액터의 상태를 유지하려면 추가 정책이 필요합니다.
C/M은 같은 Pawn의 서로 다른 허용 컴포넌트·바디 키입니다. 집합의 빈 상태가 액터 점유 해제 조건이며, 중복 시작 키·없는 종료 키·파괴와 초기 상태 정리는 별도 정책으로 처리합니다.
| 받은 사건 | 예제의 처리 | 상태를 유지할 때의 판단 |
|---|---|---|
| BeginOverlap | 로컬 플레이어 Pawn이면 진입 로그 | 캡슐·메시 등 여러 컴포넌트가 겹칠 수 있어 처리 대상 부품을 정함 |
| EndOverlap | 같은 필터를 통과하면 이탈 로그 | 한 컴포넌트의 이탈을 액터 전체의 퇴장으로 단정하지 않음 |
| 영역에 머무름 | 별도 지속 처리 없음 | 필요할 때만 점유 집합이나 타이머로 유지 효과를 관리 |
- BeginOverlap
- 예제의 처리: 로컬 플레이어 Pawn이면 진입 로그상태를 유지할 때의 판단: 캡슐·메시 등 여러 컴포넌트가 겹칠 수 있어 처리 대상 부품을 정함
- EndOverlap
- 예제의 처리: 같은 필터를 통과하면 이탈 로그상태를 유지할 때의 판단: 한 컴포넌트의 이탈을 액터 전체의 퇴장으로 단정하지 않음
- 영역에 머무름
- 예제의 처리: 별도 지속 처리 없음상태를 유지할 때의 판단: 필요할 때만 점유 집합이나 타이머로 유지 효과를 관리
컴포넌트의 콜백 호출 기록을 액터의 단 한 번 진입·퇴장이나 UI 상태 복구로 읽지 않습니다. BeginPlay 구독 전에 이미 겹친 대상을 포함하려면 현재 겹침 목록을 별도로 조회해 초기화합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 본문·주석 | 본문 코드는 플레이어 제어·로컬 제어 Pawn의 진입과 이탈만 로그로 남깁니다. 영역 안에 머무는 액터의 상태를 유지하려면 추가 정책이 필요합니다. |
| 관계·조건 설명 | 같은 허용 Pawn의 캡슐 C와 메시 M이 겹치는 사건 순서 하나를 예로 든다. 비어 있는 집합에서 BeginC, BeginM, EndC, EndM을 받으면 집합은 C, C와 M, M, 비어 있음으로 바뀐다. EndC 뒤에도 M이 남으므로 액터 점유는 유지된다. 실제 모든 콜백의 전역 순서를 보장하거나 원래 로그 예제에 집합이 구현됐다는 뜻은 아니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 두 Begin C와 M 컴포넌트의 겹침이 차례로 등록된다. |
|
|
| EndC C만 나가도 M은 영역에 남는다. |
|
|
| EndM 마지막 컴포넌트가 나가 점유 집합이 빈다. |
|
|
트리거 박스와 오버랩 이벤트는 언리얼 엔진에서 비물리적인 게임플레이 상호작용을 구현하는 데 있어 가장 기본적인 도구입니다.
이들을 통해 아이템 획득, 구역 진입/이탈 감지, 이벤트 발동 등 다양한 기능을 효율적으로 구현할 수 있습니다.
충돌 채널과 프로파일 설정을 통해 원하는 액터들 사이에서만 오버랩이 발생하도록 정교하게 제어할 수 있다는 점을 기억하는 것이 중요합니다.