DELEGATE
델리게이트란 직역하자면 "대리자" 라는 뜻을 가지고 있다.
언리얼에서 Delegate
는 C++오브젝트 상의 멤버 함수 호출을 일반적이고 유형적으로 안전한 방식으로 할 수 있다고 나와있습니다.. 또한 델리게이트를 사용하여 임의 오브젝트의 멤버 함수에 동적으로 바인딩(연결)시킬 수 있으며, 그런 다음 그 오브젝트에서 함수를 호출 할 수 있다고 나옵니다..(호출하는 곳에서 오브젝트의 유형을 알지 못하여도 가능하다.)
또한 델리게이트는 값으로 전달 가능하나 heap 에 메모리를 할당해야 하기 때문에 추천하지는 않으며 가급적이면 델리게이트는 항상 참조 전달해야 한다고 합니다.
DELEGATE의 종류
델리게이트는 싱글-캐스트(형 변환)과 멀티-캐스트 모두 지원되며, 디스크에 안전하게 직렬화(Serialize) 시킬 수 있는 "다이나믹" 델리게이트도 지원합니다.
- Single-cast Delegate:
- 한 번에 하나의 함수만 바인딩(호출)이 가능합니다.
- 이미 다른 함수가 바인딩된 상태에서 새로운 함수를 바인딩하면 이전 함수가 대체됩니다.
- 주로 특정 작업을 단일 객체가 처리해야 하는 경우에 사용됩니다.
- 사용 예제
DECLARE_DELEGATE(FMySingleDelegate); // 델리게이트 선언
void MyFunction() {
UE_LOG(LogTemp, Warning, TEXT("싱글캐스트 델리게이트 호출됨!"));
}
// 바인딩 및 호출
FMySingleDelegate MyDelegate;
MyDelegate.BindStatic(&MyFunction); // 단일 함수 바인딩
MyDelegate.Execute(); // 호출 시 MyFunction 실행
- 사용 용도
- 특정 작업에 대한 단일 처리 함수가 필요할 때.
- 이벤트가 발생했을 때 정확히 한 개의 함수 호출만 필요할 때.
- Multi-cast Delegate:
- 여러 개의 함수 바인딩이 가능합니다.
- 바인딩된 모든 함수가 호출됩니다.
- 주로 한 이벤트에 대해 여러 객체가 반응해야 하는 경우에 사용됩니다.
DECLARE_MULTICAST_DELEGATE(FMyMultiDelegate);
void FunctionA() {
UE_LOG(LogTemp, Warning, TEXT("FunctionA 호출됨!"));
}
void FunctionB() {
UE_LOG(LogTemp, Warning, TEXT("FunctionB 호출됨!"));
}
// 바인딩 및 호출
FMyMultiDelegate MyDelegate;
MyDelegate.AddStatic(&FunctionA); // 여러 함수 추가 가능
MyDelegate.AddStatic(&FunctionB);
MyDelegate.Broadcast(); // 호출 시 모든 바인딩된 함수 실행
사용 용도
- 이벤트가 발생했을 때 여러 함수가 호출 되어야 하는 경우
- UI업데이트, 이펙트 발생, 소리 재생 등 다양한 동작이 동시에 필요할 때.
싱글캐스트와 멀티캐스트의 주요 차이점
특징 | Single-cast | Multi-cast |
바인딩 가능한 함수 개수 | 하나 | 여러개 |
기존 함수 덮어쓰기 여부 | 기존 함수 대체 | 추가로 바인딩 |
호출 방식 | 바인딩된 단일 함수 실행 | 바인딩된 모든 함수 실행 |
사용 용도 | 특정 작업에 대한 단일 함수 호출 | 이벤트 발생 시 여러 반응 동시 처리 |
싱글 캐스트는 한 개의 함수만 바인딩 및 실행 멀티 캐스트는 여러 개의 함수를 바인딩하고 모두 실행 사용 목적과 상황에 따라서 적합한 델리게이트를 선언하면 되겠습니다.
DELEGATE 선언시 고려사항
어떤 데이터를 전달하고 받을 것인가? 인자의 갯수와 타입의 설계
- 몇 개의 인자를 전달할 것인가?
- 어떤 방식으로 전달할 것인가?
- 일대일로 전달할 것인지?
- 일대다로 전달할 것인지?
해당 방식들은 Single-cast , Multi-cast의 선언 방식으로 정할 수 있습니다.
프로그래밍 환경 설정
- C++ 프로그래밍에서만 사용할 것인가?
- UFUNCTION()으로 지정된 블루프린트 함수와 사용할 것인가?
어떤 함수와 연결할 것인가?
- 클래스 외부에 설계된 C++ 함수와 연결
- 전역에 설계된 정적 함수와 연결
- 언리얼 오브젝트의 멤버 함수와 연결
DELEGATE 선언
DECLARE_(델리게이트 유형)_DELEGATE_(인자 갯수)
델리게이트 선언은 이미지 제공 되어 있는 선언 매크로 중 하나를 사용하여야 한다. 사용되는 매크로는 델리게이트에 바인딩 되는 함수의 시그니처에 따라 결정됩니다. 시스템에서는 델리게이트 유형을 선언해 낼 수 있는 범용 함수 시그니처의 다양한 조합을 미리 정의, 이를 통해 반환값과 파라미터에 대한 유형 이름을 필요한대로 채워넣는다. 다음 항목들의 대해서 델리게이트 시그니처가 지원되고 있습니다..
- 값을 반환하는 함수 : 선언 매크로에 DECLARE_DELEGATE_RetVal_... RetVal 추가로 지원합니다.
- "페이로드"(payload, 유상) 변수 4개 까지
- 함수 파라미터 8 개 까지
- 'const'로 선언된 함수
이제 각각의 Single-cast, Multi-cast, Dynamic-cast의 선언 방식을 자세히 알아봅시다.
델리게이트 선언하기
함수 시그니처 | 선언 매크로 |
void Function() | DECLARE_DELEGATE( DelegateName ) |
void Function( <Param1> ) | DECLARE_DELEGATE_OneParam( DelegateName, Param1Type ) |
void Function( <Param1>, <Param2> ) | DECLARE_DELEGATE_TwoParams( DelegateName, Param1Type, Param2Type ) |
void Function( ... , <NumParam> ) | DECLARE_DELEGATE_<Num>Params( DelegateName, , ... NumType ) ) |
<RetVal> Function() | DECLARE_DELEGATE_RetVal( RetValType, DelegateName ) |
<RetVal> Function( <Param1> ) | DECLARE_DELEGATE_RetVal_OneParam( RetValType, DelegateName, Param1Type ) |
<RetVal> Function( <Param1>, <Param2> ) | DECLARE_DELEGATE_RetVal_TwoParams( RetValType, DelegateName, Param1Type, Param2Type ) |
<RetVal> Function( ... , <NumParam> ) | DECLARE_DELEGATE_RetVal_<Num>Params( RetValType, DelegateName, ... NumType ) |
Multi-cast와 Dynamic-cast 델리게이트 선언은 (델리게이트 유형) 부분에 MULTICAST와 DYNAMIC선언을 추가해주면 되겠습니다.
선언 매크로 | 설명 |
DECLARE_DYNAMIC_DELEGATE (DelegateName \) | 다이나믹 델리게이트 생성 |
DECLARE_MULTICAST_DELEGATE (DelegateName \) | 멀티캐스트 델리게이트 생성 |
DECLARE_DYNAMIC_MULTICAST_DELEGATE (DelegateName \) | 다이나믹 멀티캐스트 델리게이트 생성 |
DELEGATE 바인딩 하기
언리얼 공식 문서에서 델리게이트의 바인딩의 특징은 이렇게 말하고 있다.
- 델리게이트 시스템은 특정 유형의 오브젝트를 이해하고 있으며, 이러한 오브젝트를 사용할 때는 추가적으로 사용할 수 있는 기능이 있습니다. UObject 나 공유 포인터 클래스 멤버에 델리게이트를 바인딩하는 경우, 델리게이트 시스템은 그 오브젝트에 대한 약한 레퍼런스를 유지할 수 있어, 델리게이트 치하에서 오브젝트가 소멸된 경우 IsBound() 나 ExecuteIfBound() 함수를 호출하여 처리해 줄 수 있습니다. 참고로 여러가지 지원되는 오브젝트 유형에 대해서는 특수한 바인딩 문법이 사용됩니다.
1. 특정 유형의 오브젝트를 이해하고 있음
- 델리게이트는 UObject 또는 **공유 포인터(Smart Pointer)**와 같은 특정 유형의 오브젝트를 처리하는 데 특화되어 있습니다.
- 이러한 오브젝트를 델리게이트에 바인딩할 때, 오브젝트의 수명과 관련된 처리를 자동으로 관리해 줍니다..
2. 약한 레퍼런스(Weak Reference)
- 델리게이트는 바인딩된 오브젝트에 대해 **약한 참조(Weak Reference)**를 유지합니다.
- 약한 참조란, 오브젝트가 삭제되었을 때 이를 자동으로 감지하여, 더 이상 참조되지 않도록 처리하는 메커니즘입니다.
- 이는 UObject와 같이 가비지 컬렉션(Garbage Collection) 대상이 되는 오브젝트나, 스마트 포인터로 관리되는 오브젝트에 매우 유용합니다.
3. 오브젝트가 소멸된 경우
- 델리게이트에 바인딩된 오브젝트가 소멸되었더라도 델리게이트 시스템은 이를 감지할 수 있습니다.
- 예를 들어, 오브젝트가 삭제된 후 델리게이트를 실행하려고 하면 크래시가 날 수 있습니다. 이를 방지하기 위해 델리게이트 시스템은 오브젝트의 상태를 확인할 수 있는 메서드를 제공합니다:
- IsBound(): 델리게이트가 현재 유효한 상태인지 확인.
- ExecuteIfBound(): 델리게이트가 유효하면 실행하고, 그렇지 않으면 아무것도 하지 않음.
- 예를 들어, 오브젝트가 삭제된 후 델리게이트를 실행하려고 하면 크래시가 날 수 있습니다. 이를 방지하기 위해 델리게이트 시스템은 오브젝트의 상태를 확인할 수 있는 메서드를 제공합니다:
4. 특수한 바인딩 문법
- 델리게이트에 바인딩할 때 특수한 문법이 사용됩니다. 이 문법은 바인딩하려는 오브젝트 유형에 따라 달라집니다.
- 예를 들어, UObject나 스마트 포인터를 바인딩할 때는 안전한 참조를 보장하기 위해 다음과 같은 방식이 사용됩니다:
MyDelegate.BindUObject(this, &MyClass::MyFunction); // UObject에 바인딩
MyDelegate.BindSP(MySharedPointer, &MyClass::MyFunction); // 공유 포인터에 바인딩
- 이 방식은 오브젝트의 소멸 여부를 추적하기 때문에 안전한 호출이 가능합니다.
델리게이트 바인딩 함수
- Single-cast 델리게이트
함수 | 설명 |
Bind() | 기존 델리게이트 오브젝트에 바인딩합니다. |
BindStatic() | raw C++ 포인터 글로벌 함수 델리게이트를 바인딩합니다. |
BindRaw() | 날(raw) C++ 포인터 델리게이트에 바인딩합니다. 만약 오브젝트가 델리게이트 치하에서 삭제된 경우 호출하기가 안전 하지 않을 수도 있습니다. 호출시 조심 |
BindSP() | 공유 포인터-기반 멤버 함수 델리게이트에 바인딩합니다. ExecuteIfBound() 로 호출 할 수 있습니다. |
BindUObject() | UObject 기반 멤버 함수 델리게이트를 바인딩합니다. ExecuteIfBound() 로 호출 할 수 있습니다. |
UnBind() | 델리게이트 바인딩을 해재 합니다. |
- Multi-cast 델리게이트
함수 | 설명 |
Add() | 멀티캐스트 델리게이트의 실행 목록에 함수 델리게이트를 추가합니다. |
AddStatic() | raw C++ 포인터 글로벌 함수 델리게이트를 추가합니다. |
AddRaw() | 날(raw) C++ 포인터 델리게이트에 바인딩합니다. 만약 오브젝트가 델리게이트 치하에서 삭제된 경우 호출하기가 안전 하지 않을 수도 있습니다.호출시 조심 |
AddSP() | 공유 포인터 기반 (빠르지만 스레드 안전성이 떨어진다) 멤버 함수 델리게이트를 추가합니다. 약한 레퍼런스를 유지 합니다. |
AddUObject() | UObject 기반 멤버 함수 델리게이트를 추가합니다. 약한 레퍼런스를 유지합니다. |
Remove() | 이 멀티캐스트 델리게이트의 실행 목록에서 함수를 제거합니다. 델리게이트의 순서는 유지되지 않을 수 있습니다. |
RemoveAll() | 저장된 UserObject에 바인딩된 이 멀티캐스트 델리게이트의 실행 목록에서 모든 함수를 제거 합니다. |
- Dynamic-cast 델리게이트
함수 | 설명 |
BindDynamic(UserObject, FuncName) | |
AddDynamic(UserObject,FuncName) | |
RemoveDynamic(UserObject, FuncName) |
델리게이트 실행하기
델리게이트에 바인딩된 함수는 델리게이트의 Execute() 함수를 호출하여 실행됩니다. 델리게이트를 실행하기 전 바인딩 되어 있는지 반디시 확인해야 하며 이는 코드 안전성을 도모하기 위함입니다. 초기화 되지 않은 상태로 접근이 가능한 반환값과 파라미터가 델리게이트에 있을 수 있기 때문입니다. 바인딩 되지 않은 델리게이트를 실행시키면 일부 인스턴스에서 메모리를 낭비 할 수 있으며 델리게이트가 실행 하여도 안전한지는 IsBound() 를 호출하여 검사 해 볼 수 있습니다. 또한 반환값이 없는 델리게이트에 대해서는 ExecutIfBound() 를 호출할 수 있으나, 출력 파라미터는 초기화 되지 않을 수 있다는 점을 주의 하여야 합니다.
실행 함수 | 설명 |
Execute() | |
ExecuteIfBound() | |
IsBound() | |
Broadcast() |
델리게이트 선언 예제
- WeaponComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "../CGameState.h"
#include "CWeaponComponent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWeaponTypeChanged, EWeaponState, InPrevState, EWeaponState, InNewState);
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class ECLIPSERA_API UCWeaponComponent : public UActorComponent
{
...
public:
FWeaponTypeChanged OnWeaponTypeChanged; // 상태변환 델리게이트 선언
}
EWeaponState는 무기의 상태를 가지고 있는 Enum 선언이고 WeaponComponent에서 무기의 장착 혹은 교체와 같은 무기를 들고 있는 상태에 따른 변화에 대해 델리게이트를 활용하여서 이벤트를 전달하려고 합니다. 현재 선언문인 DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(...) 보면 해당 델리게이트는 다이나믹 멀티 캐스트 델리게이트이며 요구하는 파라미터의 갯수는 두개인걸 확인 할 수 있습니다.
- WeaponComponent.cpp
#include "Components/CWeaponComponent.h"
...
void UCWeaponComponent::SetState(EWeaponState InState)
{
// 무기 장착 로직
if (OnWeaponTypeChanged.IsBound()) // AnimInst State Changed
OnWeaponTypeChanged.Broadcast(PrevType, CurrentType); //
}
cpp 소스에 선언되어 있는 SetState가 호출되게 되면 무기 장착 로직을 실행한 후에 `if (OnWeaponTypeChaged.IsBound()`를 통하여 델리게이트가 바인딩 즉, 연결 되어 있는지 확인하고 if문을 통과하게 되면 Multicast로 선언되어 있기에 OnWeaponTypeChaged의 Broadcast를 통해 두개의 인자를 전달합니다.
- StateComponent.cpp
#include "Components/CStateComponent.h"
#include "Components/CWeaponComponent.h"
UCStateComponent::UCStateComponent()
{
OwnerCharacter = Cast<ACPlayer>(GetOwner());
UCWeaponComponent* Weapon
Weapon = Cast<CWeaponComponent>(OwnerCharacter->GetComponentByClass(UCWeaponComponent::StaticClass()));
// 델리게이트 구독
Weapon->OnWeaponTypeChanged.AddDynamic(this, &UCStateComponent::OnWeaponTypeChanged);
}
void UCStateComponent::OnWeaponTypeChanged(EWeaponState InPrevState, EWeaponState InNewState)
{
if (InNewState != EWeaponState::Max)
{
bEquip = true;
CLog::Log("UCStateComponent true");
}
else if (InNewState == EWeaponState::Max)
{
bEquip = false;
CLog::Log("UCStateComponent fasle");
}
}
WeaponComponent에서 발생시킨 델리게이트인 OnWeaponTypeChaged가 이벤트를 발생하면 StateComponent의 Weapon->OnWeaponTypeChanged.AddDynamic(this, &UCStateComponent::OnWeaponTypeChanged); 선언 부분에서 이벤트를 받아오며 .AddDynamic 통해 UCStateComponent안에 있는 OnWeaponTypeChaged 함수에 TwoParams 즉, 두개의 인자를 전달하게 됩니다.
'Unreal > Unreal For C++' 카테고리의 다른 글
FMath (0) | 2025.02.01 |
---|---|
SERIALIZATION (직렬화) (0) | 2025.01.31 |