다양한 기록

[UE] Delegate (델리게이트) / 발행 구독 패턴 본문

언리얼 엔진/Unreal C++

[UE] Delegate (델리게이트) / 발행 구독 패턴

라구넹 2024. 12. 26. 17:45

클래스 간 느슨한 결합 (Loose Coupling) 구현에 편리함

- 추상적 설계에 의존

 

인터페이스를 쓰는 등의 방법으로 느슨한 결합의 구현이 가능

그런데 좀 귀찮을 수 있음 -> 어떤 함수를 오브젝트처럼 관리 .. 델리게이트

C#의 델리게이트 생각하면 됨

* C의 함수 포인터보다 안전한 버전 느낌


콘텐츠 제작자는 생산하고, 자신을 대신하여 발행할 발행자를 생성

구독자는 발행자한테 받아서 소비

이때 델리게이트를 발행자로 하고, 제작자와 구독자를 엮음

* 순환 참조 안하도록 주의

 

언리얼 델리게이트 선언 매크로

DECLEAR_{델리게이트유형}DELEGATE{함수정보}

델리게이트 유형 (구독자의 수가 많으면 멀티)

- DECLEAR_DELEGATE - 1:1형태로 C++만 지원함

- DECLEAR_MULTICAST - 1:N 형태로 C++만 지원함

- DECLEAR_DYNAMIC - 1:1 형태로 블루프린트 지원

- DECLEAR_DYNAMIC_MULTICAST - 1:N 형태로 블루프린트 지원

 

함수 정보

- 인자 없고 반환값 없으면 비워둠  .. DECLEAR_DELEGATE

- 인자가 하나고 반환값이 없으면 OneParam  .. DECLEAR_DELEGATE_OneParam

- 인자가 세개고 반환값이 있으면 RetVal_ThreeParams  .. DECLEAR_DELEGATE_ RetVal_ThreeParams

** 멀티캐스트는 반환값 지원 안함

** 인자 최대 9개 까지됨


// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "CourseInfo.generated.h"

DECLARE_MULTICAST_DELEGATE_TwoParams(FCourseInfoOnChangedSignature, const FString&, const FString&);

/**
 * 
 */
UCLASS()
class UNREALDELEGATE_API UCourseInfo : public UObject
{
	GENERATED_BODY()

public:
	UCourseInfo();

	FCourseInfoOnChangedSignature OnChanged;
	void ChangeCourseInfo(const FString& InSchoolName, const FString& InNewContents);

private:
	FString Contents;
};

CourseInfo.h

발행자로서 델리게이트를 선언해서 가지고 있음

FCourseInfoOnChangedSignature 라는 이름은?

- OnChanged -> 변경이라는 이벤트 발생

- Signature는 관행임

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "CourseInfo.h"

UCourseInfo::UCourseInfo()
{
	Contents = TEXT("기본 학사 정보");
}

void UCourseInfo::ChangeCourseInfo(const FString& InSchoolName, const FString& InNewContents)
{
	Contents = InNewContents;

	UE_LOG(LogTemp, Log, TEXT("[CourseInfo] 학사 정보가 변경되어 알림을 발송합니다."));
	OnChanged.Broadcast(InSchoolName, InNewContents);
}

ChangeCourseInfo 호출 시 OnChanged에서 브로드캐스트 -> 구독하는 객체들의 함수들이 불리게 됨

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "MyGameInstance.generated.h"

/**
 * 
 */
UCLASS()
class UNREALDELEGATE_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
	
public:
	UMyGameInstance();
	virtual void Init() override;
private:
	UPROPERTY()
	FString SchoolName;
	UPROPERTY()
	TObjectPtr<class UCourseInfo> CourseInfo;
};

도와주는 객체로 게임 인스턴스 사용

게임 인스턴스에서 CourseInfo를 내부 변수로 가지고

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "MyGameInstance.h"
#include "Student.h"
#include "Teacher.h"
#include "Staff.h"
#include "Card.h"
#include "CourseInfo.h"

UMyGameInstance::UMyGameInstance()
{
	SchoolName = TEXT("학교");
}

void UMyGameInstance::Init()
{
	Super:: Init();

	CourseInfo = NewObject<UCourseInfo>(this);
	
	TArray<UPerson*> Persons = { NewObject<UStudent>(), NewObject<UTeacher>(), NewObject<UStaff>() };

	UE_LOG(LogTemp, Log, TEXT("=================================="));
	
	UStudent* Student1 = NewObject<UStudent>();
	Student1->SetName(TEXT("학생1"));
	UStudent* Student2 = NewObject<UStudent>();
	Student2->SetName(TEXT("학생2"));
	UStudent* Student3 = NewObject<UStudent>();
	Student3->SetName(TEXT("학생3"));

	CourseInfo->OnChanged.AddUObject(Student1, &UStudent::GetNotification);
	CourseInfo->OnChanged.AddUObject(Student2, &UStudent::GetNotification);
	CourseInfo->OnChanged.AddUObject(Student3, &UStudent::GetNotification);

	CourseInfo->ChangeCourseInfo(SchoolName, TEXT("변경된 학사 정보"));

	UE_LOG(LogTemp, Log, TEXT("=================================="));

}

CourseInfo는 해당 객체가 들고 있는 거라 메모리 관리 필요 .. -> 아우터 지정해서 서브 오브젝트로 지정

Student들은 내부에서 선언된거라 알아서 사라짐

 

중요한 건 OnChanged.AddUObject

오브젝트의 함수를 델리게이트에 추가시킬 수 있음

void UStudent::GetNotification(const FString& School, const FString& NewContents)
{
	UE_LOG(LogTemp, Log, TEXT("[Student] %s님이 %s님으로부터 받은 메시지: %s"),
		*Name, *School, *NewContents);
}

Student의 GetNotification은 이렇게 되어 있음