PART 1 · 강의 6/7

델리게이트 시스템

이벤트 기반 프로그래밍과 느슨한 결합 구현하기

01

델리게이트 개요

타입 안전한 함수 포인터

델리게이트는 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 vs Non-Dynamic

Dynamic: 리플렉션 사용, Blueprint 노출 가능, 약간 느림
Non-Dynamic: C++ 전용, 더 빠름, Blueprint에서 사용 불가

02

단일 델리게이트

하나의 함수만 바인딩

C++ // 델리게이트 선언 (보통 헤더 상단에) 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(); } }
03

멀티캐스트 델리게이트

여러 함수를 동시에 바인딩

C++ // 멀티캐스트 델리게이트 선언 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); // 모든 바인딩 호출 }

핸들 기반 관리

C++ // 핸들로 특정 바인딩 관리 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); }
04

Dynamic 멀티캐스트 (Blueprint용)

Blueprint에서 이벤트 디스패처로 사용

C++ // 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(); } } };
C++에서 Dynamic 델리게이트 바인딩 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 델리게이트 바인딩 함수

Dynamic 델리게이트에 바인딩하는 함수는 반드시 UFUNCTION()으로 선언해야 합니다!

05

다양한 바인딩 방식

상황에 맞는 바인딩 선택

C++ // 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 기반 객체용. 참조 카운팅 활용.

06

베스트 프랙티스

실무에서의 델리게이트 활용 팁

파라미터가 많을 때 구조체 사용 // 나쁜 예: 파라미터가 너무 많음 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);
EndPlay에서 정리 void AMyActor::EndPlay(const EEndPlayReason::Type Reason) { // 모든 델리게이트 정리 OnHealthChanged.Clear(); OnDamageReceived.Clear(); // 다른 객체에 바인딩한 것들도 해제 if (GameMode) { GameMode->OnScoreChanged.RemoveAll(this); } Super::EndPlay(Reason); }
💡 Broadcast는 항상 안전

바인딩이 없어도 Broadcast()는 안전하게 호출할 수 있습니다. 별도의 체크 없이 사용하세요!

SUMMARY

핵심 요약

  • 단일 델리게이트 — DECLARE_DELEGATE, BindUObject, ExecuteIfBound
  • 멀티캐스트 — DECLARE_MULTICAST_DELEGATE, AddUObject, Broadcast
  • Dynamic (BP용) — DECLARE_DYNAMIC_MULTICAST_DELEGATE, BlueprintAssignable
  • AddDynamic/RemoveDynamic — Dynamic 델리게이트 바인딩, UFUNCTION 필수
  • EndPlay에서 정리 — Clear() 또는 RemoveAll()로 모든 바인딩 해제
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: RPG 이벤트 시스템 구현

DECLARE_DYNAMIC_MULTICAST_DELEGATE로 OnHealthChanged(float NewHealth), OnDeath(), OnLevelUp(int32 NewLevel) 델리게이트를 선언하고, BlueprintAssignable로 노출하세요. UHealthComponent에서 Broadcast()로 이벤트를 발생시키세요.

실습 2: 멀티캐스트 델리게이트로 옵저버 패턴

DECLARE_MULTICAST_DELEGATE_TwoParams로 FOnEnemyDefeated를 선언하고, HUD, 업적 시스템, 퀘스트 시스템에서 각각 AddUObject로 바인딩하세요. EndPlay에서 FDelegateHandle로 정리하는 코드도 구현하세요.

심화 과제: 구조체 기반 델리게이트와 BindWeakLambda

FDamageInfo 구조체(Damage, Attacker, HitLocation, DamageType)를 파라미터로 사용하는 델리게이트를 설계하세요. BindWeakLambda를 활용하여 UObject 파괴 시 자동으로 해제되는 안전한 콜백을 구현하세요.