Unreal Engine의 Delegate는 이벤트 기반 프로그래밍에서 자주 사용하는데, 객체 간의 메시지 전달을 효율적으로 처리할 수 있습니다. Gameplay Ability System(GAS)에서 Delegate는 능력 변화나 속성 변화를 감지하고 이를 반영하는데 사용합니다. UI에서 값을 전달할때도 자주 사용합니다. 이번 글에서는 Delegate의 기본 개념과 구현, 그리고 GAS에서의 사용 사례를 다룹니다.
2024년 4월에 한번 다룬적이 있는 주제인데, 그 때는 단순하게 한 오브젝트가 다른 오브젝트의 멤버 함수를 call하는 것이라고만 이해했습니다. 델리게이트에는 다양한 종류가 있고, C++에서뿐만 아니라 블루프린트에서도 사용이 가능하다는 것을 학습하게 되었습니다.
이전 글 :
https://velog.io/@whoamicj/UE5-TIL-26-Delegate-Dynamic-Delegate
들어가기에 앞서 생각해볼만한 사용 예를 적어보겠습니다.
버튼을 누르면 메뉴가 열리는 간단한 로직의 델리게이트입니다. 즉 HUD의 메뉴버튼이죠. 잘 보면 버튼을 참조하고 있는데, 만약 해당 메뉴가 닫혔을때 버튼의 모양이 달라지는 기능을 구현하고자 한다고 해봅시다. 그럼 HUD쪽에서 HUD가 만든 메뉴 위젯의 레퍼런스를 가지고 있게하고 만들어진 메뉴 위젯에서는 HUD의 레퍼런스를 가지고 있게끔하는게 일반적으로 떠올리기 쉬운 방법인데, 이렇게 하면 안됩니다. 바로 순환참조가 생기기 쉬운 방법이기 때문입니다. 객체지향의 관점에서 봤을때는 서로가 서로의 레퍼런스를 가지기보다는 버튼을 누르면 그 만들어진 메뉴와 나(Pannel UI)와는 관계가 없어야합니다. 바로 이럴때 델리게이트가 아주 유용합니다.
Delegate는 C++에서 함수 포인터와 유사한 개념으로, 다음과 같은 작업을 처리할 수 있습니다:
Unreal Engine에서는 다음과 같은 Delegate를 제공합니다:
Single-cast Delegate
DECLARE_DELEGATE
Multi-cast Delegate
DECLARE_MULTICAST_DELEGATE
Dynamic Delegate
DECLARE_DYNAMIC_DELEGATE
Dynamic Multicast Delegate
DECLARE_DYNAMIC_MULTICAST_DELEGATE
특징:
DECLARE_DELEGATE
를 통해 선언.장점:
단점:
사용 시기:
특징:
DECLARE_MULTICAST_DELEGATE
를 통해 선언.장점:
단점:
사용 시기:
특징:
DECLARE_DYNAMIC_DELEGATE
를 통해 선언.장점:
단점:
사용 시기:
특징:
DECLARE_DYNAMIC_MULTICAST_DELEGATE
를 통해 선언.장점:
단점:
사용 시기:
Delegate 종류 | 장점 | 단점 | 사용 시기 |
---|---|---|---|
Single-cast | 단순, 빠름, 타입 안정성 | 하나의 함수만 바인딩 | 단일 리스너가 필요한 게임 로직 |
Multi-cast | 유연성, 여러 함수 바인딩 가능 | 블루프린트 연동 불가 | 여러 리스너가 이벤트를 처리해야 할 때 |
Dynamic | 블루프린트 지원, 동적 할당 가능 | 느림, 타입 안정성 부족 | 블루프린트와의 연동 또는 동적 이벤트 필요 시 |
Dynamic Multicast | 블루프린트 지원, 여러 함수 바인딩 가능 | 가장 느림, 타입 안정성 부족 | 여러 블루프린트에서 동일 이벤트 처리 필요 시 |
다음은 GAS에서 Attribute 변화 감지에 사용된 Delegate 코드입니다:
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AuraAttributeSet->GetHealthAttribute()).AddLambda(
[this](const FOnAttributeChangeData& Data)
{
OnHealthChanged.Broadcast(Data.NewValue);
}
);
// Delegate 호출
OnHealthChanged.Broadcast(AuraAttributeSet->GetHealth());
Attribute 변화 감지
GetGameplayAttributeValueChangeDelegate
로 Health Attribute 변화를 감지.AddLambda
로 바인딩된 람다 함수 실행.Delegate Broadcast
OnHealthChanged
라는 멀티캐스트 Delegate에 변화된 값을 전달.Unreal Engine에서 Delegate는 TMulticastDelegate
템플릿 클래스를 사용하여 구현됩니다. 주요 동작 방식을 살펴보겠습니다.
TMulticastDelegate
는 멀티캐스트 Delegate의 기본 구현입니다. 여러 수신자를 관리하며, 각각의 수신자에게 이벤트를 전달할 수 있습니다.
template <typename... ParamTypes, typename UserPolicy>
class TMulticastDelegate<void(ParamTypes...), UserPolicy> : public UserPolicy::FMulticastDelegateExtras
{
public:
using FDelegate = TDelegate<void(ParamTypes...), UserPolicy>;
// Delegate 추가
FDelegateHandle Add(FDelegate&& InNewDelegate)
{
return Super::AddDelegateInstance(MoveTemp(InNewDelegate));
}
// Lambda 추가
template<typename FunctorType, typename... VarTypes>
inline FDelegateHandle AddLambda(FunctorType&& InFunctor, VarTypes&&... Vars)
{
return Super::AddDelegateInstance(FDelegate::CreateLambda(Forward<FunctorType>(InFunctor), Forward<VarTypes>(Vars)...));
}
// Delegate 호출
void Broadcast(ParamTypes... Params) const
{
Super::template Broadcast<DelegateInstanceInterfaceType, ParamTypes...>(Params...);
}
};
GAS는 Attribute 변화를 Delegate로 감지하여 반응형 시스템을 구축합니다. Delegate를 활용하면 능력 변화와 같은 이벤트를 효율적으로 처리할 수 있습니다.
다음은 Health Attribute 변화 감지에 Delegate를 사용하는 예제입니다.
GameplayEffect
가 Attribute를 변경할 때마다 호출될 Delegate를 설정합니다.
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AuraAttributeSet->GetHealthAttribute()).AddLambda(
[this](const FOnAttributeChangeData& Data)
{
OnHealthChanged.Broadcast(Data.NewValue);
}
);
바인딩된 람다 함수에서 OnHealthChanged라는 멀티캐스트 Delegate를 호출합니다.
OnHealthChanged.Broadcast(AuraAttributeSet->GetHealth());
모든 바인딩된 수신자에게 이벤트를 전파합니다.
객체 유효성 검사
Delegate가 바인딩된 객체가 소멸되었는지 확인해야 합니다. Unreal Engine은 이를 위해 Weak Pointer를 사용합니다.
메모리 관리
Delegate에 동적으로 추가된 함수가 제대로 해제되지 않으면 메모리 누수가 발생할 수 있습니다.
성능
멀티캐스트 Delegate는 수신자가 많을수록 성능이 저하될 수 있습니다.
Unreal Engine의 Delegate는 이벤트 기반 프로그래밍의 핵심 요소로, 객체 간의 효율적인 의사소통을 가능하게 합니다. GAS와 같은 시스템에서는 Attribute 변화 감지와 같은 작업을 처리하는 데 필수적입니다.