본문 바로가기
개발이야기/언리얼 c++

언리얼 엔진의 델리게이트 기초와 심층분석

by oddsilk 2024. 5. 27.

1.델리게이트(Delegate)란?

  • 델리게이트는 특정 이벤트가 발생했을 때 호출될 함수를 지정하는 방식입니다. 이는 C++의 함수 포인터와 유사하지만 더 안전하고 유연합니다

 

  1. 델리게이트와 이벤트 시스템의 차이점
    • 델리게이트는 함수 포인터의 역할을 하며, 특정 시점에 함수를 호출할 수 있게 합니다.
    • 이벤트는 델리게이트를 기반으로 하며, 여러 개의 델리게이트를 바인딩하여 특정 이벤트 발생 시 여러 함수를 호출할 수 있습니다.
  2. 델리게이트의 활용 예제
    • UI 버튼 클릭 이벤트 처리: UI 버튼이 클릭되었을 때 특정 함수를 호출하도록 설정.
    • 캐릭터 상태 변경: 캐릭터의 상태가 변경될 때마다 해당 상태에 맞는 함수를 호출.
  3. 델리게이트의 메모리 관리
    • 델리게이트는 자동으로 메모리를 관리하지만, 바인딩된 객체가 소멸될 때 바인딩을 해제하는 것이 중요합니다.

1)  델리게이트의 종류

Single-cast Delegate:
  • 하나의 함수만을 호출(바인딩)하는 델리게이트입니다.
  • 비교적 가볍고 성능이 뛰어납니다
  • 컴파일 타임에 바인딩되어야 하며, 런타임에 바인딩을 변경할 수 없습니다.

사용시기

  • 이벤트가 발생했을 때 한 가지 작업만 수행해야 할 때.
  • 성능이 중요한 상황에서 델리게이트를 사용할 때.
  • 런타임에 델리게이트 바인딩을 변경할 필요가 없을 때.

기본형태

// 델리게이트 선언
DECLARE_DELEGATE(FSimpleDelegate);

// 델리게이트 변수 정의
FSimpleDelegate MyDelegate;

// 함수 바인딩
MyDelegate.BindUObject(this, &MyClass::MyFunction);

// 델리게이트 호출
MyDelegate.ExecuteIfBound();

Multi-cast Delegate:

  • 여러 개의 함수를 바인딩할 수 있습니다.
  • 한 번의 이벤트 발생 시 여러 개의 콜백을 실행할 수 있습니다.
  • 컴파일 타임에 바인딩되어야 하며, 런타임에 바인딩을 변경할 수 없습니다.

사용시기

  • 이벤트가 발생했을 때 여러 작업을 동시에 수행해야 할 때.
  • 여러 객체가 같은 이벤트를 청취하고 반응해야 할 때.
  • 런타임에 델리게이트 바인딩을 변경할 필요가 없을 때.

기본형태

// 델리게이트 선언
DECLARE_MULTICAST_DELEGATE(FSimpleMulticastDelegate);

// 델리게이트 변수 정의
FSimpleMulticastDelegate MyDelegate;

// 함수 바인딩
MyDelegate.AddUObject(this, &MyClass::MyFunction);

// 델리게이트 호출
MyDelegate.Broadcast();

Dynamic (Single or Multi)-cast Delegate:

  1. 특징
    • 블루프린트에서 사용 가능합니다.
    • 런타임에 바인딩을 변경할 수 있습니다.
    • 싱글캐스트와 멀티캐스트 형태로 모두 사용할 수 있습니다.
    • 런타임 성능이 비교적 낮으며, 리플렉션 시스템을 사용합니다.
  2. 사용 시기
    • 블루프린트에서 이벤트를 처리해야 할 때.
    • 런타임에 델리게이트 바인딩을 동적으로 변경할 필요가 있을 때.
    • 게임 플레이 로직에서 블루프린트와 C++ 간의 상호작용이 필요한 경우.
// 1. 싱글캐스트 다이나믹 델리게이트
// 델리게이트 선언
DECLARE_DYNAMIC_DELEGATE(FSimpleDynamicDelegate);

// 델리게이트 변수 정의
FSimpleDynamicDelegate MyDelegate;

// 함수 바인딩
MyDelegate.BindDynamic(this, &MyClass::MyFunction);

// 델리게이트 호출
MyDelegate.ExecuteIfBound();


// 2. 멀티캐스트 다이나믹 델리게이트
// 델리게이트 선언
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FSimpleDynamicMulticastDelegate);

// 델리게이트 변수 정의
FSimpleDynamicMulticastDelegate MyDelegate;

// 함수 바인딩
MyDelegate.AddDynamic(this, &MyClass::MyFunction);

// 델리게이트 호출
MyDelegate.Broadcast();

 

2) 파라미터가 있는 델리게이트 

각각 델리게이트는 파라미터의 개수에 따라서 선언하는 형태가 다음과 같이 달라진다

//---------------------------SingleCast----------------------------------
//1. 기본형태
DECLARE_DELEGATE(FSimpleDelegate); // 함수인자 없는 함수 호출


//2. 파라미터가 있는 형태
DECLARE_DELEGATE_OneParam(FOneParamDelegate, int32); // 함수인자가 하나인 파라미터 호출
DECLARE_DELEGATE_TwoParams(FTwoParamsDelegate, int32, float);
DECLARE_DELEGATE_ThreeParams(FThreeParamsDelegate, int32, float, FString);
//....

//-------------------------------MultiCast-------------------------------
//1. 기본형태
DECLARE_MULTICAST_DELEGATE(FSimpleMulticastDelegate);

//2. 파라미터가 있는 형태
DECLARE_MULTICAST_DELEGATE_OneParam(FOneParamMulticastDelegate, int32);
DECLARE_MULTICAST_DELEGATE_TwoParams(FTwoParamsMulticastDelegate, int32, float);
DECLARE_MULTICAST_DELEGATE_ThreeParams(FThreeParamsMulticastDelegate, int32, float, FString);
//...


//--------------------------Dynamic---------------------------
//1. 싱글캐스트 다이나믹 델리게이트
DECLARE_DYNAMIC_DELEGATE(FSimpleDynamicDelegate);

//2. 멀티캐스트 다이나믹 델리게이트
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FSimpleDynamicMulticastDelegate);


//3. 파라미터가 있는 다이나믹 델리게이트
DECLARE_DYNAMIC_DELEGATE_OneParam(FOneParamDynamicDelegate, int32);
DECLARE_DYNAMIC_DELEGATE_TwoParams(FTwoParamsDynamicDelegate, int32, float);
DECLARE_DYNAMIC_DELEGATE_ThreeParams(FThreeParamsDynamicDelegate, int32, float, FString);

//4. 파라미터가 있는 다이나믹 멀티개스트 델리게이트
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOneParamDynamicMulticastDelegate, int32);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FTwoParamsDynamicMulticastDelegate, int32, float);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FThreeParamsDynamicMulticastDelegate, int32, float, FString);

 

델리게이트 호출시 파라미터에 값을 입력하기

//example
MyDelegate.Broadcast(42);

 

 

그 외 함수를 바인딩 할 수 있는 여러가지 방법들

FSimpleDelegate MyDelegate;
MyDelegate.BindUObject(this, &MyClass::MyFunction); // 외부 클래스 레퍼런스 참조 후 멤버 함수 호출 // 기본형태
MyDelegate.BindUFunction(this, FName("MyFunctionWithParam")); // 클래스 내부 함수 이름으로 호출

2)실제 사용 예제

여기서 하나의 간단한 예제를 통해 델리게이트를 구현해 보겠습니다.

Step 1: 델리게이트 선언 및 바인딩

먼저, MyActor 클래스에서 델리게이트를 선언하고 바인딩해 보겠습니다.

 

// MyActor.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSomethingHappened);// 델리게이트 선언

UCLASS()
class MYPROJECT_API AMyActor : public AActor
{
    GENERATED_BODY()
    //  ... 생략//
    UPROPERTY(BlueprintAssignable, Category = "Events")
    FOnSomethingHappened OnSomethingHappened; // 델리게이트 변수로 선언
    
    void TriggerEvent();
};
// MyActor.cpp
#include "MyActor.h"

// 트리거 이벤트 함수가 호출될때, 델리게이트 실행
void AMyActor::TriggerEvent()
{
    OnSomethingHappened.Broadcast();
}

이제 다른 클래스에서 해당 델리게이트에 함수를 바인딩하고 이벤트를 발생시켜 보겠습니다.

// 다른 액터(여기서는 MyOtherActor로 이름지음)에서 델리게이트를 선언한 헤더파일을 참조 MyActor.h 
// MyOtherActor.cpp

#include "MyOtherActor.h"
#include "MyActor.h"

void AMyOtherActor::BeginPlay()
{
    Super::BeginPlay();

    // MyActor 인스턴스를 찾고 델리게이트 바인딩
    for (TActorIterator<AMyActor> It(GetWorld()); It; ++It)
    {
        AMyActor* MyActor = *It;
        if (MyActor)
        {
            MyActor->OnSomethingHappened.AddDynamic(this, &AMyOtherActor::OnMyActorSomethingHappened);
            // 여기에서 바인딩할 함수를 지정
        }
    }
}



void AMyOtherActor::OnMyActorSomethingHappened()
{
    // 이벤트 발생 시 실행될 코드
    UE_LOG(LogTemp, Warning, TEXT("Something happened in MyActor!"));
}

 

 

 

델리게이트의 내부 코드를 따라가보자

DECLARE_DELEGATE(FSuccessConnect); //델리게이트 선언

-------f12---------->

/** Declares a delegate that can only bind to one native function at a time */
#define DECLARE_DELEGATE( DelegateName ) FUNC_DECLARE_DELEGATE( DelegateName, void )
// 아하 여기서 보면 #define으로 델리게이트 매크로를 전처리 했다. 
//여기서의 실제 몸체는 FUNC_DECLARE_DELEGATE 매크로에 있는 것을 확인할 수 있다..


-------f12---------->


/**
 * Declares a delegate that can only bind to one native function at a time
 *
 * @note: The last parameter is variadic and is used as the 'template args' for this delegate's classes (__VA_ARGS__)
 * @note: To avoid issues with macro expansion breaking code navigation, make sure the type/class name macro params are unique across all of these macros
 */
#define FUNC_DECLARE_DELEGATE( DelegateName, ReturnType, ... ) \
	typedef TDelegate<ReturnType(__VA_ARGS__)> DelegateName;

// 여기서도 전처리된 FUNC_DELCARE_DELGATE를 볼 수 있다. 
//typedef를 활용해서 델리게이트의 구현부를 작성했다.

//그럼 TDelegate는 도대체뭘까?-------f12---------->
/**
 * Unicast delegate template class.
 */
template <typename DelegateSignature, typename UserPolicy = FDefaultDelegateUserPolicy>
class TDelegate
{
	static_assert(sizeof(UserPolicy) == 0, "Expected a function signature for the delegate template parameter");
};
// 클래스로 static_asset를 가지고 있다. 
//template를 활용하여 DelegateSignature를 활용하자