언리얼의 Delegate

1000·2020년 12월 6일
0

언리얼 기초 공부

목록 보기
12/16

1. Delegate의 정의

딜리게이트(delegate)라는 단어를 사전에서 검색해보면 집단의 의사를 대표하는 대표자라고 나옵니다.
하지만 컴퓨터 프로그래밍에서 딜리게이트는 함수를 안전하게 호출할 수 있는 기능을 의미합니다.

사실 C++ 언어에서는 딜리게이트라는 개념이 존재하지 않습니다. 딜리게이트는 C++ 의 다음 세대 언어인 C#에서 (정확히는 CLI 플랫폼 기반) 선보인 개념인데, 콜백 함수를 등록하기 위해 C 혹은 C++에서 사용한 함수 포인터가 문법이 복잡하고, 위험한 방식이었다면, 딜리게이트는 간편한 문법과 안전성을 갖춰 콜백 함수를 호출하기 위해 고안되었습니다.

딜리게이트의 간편함과 안정성 외에도 이전 방식과 다른 큰 특징은 사전적 의미대로 집단의 의사를 대표한다는 점입니다. 이는 딜리게이트가 하나의 함수 뿐만 아니라 동일한 리턴값과 인자 타입을 가지는 여러 개를 관리할 수 있다는 것을 의미합니다.
딜리게이트의 이러한 특징은 C# 언어의 주요 패턴 중 하나인 발행/구독(Publish/Subscribe) 패턴의 구현으로 이어집니다. C#의 발행/구독 패턴은 우리가 일상 생활에서 흔히 접하는 신문을 구독하는 절차와 유사한 방식입니다. 구독자가 신문사에게 신문을 받아보겠다는 구독 의사를 밝히고 등록하면, 신문이 발행될 때마다 동일한 시간에 구독한 모든 구독자의 집으로 신문을 배달해주듯이, 특정 이벤트가 발생하면 딜리게이트에 등록된 모든 함수를 한꺼번에 호출할 수 있습니다.

예를 들어 어떤 게임에서 보스와 보스가 스폰한 미니언(Minion)이 있어서 보스가 죽으면, 이 미니언(Minion)도 함께 죽게 만들고 싶다고 가정합시다. 보스가 죽는 이벤트를 딜리게이트로 정의하고 미니언을 스폰할 때마다 각 미니언들이 이를 구독하게 설정해두면, 보스가 죽을 때 명령 하나로 모든 미니언들에게 보스가 죽었다고 알려줄 수 있습니다.

딜리게이트 기능의 특징은 다음과 같이 요약할 수 있습니다.

  • 함수 포인터의 직접 접근이 아닌 대리자를 통한 함수 호출 방식
  • 호출할 함수나 이를 포함하는 객체가 없어져도 대리자가 체크해 안전하게 처리할 수 있음.
  • 동일한 형을 가진 함수 여러 개를 대리자가 묶어서 관리하고, 필요할 때 동시에 모두 호출하는 것이 가능함.

딜리게이트 기능은 C++언어에서는 제공하지 않습니다만 우리의 언리얼 C++은 자체적으로 프레임웍을 제작해 이 기능을 지원하고 있습니다.

2. Delegate 사용 예제

딜리게이트를 바인딩할 시 주의할 점은 딜리게이트에 등록할 함수의 종류에 따라 호출하는 함수가 달라진다는 점입니다.
언리얼 딜리게이트 시스템에 등록 가능한 함수는 다음과 같습니다.

  • 전역 C++ 함수 : BindStatic API를 사용해 등록
  • 전역 C++ 람다 함수 : BindLambda API를 사용해 등록
  • C++클래스 멤버 함수 : BindRaw API를 사용해 등록
  • 공유포인터 클래스의 멤버 함수 (쓰레드 미지원) : BindSP API를 사용해 등록
  • 공유포인터 클래스의 멤버 함수 (쓰레드 지원) : BindThreadSafeSP API를 사용해 등록
  • UFUNCTION 멤버 함수 : BindUFunction API를 사용해 등록
  • 언리얼 오브젝트의 멤버함수 : BindUObject API를 사용해 등록
// WebConnection.h
DECLARE_DELEGATE_OneParam(FTokenCompleteSignature, const FString& );

pubilc:
    FTokenCompleteSignature TokenCompleteDelegate;
// WebConnection.cpp
void UWebConnection::RequestToken(const FString& UserID)
{
    TokenCompleteDelegate.ExecuteIfBound(TEXT("Key"));
}
// ABGameInstance.cpp
void UABGameInstance::Init()

{
    Super::Init();

    WebConnection->TokenCompleteDelegate.BindUObject(this, &UABGameInstance::RequestTokenComplete);
    WebConnection->RequestToken(TEXT("destiny"));
}

3. Delegate를 이용한 비동기로드

언리얼 엔진에서는 비동기로딩 방식을 위해 FStreamableManager 클래스에서 AsyncLoadRequest라는 API를 제공합니다. 이 API에는 결과를 받아오기 위한 딜리게이트 FStreamableDelegate를 전달하는 것이 필요한데, 이 딜리게이트는 StreamableManager.h에 아래와 같이 선언되어 있습니다. 이는 리턴값이 void이고 인자가 없는 함수 형식을 의미합니다.

/** Defines FStreamableDelegate delegate interface */
DECLARE_DELEGATE( FStreamableDelegate );

ABPawn의 멤버 변수로 해당 딜리게이트를 선언하고 이를 전달해도 되지만 아래와 같이 Create로 시작하는 API 셋을 사용하면 딜리게이트를 즉시 생성해 필요한 곳에 전달하는 것이 가능합니다. 아래 코드에서 주석을 처리한 부분과 비교해보시기 바랍니다.


void AABPawn::BeginPlay()
{
    Super::BeginPlay();
    UABGameInstance* ABGameInstance = Cast<UABGameInstance>(GetGameInstance());

    if (ABGameInstance)
    {
        //StreamableDelegate.BindUObject(this, &AABPawn::CharacterMeshDeferred);
        //ABGameInstance->AssetLoader.RequestAsyncLoad(CharacterAssets[NewIndex], StreamableDelegate);

        ABGameInstance->AssetLoader.RequestAsyncLoad(CharacterAssets[NewIndex], FStreamableDelegate::CreateUObject(this, &AABPawn::CharacterMeshDeferred));
    }
}

void AABPawn::CharacterMeshDeferred()
{
    TAssetPtr<USkeletalMesh> NewCharacter(CharacterAssets[NewIndex]);

    if (NewCharacter)
    {
        Mesh->SetSkeletalMesh(NewCharacter.Get());
    }
}

4. Multicast Delegate와 Dynamic Delegate

지금까지 알아본 딜리게이트 선언은 하나의 딜리게이트에 하나의 함수만 연결(Binding)해 실행하는 기능이었습니다.

이번 강좌 도입부에서 설명한 하나의 딜리게이트에 여러 개의 함수를 연결하는 기능은 우리가 지금까지 실습한 DECLARE_DELEGATE 매크로가 아닌 MULTICAST를 붙인 DECLARE_MULTICAST_DELEGATE 매크로를 사용해야 합니다. 그리고 MULTICAST 딜리게이트는 Execute API 대신에 Broadcast API를 사용해야 딜리게이트에 연결된 모든 함수가 실행됩니다. ( Broadcast API에는 BroadcastIfBound라는 함수는 없습니다. 아무 연결이 없으면 그냥 아무일도 안 일어납니다. )

언리얼 엔진에서는 딜리게이트 종류에 MULTICAST 외에도 다이나믹(Dynamic) 딜리게이트라는 것을 제공합니다. 다이나믹 딜리게이트는 함수포인터가 아닌, 함수의 이름을 기반으로 등록해 호출하는 방식입니다. 이름 기반이다보니 저장할 수 있다는 특징이 있습니다만, 반면에 동작이 느리다는 단점이 있습니다. 다이나믹 방식으로 딜리게이트를 선언하려면 인자의 이름까지 정확히 일치해야 합니다. 그래서 딜리게이트의 선언도 함수 인자 하나당 타입과 이름 정보가 들어가야 합니다.
이 다이나믹 딜리게이트 시스템이 필요한 이유는 딜리게이트 시스템에 C++ 함수 뿐만 아니라 블루프린트 함수도 연결할 수 있게 하기 위해서입니다. 하지만 블루프린트에서 사용할 수 있게 하려면 기본적으로 MULTICAST 기능도 지원해주어야 합니다. 따라서 딜리게이트를 블루프린트의 함수와도 연동하고 싶은 경우에는 DYNAMIC과 MULTICAST가 합쳐진 DECLARE_DYNAMIC_MULTICAST_DELEGATE 매크로를 사용해야 합니다.

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTokenCompleteSignature, const FString& , Token);
WebConnection->TokenCompleteDelegate.AddDynamic(this, &UABGameInstance::RequestTokenComplete);
TokenCompleteDelegate.Broadcast(TEXT("0LCJydGkiOiI2a3NjVE9pTUNESVZWM05qVTIyUnlTIn0.VJyMOicM"));

5. 정리

  • 딜리게이트는 함수 포인터 대신 간편한 문법으로 안전하게 호출해주는 대리자 개념. 콜백, 이벤트 구독에 많이 사용됨.
  • 하나의 딜리게이트가 모든 유형의 함수를 커버할 수 없기 때문에 대리할 함수 유형을 매크로로 지정해야 한다.
  • 언리얼 엔진의 입력 시스템은 딜리게이트를 통한 입력 값의 전달 방식으로 이루어져있다.
  • 연결(바인딩)할 함수의 성격에 따라 다양한 API가 존재한다.
  • 딜리게이트형선언::Create~ 함수를 사용해 즉석에서 딜리게이트를 제작해 넘겨주는 것도 가능하다.
  • 같은 유형을 가진 여러 함수를 묶어서 발행/구독 모델처럼 사용할 때는 MULTICAST 계열 매크로를 사용한다.
  • 블루프린트와 연동시에는 DYNAMIC_MULTICAST 계열 매크로를 사용한다.
profile
Game Programming

0개의 댓글