UE4

[UE4] 델리게이트(Delegate)

Honey Badger 2022. 10. 19. 15:33

  특정 객체가 해야 할 로직을 다른 객체가 대신 처리할 수 있도록 만드는 보편적인 설계의 개념이다. 언리얼 엔진의 델리게이트는 A객체가 B객체에게 작업 명령을 내릴 때 B객체에 자신을 등록하고 B의 작업이 끝나면 A에게 알려주는 설계방식을 의미한다.

 

로직 순서

1. A의 함수를 B에 등록

2. B에게 A가 명령.

3. B A의 명령을 실행.

4. 1에서 등록한 A의 함수를 호출.

 

애님 인스턴스에서 제공하는 델리게이트

  애님 인스턴스에는 에니메이션 몽타주 재생이 끝나면 발동하는 OnMontageEnded라는 델리게이트를 제공한다. 어떠한 언리얼 오브젝트라도 UAnimMontage* 인자와 bool 인자를 가진 멤버 함수를 가지고 있다면, 이를 OnMontageEnded 델리게이트에 등록해 몽타주 재생이 끝나는 타이밍을 파악할 수 있다.

 

델리게이트의 사용

언리얼에서 델리게이트의 선언은 언리얼이 제공하는 매크로를 통해 정의되며, 이렇게 정의된 델리게이트의 형식을 시그니처라고 한다. 델리게이트 시그니처는 다음 항목들에 대해 지원된다.

 

- 값을 반환하는 함수

- 유상변수(payload) 4개까지 (바인딩된 함수를 불러낼 때 직접 전해지는 임의의 변수)

- 함수 파라미터 8개까지

- const로 선언된 함수

 

델리게이트 선언에 사용할 매크로는 다음 링크에서 찾을 수 있다.

https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/Delegates/

 

델리게이트

C++ 오브젝트 상의 멤버 함수를 가리키고 실행시키는 데이터 유형입니다.

docs.unrealengine.com

 

  델리게이트에 바인딩 된 함수는 델리게이트의 Excute()함수를 호출하여 실행된다. 델리게이트를 실행하기 전 바인딩되어있는지 반드시 확인해야 한다. 이는 코드 안전성을 도모하기 위함인데, 초기화되지 않은 상태로 접근이 가능한 반환값과 출력 파라미터가 델리게이트에 있을 수 있기 때문이다. 델리게이트가 실행해도 안전한지는 IsBound()를 호출하여 검사할 수 있고, 반환값이 없는 델리게이트에 대해서는 ExcuteIfBound()함수를 호출할 수 있으나, 출력 파라미터는 초기화되지 않을 수 있다는 점 주의해야한다.

 

사용예제

class FLogWriter
{
    void WriteToLog( FString );
};

  아무데서나 호출했으면 하는 메서드를 가진 클래스가 있다고 봅시다.

  WriteToLog 함수를 호출하려면, 해당 함수의 시그너처에 맞는 델리게이트 유형을 생성해야 합니다. 그러기 위해서는 먼저 아래 매크로 하나를 사용하여 델리게이트를 선언해야 합니다. 여기 예제에서는 단순한 델리게이트 유형입니다:

DECLARE_DELEGATE_OneParam( FStringDelegate, FString );

  이는 'FStringDelegate' 라는 델리게이트 유형을 생성하며, 'FString' 유형 파라미터를 하나 받습니다.

클래스에서 'FStringDelegate' 어떻게 사용하는가, 예제는 이렇습니다:

class FMyClass
{
    FStringDelegate WriteToLogDelegate;
};

  이렇게 하여 클래스의 임의 클래스 안에 있는 메서드로의 포인터를 담을 있습니다. 클래스가 델리게이트에 대해 아는 것이라고는, 함수 시그너처 뿐입니다.

 

  이제 델리게이트 할당을 위해, 단순히 델리게이트 클래스의 인스턴스를 생성하고, 메서드를 소유하는 클래스와 함께 템플릿 파라미터로 전해줍니다. 자기 오브젝트의 인스턴스와 메서드의 실제 함수 주소 역시도 전해줘야 합니다. 그래서 여기서는 우리 'FLogWriter' 클래스의 인스턴스를 생성한 다음, 오브젝트 인스턴스의 'WriteToLog' 메서드에 대한 델리게이트를 생성하겠습니다.

 

TSharedRef< FLogWriter > LogWriter( new FLogWriter() );

WriteToLogDelegate.BindSP( LogWriter, &FLogWriter::WriteToLog );

이렇게 하여 클래스의 메서드에 델리게이트를 동적으로 바인딩하였습니다!

BindSP  SP 부분은 shared pointer, 공유 포인터를 뜻하는데, 공유 포인터에 소유된 오브젝트에 바인딩하고 있기 때문입니다. BindRaw(), BindUObject() 처럼 다양한 오브젝트 유형 버전도 있습니다.

 

이제 'FLogWriter' 클래스에 대해 아무것도 모를지라도 FMyClass 통해 'WriteToLog' 메서드를 호출할 있습니다! 자신의 델리게이트를 호출하려면, 그냥 'Execute()' 메서드를 사용하면 됩니다:

 

WriteToLogDelegate.Execute( TEXT( "델리게이트에 바인딩된 함수 호출!" ) );

델리게이트에 함수를 바인딩하기 Execute() 호출하면 assert 발동됩니다. 그런 경우를 피하기 위해 대부분 이렇게 하는 것이 좋습니다.

WriteToLogDelegate.ExecuteIfBound( TEXT( "함수가 바인딩되었을 때만 실행!" ) );

 

 

델리게이트 장점

  예를 들어 설명하자면, 애님인스턴스 클래스에서 캐릭터의 콤보 카운트를 전달받아 해당 몽타주 섹션을 재생하고, NextAttackCheck 노티파이가 발생할 때마다 캐릭터에 이를 전달할 델리게이트를 선언하였다고 가정하자. 노티파이 함수에서 이 델리게이트를 호출해주면 애님 인스턴스는 자신의 델리게이트를 사용하는 객체(캐릭터)가 어떤 것인지 몰라도 델리게이트에 연결된 함수만 호출하면 되므로, 다른 클래스와 연결되지 않는 의존성 없는 설계를 할 수 있다는 장점이 생긴다.

 

델리게이트 종류

  언리얼에서 델리게이트는 C++만 사용할 수 있는 델리게이트와 블루프린트 객체도 사용할 수 있는 델리게이트로 나뉜다. 블루프린트 오브젝트는 멤버 함수에 대한 정보를 저장하고 로딩하는 직렬화 메커니즘이 들어있기 때문에 일반 C++ 언어가 관리하는 방법으로 멤버 함수를 관리할 수 없다. 그래서 블루프린트와 관련된 함수는 모두 UFUNCTION 매크로를 사용해야 한다. 이렇게 블루프린트 객체와도 연동하는 델리게이트를 언리얼에서는 다이내믹 델리게이트 라고 한다.

멀티캐스트 델리게이트는 여러 개의 함수를 받을 수 있어서 행동이 끝나면 등록된 모든 함수들에게 모두 알려주는 기능도 제공한다. 멀티캐스트 델리게이트에 등록된 모든 함수를 호출하는 명령은 Broadcast이다.