델리게이트 (Delegate)의 개념과 구현
언리얼 엔진의 델리게이트 시스템은 유연하고 효율적인 이벤트 기반 프로그래밍을 가능하게 합니다.
이 절에서는 델리게이트의 개념, 유형, 그리고 C++에서의 구현 방법을 살펴보겠습니다.
델리게이트의 개념
델리게이트는 함수 포인터의 객체 지향적 버전으로, 런타임에 동적으로 함수를 호출할 수 있게 해줍니다.
- 타입 안전성
- 단일 또는 다중 함수 바인딩
- 비동기 프로그래밍 지원
- 블루프린트 연동 가능
델리게이트 유형
- 단일 캐스트 델리게이트 : 단일 함수만 바인딩 가능
- 멀티 캐스트 델리게이트 : 여러 함수를 바인딩하고 순차적으로 호출
- 동적 멀티 캐스트 델리게이트 : 멀티 캐스트 + 블루프린트 노출 가능
C++에서 델리게이트 구현
델리게이트 선언
DECLARE_DELEGATE(FMyDelegate);
DECLARE_DELEGATE_OneParam(FMyDelegateWithParam, int32);
DECLARE_MULTICAST_DELEGATE(FMyMulticastDelegate);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMyDynamicMulticastDelegate);
델리게이트 사용 예시
UCLASS()
class MYGAME_API AMyActor : public AActor
{
GENERATED_BODY()
public:
// 단일 캐스트 델리게이트
FMyDelegate OnSingleEvent;
// 멀티 캐스트 델리게이트
FMyMulticastDelegate OnMultiEvent;
// 동적 멀티 캐스트 델리게이트 (블루프린트 노출)
UPROPERTY(BlueprintAssignable, Category="Events")
FMyDynamicMulticastDelegate OnDynamicEvent;
void SomeFunction();
};
델리게이트 바인딩
void AMyActor::SomeFunction()
{
// 멤버 함수 바인딩
OnSingleEvent.BindUObject(this, &AMyActor::HandleSingleEvent);
// 람다 함수 바인딩
OnMultiEvent.AddLambda([this]()
{
UE_LOG(LogTemp, Log, TEXT("Lambda function called"));
});
// 정적 함수 바인딩
OnMultiEvent.AddStatic(&StaticHandlerFunction);
}
void AMyActor::HandleSingleEvent()
{
UE_LOG(LogTemp, Log, TEXT("Single event handled"));
}
static void StaticHandlerFunction()
{
UE_LOG(LogTemp, Log, TEXT("Static function called"));
}
델리게이트 실행
// 단일 캐스트 델리게이트 실행
if (OnSingleEvent.IsBound())
{
OnSingleEvent.Execute();
}
// 멀티 캐스트 델리게이트 실행
OnMultiEvent.Broadcast();
// 동적 멀티 캐스트 델리게이트 실행
OnDynamicEvent.Broadcast();
언바인딩
// 특정 함수 언바인딩
OnMultiEvent.Remove(this, &AMyActor::HandleMultiEvent);
// 모든 바인딩 제거
OnMultiEvent.Clear();
델리게이트와 이벤트의 차이점
- 델리게이트 : 클래스 외부에서 호출 가능
- 이벤트 : 선언된 클래스 내부에서만 호출 가능 (캡슐화 강화)
DECLARE_EVENT(FMyClass, FMyEvent);
class FMyClass
{
public:
FMyEvent& OnMyEvent() { return MyEvent; }
private:
FMyEvent MyEvent;
};
템플릿 기반 델리게이트
템플릿을 사용하여 다양한 시그니처의 델리게이트를 생성할 수 있습니다.
template<typename T>
class TMyTemplateDelegate
{
public:
DECLARE_DELEGATE_RetVal_OneParam(bool, FTemplateDelegate, T);
FTemplateDelegate OnTemplateEvent;
};
TMyTemplateDelegate<int32> IntDelegate;
TMyTemplateDelegate<FString> StringDelegate;
블루프린트 연동
동적 멀티 캐스트 델리게이트를 사용하여 블루프린트와 연동할 수 있습니다.
UCLASS()
class MYGAME_API AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable, Category="Events")
FMyDynamicMulticastDelegate OnBlueprintEvent;
UFUNCTION(BlueprintCallable, Category="Events")
void TriggerEvent();
};
void AMyActor::TriggerEvent()
{
OnBlueprintEvent.Broadcast();
}
블루프린트에서는 이 이벤트를 구독하고 처리할 수 있습니다.
느슨한 결합 설계
델리게이트를 사용하면 시스템 간의 결합도를 낮출 수 있습니다.
UCLASS()
class MYGAME_API AWeapon : public AActor
{
GENERATED_BODY()
public:
FOnWeaponFiredSignature OnWeaponFired;
void Fire();
};
void AWeapon::Fire()
{
// 발사 로직
OnWeaponFired.Broadcast();
}
// 다른 클래스에서
WeaponInstance->OnWeaponFired.AddUObject(this, &AMyCharacter::HandleWeaponFired);
이 방식으로 Weapon
클래스는 Character
클래스에 대해 알 필요가 없습니다.
성능 고려사항
- 델리게이트 호출은 직접 함수 호출보다 약간의 오버헤드가 있습니다.
- 자주 호출되는 핫 경로에서는 직접 함수 호출을 고려하세요.
- 멀티 캐스트 델리게이트의 경우, 등록된 함수 수에 비례하여 실행 시간이 증가합니다.
메모리 관리 주의점
- 객체 소멸 시 델리게이트 언바인딩 처리
AMyActor::~AMyActor()
{
if (OnMultiEvent.IsBound())
{
OnMultiEvent.Clear();
}
}
- 약한 포인터 사용으로 댕글링 포인터 방지
FWeakObjectPtr WeakThis(this);
OnMultiEvent.AddLambda([WeakThis]()
{
if (WeakThis.IsValid())
{
WeakThis->SomeFunction();
}
});
Best Practices
- 명확한 명명 규칙 사용
DECLARE_DELEGATE(FOnHealthChangedDelegate);
- 델리게이트 시그니처를 위한 typedef 사용
DECLARE_DELEGATE_TwoParams(FOnItemAddedSignature, UItem*, int32);
typedef FOnItemAddedSignature::FDelegate FOnItemAddedDelegate;
- 안전한 실행을 위한 헬퍼 함수 사용
template<typename T>
void SafeExecuteDelegate(T& Delegate)
{
if (Delegate.IsBound())
{
Delegate.Execute();
}
}
- 이벤트 기반 시스템 설계 시 델리게이트 활용
UCLASS()
class MYGAME_API UEventManager : public UObject
{
GENERATED_BODY()
public:
DECLARE_MULTICAST_DELEGATE_OneParam(FOnLevelCompleteSignature, int32);
FOnLevelCompleteSignature OnLevelComplete;
};
델리게이트는 언리얼 엔진에서 유연하고 강력한 이벤트 처리 메커니즘을 제공합니다.
델리게이트를 통한 느슨한 결합 설계는 특히 대규모 프로젝트에서 유지보수성을 크게 개선할 수 있습니다.
성능과 메모리 관리에 주의를 기울이면 델리게이트를 활용하면 더 유연하고 확장 가능한 게임 시스템을 구축할 수 있습니다.