1. 바인딩을 하는 이유
핵심은 이벤트 기반 프로그래밍(Event-Driven Programming)과 옵저버 패턴(Observer Pattern)의 구현에 있다.
A. 성능 최적화 (Tick 최소화)
가장 큰 이유는 성능이다. 만약 바인딩을 사용하지 않는다면, 개발자는 매 프레임 실행되는 Tick() 함수에서 충돌 여부를 검사해야 한.
- Polling (비효율적):
Tick에서 "나 지금 충돌했니?"라고 매번 물어보는 방식입니다. 충돌이 발생하지 않는 99%의 프레임에서도 연산이 낭비된다. - Event (효율적): 물리 엔진(Chaos Physics)이 충돌을 감지했을 때만 등록된 함수(델리게이트)를 호출한다. 즉, 비용이 0에 가깝다가 실제 충돌 시에만 코드가 실행.
B. 결합도 감소 (Decoupling)
물리 엔진은 충돌 사실만 알 뿐, 그 이후에 체력을 깎을지, 사운드를 재생할지 알 필요가 없다. 델리게이트를 통해 물리 시스템과 게임플레이 로직을 분리함으로써, 코드의 재사용성과 모듈성을 높인다.
2. 작동 원리 및 내부 구조
UE5의 충돌 바인딩은 델리게이트(Delegate), 특히 다이내믹 멀티캐스트 델리게이트(Dynamic Multicast Delegate)를 통해 작동한다.
A. 핵심 메커니즘: 옵저버 패턴
- Subject (주체):
UPrimitiveComponent(예:UCapsuleComponent,UBoxComponent)는 충돌 이벤트 목록(OnComponentHit,OnComponentBeginOverlap등)을 가지고 있는다. - Observer (관찰자): 작성한 C++ 클래스와 함수.
- Binding (구독):
AddDynamic매크로를 통해 내 함수를 컴포넌트의 이벤트 목록에 등록하는 과정.
B. 논리 흐름도

C. 리플렉션 시스템과 UFUNCTION
UE5의 다이내믹 델리게이트는 함수 포인터를 직접 저장하는 것이 아니라, 함수의 이름(String)을 통해 리플렉션 시스템에서 해당 함수를 찾아 실행한다.
- 필수 조건: 바인딩할 함수는 반드시
UFUNCTION()매크로가 붙어있어야 한다. 그렇지 않으면 엔진이 런타임에 함수를 찾지 못해 크래시가 나거나 호출되지 않음.
3. C++ 구현 가이드
가장 많이 사용되는 OnComponentBeginOverlap을 예시로, 표준 C++ 구현.
헤더 파일 (.h)
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class MYGAME_API AMyActor : public AActor
{
GENERATED_BODY()
public:
AMyActor();
protected:
virtual void BeginPlay() override;
// 충돌 컴포넌트
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
class UBoxComponent* CollisionBox;
// [중요] 델리게이트에 바인딩할 함수는 반드시 UFUNCTION이어야 함.
// 시그니처(매개변수)는 FComponentBeginOverlapSignature와 정확히 일치해야 함.
UFUNCTION()
void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult);
};
소스 파일 (.cpp)
#include "MyActor.h"
#include "Components/BoxComponent.h"
AMyActor::AMyActor()
{
CollisionBox = CreateDefaultSubobject<UBoxComponent>(TEXT("CollisionBox"));
RootComponent = CollisionBox;
// [Tip] 생성자에서 바인딩을 할 수도 있지만,
// 동적 생성 객체나 레벨 로딩 순서를 고려할 때 BeginPlay가 안전한 경우가 많다.
}
void AMyActor::BeginPlay()
{
Super::BeginPlay();
// [방어 코드] 컴포넌트 유효성 검사
if (CollisionBox)
{
// 델리게이트에 함수 등록 (바인딩)
CollisionBox->OnComponentBeginOverlap.AddDynamic(this, &AMyActor::OnOverlapBegin);
}
}
void AMyActor::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult)
{
// 자기 자신과의 충돌이나 Nullptr 체크는 필수.
if (OtherActor && (OtherActor != this) && OtherComp)
{
UE_LOG(LogTemp, Warning, TEXT("Overlap Detected with: %s"), *OtherActor->GetName());
// 여기에 데미지 처리 등의 로직 구현
}
}
4. 팁 및 주의사항
1) 시그니처 불일치 주의 (Rider 활용)
바인딩할 함수의 매개변수가 하나라도 틀리면 컴파일 에러가 발생한다.
- Rider Tip:
OnComponentBeginOverlap에 마우스를 올리거나F12(Go to Definition)를 누르면FComponentBeginOverlapSignature선언부로 이동하여 정확한 인자 목록을 복사해올 수 있습니다. Rider는 종종 이를 자동으로 생성해 주는 퀵 픽스(Alt+Enter)도 제공한다.
2) AddDynamic vs AddUniqueDynamic
AddDynamic: 단순히 함수를 추가. 실수로 두 번 호출하면 이벤트가 두 번 발생할 수 있다.AddUniqueDynamic: 이미 등록된 함수인지 확인하고, 없을 때만 등록한다. 안전성을 위해 이 방식을 권장하는 경우가 많다.
3) 바인딩 해제 (Unbinding)
액터가 소멸될 때 엔진이 자동으로 정리해주긴 하지만, 런타임 중에 특정 조건(예: 무적 상태)에서만 충돌을 끄고 싶다면 바인딩을 해제해야 한다.
CollisionBox->OnComponentBeginOverlap.RemoveDynamic(this, &AMyActor::OnOverlapBegin);
또는 SetCollisionEnabled(ECollisionEnabled::NoCollision)을 사용하는 것이 더 효율적일 수 있다.
4) 하이브리드 아키텍처 (C++ & Blueprint)
C++에서는 핵심 로직(데미지 계산, 점수 획득)만 처리하고, 시각적 효과(파티클, 사운드)는 블루프린트에서 처리하는 것이 좋다.
// C++에서 BlueprintImplementableEvent 선언
UFUNCTION(BlueprintImplementableEvent, Category="Combat")
void OnHitVFX(FVector Location);
void AMyActor::OnOverlapBegin(...) {
// C++ 로직
ApplyDamage();
// BP 로직 호출
OnHitVFX(OtherActor->GetActorLocation());
}
'프로그래밍 > Unreal Engine 5' 카테고리의 다른 글
| [Unreal Engine 5] UFUNCTION()와 지정자(Specfier)들 (0) | 2026.01.29 |
|---|---|
| [Unreal Engine 5] GAS(Gameplay Ability System) (0) | 2026.01.26 |
| [Unreal Engine 5] TSubclassOf<T> (0) | 2026.01.16 |
| [Unreal Engine 5] UPROPERTY()와 지정자(Specifier)들 (0) | 2026.01.15 |
| [Unreal Engine 5] 액터의 라이프 사이클 (2) | 2026.01.09 |