델리게이트 시스템
이벤트 기반 프로그래밍과 느슨한 결합 구현하기
델리게이트 개요
타입 안전한 함수 포인터
델리게이트는 C++ 함수 포인터의 타입 안전한 버전으로, 느슨한 결합(Loose Coupling)을 통해 객체 간 통신을 가능하게 합니다.
| 타입 | 바인딩 수 | Dynamic | 사용 케이스 |
|---|---|---|---|
| DECLARE_DELEGATE | 1개 | X | C++ 내부 콜백 |
| DECLARE_MULTICAST_DELEGATE | N개 | X | C++ 다중 리스너 |
| DECLARE_DYNAMIC_DELEGATE | 1개 | O | BP 노출 (단일) |
| DECLARE_DYNAMIC_MULTICAST_DELEGATE | N개 | O | BP 이벤트 디스패처 |
Dynamic: 리플렉션 사용, Blueprint 노출 가능, 약간 느림
Non-Dynamic: C++ 전용, 더 빠름, Blueprint에서 사용 불가
단일 델리게이트
하나의 함수만 바인딩
// 델리게이트 선언 (보통 헤더 상단에)
DECLARE_DELEGATE(FSimpleDelegate);
DECLARE_DELEGATE_OneParam(FOnHealthChanged, float /* NewHealth */);
DECLARE_DELEGATE_TwoParams(FOnDamageReceived, float, AActor*);
DECLARE_DELEGATE_RetVal(bool, FCanActivate);
DECLARE_DELEGATE_RetVal_OneParam(int32, FCalculateBonus, float);
// 클래스에서 사용
class UHealthComponent : public UActorComponent
{
GENERATED_BODY()
public:
// 델리게이트 멤버 변수
FOnHealthChanged OnHealthChanged;
FCanActivate CanActivateCheck;
};
// 바인딩 및 실행
void AMyCharacter::SetupHealth()
{
// UObject 멤버 함수 바인딩
HealthComp->OnHealthChanged.BindUObject(
this, &AMyCharacter::HandleHealthChanged);
// 반환값 있는 델리게이트
HealthComp->CanActivateCheck.BindUObject(
this, &AMyCharacter::CheckCanActivate);
}
void UHealthComponent::TakeDamage(float Damage)
{
Health -= Damage;
// 안전하게 실행 (바인딩 없으면 아무것도 안 함)
OnHealthChanged.ExecuteIfBound(Health);
// 반환값 확인
if (CanActivateCheck.IsBound())
{
bool bCanActivate = CanActivateCheck.Execute();
}
}
멀티캐스트 델리게이트
여러 함수를 동시에 바인딩
// 멀티캐스트 델리게이트 선언
DECLARE_MULTICAST_DELEGATE(FOnGameStarted);
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnScoreChanged, int32, APlayerState*);
class AMyGameMode : public AGameModeBase
{
public:
FOnGameStarted OnGameStarted;
FOnScoreChanged OnScoreChanged;
};
// 여러 곳에서 바인딩
void AMyHUD::BeginPlay()
{
Super::BeginPlay();
if (AMyGameMode* GM = GetWorld()->GetAuthGameMode<AMyGameMode>())
{
// AddUObject로 다중 바인딩
GM->OnScoreChanged.AddUObject(this, &AMyHUD::UpdateScoreDisplay);
}
}
void UAchievementManager::Initialize(AMyGameMode* GM)
{
// 같은 델리게이트에 또 다른 리스너 추가
GM->OnScoreChanged.AddUObject(this, &UAchievementManager::CheckScoreAchievements);
}
// Broadcast로 모든 리스너에게 알림
void AMyGameMode::AddScore(APlayerState* Player, int32 Score)
{
TotalScore += Score;
OnScoreChanged.Broadcast(TotalScore, Player); // 모든 바인딩 호출
}
핸들 기반 관리
// 핸들로 특정 바인딩 관리
FDelegateHandle ScoreHandle;
void AMyHUD::RegisterEvents()
{
if (AMyGameMode* GM = GetGameMode())
{
// 핸들 저장
ScoreHandle = GM->OnScoreChanged.AddUObject(
this, &AMyHUD::UpdateScore);
}
}
void AMyHUD::EndPlay(const EEndPlayReason::Type Reason)
{
if (AMyGameMode* GM = GetGameMode())
{
// 특정 핸들만 제거
GM->OnScoreChanged.Remove(ScoreHandle);
// 또는 이 객체의 모든 바인딩 제거
GM->OnScoreChanged.RemoveAll(this);
}
Super::EndPlay(Reason);
}
Dynamic 멀티캐스트 (Blueprint용)
Blueprint에서 이벤트 디스패처로 사용
// Dynamic 멀티캐스트 델리게이트 선언
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeathSignature);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChangedSignature,
float, NewHealth);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnDamageSignature,
float, Damage,
AActor*, DamageCauser);
UCLASS()
class UHealthComponent : public UActorComponent
{
GENERATED_BODY()
public:
// BlueprintAssignable: BP에서 바인딩 가능
UPROPERTY(BlueprintAssignable, Category="Health|Events")
FOnHealthChangedSignature OnHealthChanged;
UPROPERTY(BlueprintAssignable, Category="Health|Events")
FOnDeathSignature OnDeath;
UPROPERTY(BlueprintAssignable, Category="Health|Events")
FOnDamageSignature OnDamageTaken;
void TakeDamage(float Damage, AActor* Causer)
{
Health = FMath::Max(0.0f, Health - Damage);
// C++과 BP 모두에게 알림
OnHealthChanged.Broadcast(Health);
OnDamageTaken.Broadcast(Damage, Causer);
if (Health <= 0.0f)
{
OnDeath.Broadcast();
}
}
};
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
if (HealthComp)
{
// Dynamic 델리게이트는 AddDynamic 매크로 사용
HealthComp->OnHealthChanged.AddDynamic(
this, &AMyCharacter::HandleHealthChanged);
HealthComp->OnDeath.AddDynamic(
this, &AMyCharacter::HandleDeath);
}
}
void AMyCharacter::EndPlay(const EEndPlayReason::Type Reason)
{
if (HealthComp)
{
// RemoveDynamic으로 해제
HealthComp->OnHealthChanged.RemoveDynamic(
this, &AMyCharacter::HandleHealthChanged);
}
Super::EndPlay(Reason);
}
// UFUNCTION() 필수!
UFUNCTION()
void AMyCharacter::HandleHealthChanged(float NewHealth)
{
// 체력 변경 처리
}
Dynamic 델리게이트에 바인딩하는 함수는 반드시 UFUNCTION()으로 선언해야 합니다!
다양한 바인딩 방식
상황에 맞는 바인딩 선택
// UObject 멤버 함수 (가장 일반적)
Delegate.BindUObject(this, &AMyClass::MyFunction);
MulticastDelegate.AddUObject(this, &AMyClass::MyFunction);
// Raw C++ 객체 (GC 대상 아님, 주의 필요)
Delegate.BindRaw(RawPointer, &FMyClass::MyFunction);
// 정적 함수
Delegate.BindStatic(&AMyClass::StaticFunction);
// 람다
Delegate.BindLambda([this](float Value)
{
ProcessValue(Value);
});
// WeakLambda (UObject가 파괴되면 자동 해제)
Delegate.BindWeakLambda(this, [this](float Value)
{
ProcessValue(Value);
});
// SP (TSharedRef/TSharedPtr 기반 객체)
Delegate.BindSP(SharedObject, &FMyClass::MyFunction);
// Thread-safe SP
Delegate.BindThreadSafeSP(ThreadSafeSharedObject, &FMyClass::MyFunction);
BindUObject
UObject 멤버 함수용. 가장 일반적이고 안전.
BindWeakLambda
람다 사용 시 권장. 객체 파괴 시 자동 해제.
BindRaw
비-UObject C++ 클래스용. 수명 관리 주의!
BindSP
TSharedPtr 기반 객체용. 참조 카운팅 활용.
베스트 프랙티스
실무에서의 델리게이트 활용 팁
// 나쁜 예: 파라미터가 너무 많음
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FBadDelegate,
float, A, float, B, int32, C, FString, D, AActor*, E);
// 좋은 예: 구조체로 묶기
USTRUCT(BlueprintType)
struct FDamageInfo
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite)
float Damage;
UPROPERTY(BlueprintReadWrite)
TObjectPtr<AActor> Attacker;
UPROPERTY(BlueprintReadWrite)
FVector HitLocation;
UPROPERTY(BlueprintReadWrite)
FName DamageType;
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDamageSignature,
const FDamageInfo&, DamageInfo);
void AMyActor::EndPlay(const EEndPlayReason::Type Reason)
{
// 모든 델리게이트 정리
OnHealthChanged.Clear();
OnDamageReceived.Clear();
// 다른 객체에 바인딩한 것들도 해제
if (GameMode)
{
GameMode->OnScoreChanged.RemoveAll(this);
}
Super::EndPlay(Reason);
}
바인딩이 없어도 Broadcast()는 안전하게 호출할 수 있습니다. 별도의 체크 없이 사용하세요!
핵심 요약
- 단일 델리게이트 — DECLARE_DELEGATE, BindUObject, ExecuteIfBound
- 멀티캐스트 — DECLARE_MULTICAST_DELEGATE, AddUObject, Broadcast
- Dynamic (BP용) — DECLARE_DYNAMIC_MULTICAST_DELEGATE, BlueprintAssignable
- AddDynamic/RemoveDynamic — Dynamic 델리게이트 바인딩, UFUNCTION 필수
- EndPlay에서 정리 — Clear() 또는 RemoveAll()로 모든 바인딩 해제
도전 과제
배운 내용을 직접 실습해보세요
DECLARE_DYNAMIC_MULTICAST_DELEGATE로 OnHealthChanged(float NewHealth), OnDeath(), OnLevelUp(int32 NewLevel) 델리게이트를 선언하고, BlueprintAssignable로 노출하세요. UHealthComponent에서 Broadcast()로 이벤트를 발생시키세요.
DECLARE_MULTICAST_DELEGATE_TwoParams로 FOnEnemyDefeated를 선언하고, HUD, 업적 시스템, 퀘스트 시스템에서 각각 AddUObject로 바인딩하세요. EndPlay에서 FDelegateHandle로 정리하는 코드도 구현하세요.
FDamageInfo 구조체(Damage, Attacker, HitLocation, DamageType)를 파라미터로 사용하는 델리게이트를 설계하세요. BindWeakLambda를 활용하여 UObject 파괴 시 자동으로 해제되는 안전한 콜백을 구현하세요.