[UE5] Delegate에 대해

ChangJin·2024년 11월 21일
0

Unreal Engine5

목록 보기
114/114
post-thumbnail

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


들어가기에 앞서 생각해볼만한 사용 예를 적어보겠습니다.

Blueprint 사용 예

버튼을 누르면 메뉴가 열리는 간단한 로직의 델리게이트입니다. 즉 HUD의 메뉴버튼이죠. 잘 보면 버튼을 참조하고 있는데, 만약 해당 메뉴가 닫혔을때 버튼의 모양이 달라지는 기능을 구현하고자 한다고 해봅시다. 그럼 HUD쪽에서 HUD가 만든 메뉴 위젯의 레퍼런스를 가지고 있게하고 만들어진 메뉴 위젯에서는 HUD의 레퍼런스를 가지고 있게끔하는게 일반적으로 떠올리기 쉬운 방법인데, 이렇게 하면 안됩니다. 바로 순환참조가 생기기 쉬운 방법이기 때문입니다. 객체지향의 관점에서 봤을때는 서로가 서로의 레퍼런스를 가지기보다는 버튼을 누르면 그 만들어진 메뉴와 나(Pannel UI)와는 관계가 없어야합니다. 바로 이럴때 델리게이트가 아주 유용합니다.


1. Delegate란 무엇인가?

Delegate는 C++에서 함수 포인터와 유사한 개념으로, 다음과 같은 작업을 처리할 수 있습니다:

  • 동적으로 함수 바인딩: 런타임에서 함수 호출 대상을 동적으로 설정.
  • 멀티캐스트 지원: 여러 수신자에게 이벤트를 전파.
  • 안정성: UObject와 함께 사용할 때, 객체가 소멸되었는지 자동으로 확인.

2. Delegate의 종류

Unreal Engine에서는 다음과 같은 Delegate를 제공합니다:

  1. Single-cast Delegate

    • 하나의 함수만 바인딩 가능.
    • 예: DECLARE_DELEGATE
  2. Multi-cast Delegate

    • 여러 개의 함수 바인딩 가능.
    • 예: DECLARE_MULTICAST_DELEGATE
  3. Dynamic Delegate

    • 블루프린트와의 연동 가능.
    • 예: DECLARE_DYNAMIC_DELEGATE
  4. Dynamic Multicast Delegate

    • 여러 개의 블루프린트 함수 바인딩 가능.
    • 예: DECLARE_DYNAMIC_MULTICAST_DELEGATE

1. Single-cast Delegate

  • 특징:

    • 하나의 함수만 바인딩 가능.
    • DECLARE_DELEGATE를 통해 선언.
  • 장점:

    • 단순성: 관리가 쉽고, 호출 대상이 명확함.
    • 퍼포먼스: 가장 빠르고, 메모리 및 실행 성능이 뛰어남.
    • 타입 안정성: 컴파일 타임에 타입 체크가 이루어져 런타임 오류 가능성이 낮음.
  • 단점:

    • 한 번에 하나의 함수만 바인딩 가능.
    • 블루프린트 연동 불가.
  • 사용 시기:

    • 하나의 객체 또는 함수만 이벤트를 처리해야 하는 경우.
    • 주로 게임 로직에서 이벤트와 단일 리스너가 필요한 상황.

2. Multi-cast Delegate

  • 특징:

    • 여러 개의 함수 바인딩 가능.
    • DECLARE_MULTICAST_DELEGATE를 통해 선언.
  • 장점:

    • 유연성: 여러 리스너를 등록할 수 있어 다양한 객체가 동일한 이벤트를 처리 가능.
    • 타입 안정성: 컴파일 타임 타입 체크를 지원.
  • 단점:

    • 바인딩된 함수가 많을 경우 성능이 저하될 수 있음.
    • 블루프린트 연동 불가.
  • 사용 시기:

    • 하나의 이벤트를 여러 리스너가 처리해야 하는 경우.
    • 예: UI 갱신, 여러 시스템이 동일 이벤트에 반응해야 할 때.

3. Dynamic Delegate

  • 특징:

    • 블루프린트와의 연동 가능.
    • DECLARE_DYNAMIC_DELEGATE를 통해 선언.
  • 장점:

    • 블루프린트 지원: 블루프린트에서 이벤트를 쉽게 처리 가능.
    • 런타임에 동적으로 함수 할당 가능.
  • 단점:

    • 퍼포먼스: Reflection을 사용하므로 실행 속도가 느림.
    • 타입 안정성: 컴파일 타임 타입 체크가 없어서 런타임 오류 발생 가능.
  • 사용 시기:

    • 블루프린트와의 상호작용이 필요한 경우.
    • 런타임에 동적으로 이벤트를 설정해야 할 때.

4. Dynamic Multicast Delegate

  • 특징:

    • 여러 개의 블루프린트 함수 바인딩 가능.
    • DECLARE_DYNAMIC_MULTICAST_DELEGATE를 통해 선언.
  • 장점:

    • 블루프린트 지원: 여러 블루프린트에서 동일 이벤트 처리 가능.
    • 런타임에 동적으로 리스너를 추가/제거 가능.
  • 단점:

    • 퍼포먼스: Reflection과 멀티캐스트의 복합 효과로 인해 가장 느림.
    • 타입 안정성: 컴파일 타임 타입 체크 불가.
  • 사용 시기:

    • 여러 블루프린트에서 동일한 이벤트를 처리해야 하는 경우.
    • UI 시스템이나 복잡한 이벤트 체계에서 유용.

요약: Delegate 종류별 적합한 사용 시기

Delegate 종류장점단점사용 시기
Single-cast단순, 빠름, 타입 안정성하나의 함수만 바인딩단일 리스너가 필요한 게임 로직
Multi-cast유연성, 여러 함수 바인딩 가능블루프린트 연동 불가여러 리스너가 이벤트를 처리해야 할 때
Dynamic블루프린트 지원, 동적 할당 가능느림, 타입 안정성 부족블루프린트와의 연동 또는 동적 이벤트 필요 시
Dynamic Multicast블루프린트 지원, 여러 함수 바인딩 가능가장 느림, 타입 안정성 부족여러 블루프린트에서 동일 이벤트 처리 필요 시

3. GAS에서의 Delegate 사용

다음은 GAS에서 Attribute 변화 감지에 사용된 Delegate 코드입니다:

예제 코드

AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
    AuraAttributeSet->GetHealthAttribute()).AddLambda(
        [this](const FOnAttributeChangeData& Data)
        {
            OnHealthChanged.Broadcast(Data.NewValue);
        }
    );

// Delegate 호출
OnHealthChanged.Broadcast(AuraAttributeSet->GetHealth());

주요 흐름

  1. Attribute 변화 감지

    • GetGameplayAttributeValueChangeDelegate로 Health Attribute 변화를 감지.
    • 변화가 감지되면 AddLambda로 바인딩된 람다 함수 실행.
  2. Delegate Broadcast

    • OnHealthChanged라는 멀티캐스트 Delegate에 변화된 값을 전달.

4. Delegate 내부 동작 분석

Unreal Engine에서 Delegate는 TMulticastDelegate 템플릿 클래스를 사용하여 구현됩니다. 주요 동작 방식을 살펴보겠습니다.

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...);
    }
};

5. GAS의 Delegate 활용

GAS는 Attribute 변화를 Delegate로 감지하여 반응형 시스템을 구축합니다. Delegate를 활용하면 능력 변화와 같은 이벤트를 효율적으로 처리할 수 있습니다.

사용 예제: Attribute 변화 감지

다음은 Health Attribute 변화 감지에 Delegate를 사용하는 예제입니다.

Step 1: Delegate 설정

GameplayEffect가 Attribute를 변경할 때마다 호출될 Delegate를 설정합니다.

AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
    AuraAttributeSet->GetHealthAttribute()).AddLambda(
        [this](const FOnAttributeChangeData& Data)
        {
            OnHealthChanged.Broadcast(Data.NewValue);
        }
    );

Step 2: Delegate 바인딩

바인딩된 람다 함수에서 OnHealthChanged라는 멀티캐스트 Delegate를 호출합니다.

OnHealthChanged.Broadcast(AuraAttributeSet->GetHealth());

Step 3: Delegate 브로드캐스트

모든 바인딩된 수신자에게 이벤트를 전파합니다.


6. Delegate 사용 시 주의 사항

  1. 객체 유효성 검사
    Delegate가 바인딩된 객체가 소멸되었는지 확인해야 합니다. Unreal Engine은 이를 위해 Weak Pointer를 사용합니다.

  2. 메모리 관리
    Delegate에 동적으로 추가된 함수가 제대로 해제되지 않으면 메모리 누수가 발생할 수 있습니다.

  3. 성능
    멀티캐스트 Delegate는 수신자가 많을수록 성능이 저하될 수 있습니다.


7. 결론

Unreal Engine의 Delegate는 이벤트 기반 프로그래밍의 핵심 요소로, 객체 간의 효율적인 의사소통을 가능하게 합니다. GAS와 같은 시스템에서는 Attribute 변화 감지와 같은 작업을 처리하는 데 필수적입니다.


profile
게임 프로그래머

0개의 댓글