Arknights EP - [Confront]
Categories
Tags
1795 words
9 minutes
[UE5/Part1] 9. Delegate & Pub/Sub
“이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해” 학습 내용을 정리한 강의 노트입니다.
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.
강의 목표
- 느슨한 결합의 장점과 이를 편리하게 구현하도록 도와주는 델리게이트의 이해
- 발행 구독 디자인 패턴의 이해
- 언리얼 델리게이트를 활용한 느슨한 결합의 설계와 구현의 학습
Delegate
이전에 배운 컴포지션(Composition)은 강한 결합 관계를 가진다.
델리게이트(Delegate)는 약한 결합 관계를 가지며, 주로 구독자 패턴(Observer Pattern)을 구현할 때 사용한다.
- 언리얼 Delegate 공식 문서
발행 구독 디자인 패턴 (Pub/Sub)

- 푸시(Push) 형태의 알림(Notification)을 구현하는데 적합한 디자인 패턴
발행자(Publisher)와구독자(Subscriber)로 구분- 콘텐츠 제작자는 콘텐츠를 생산한다.
- 발행자는 콘텐츠를 배포한다.
- 구독자는 배포된 콘텐츠를 받아 소비한다.
- 제작자와 구독자가 서로를 몰라도, 발행자를 통해 콘텐츠를 생산하고 전달할 수 있다.
- 장점
- 제작자, 구독자는 서로를 모르기 때문에
느슨한 결합(Loose Coupling)으로 구성된다. - 유지 보수가 쉽고, 유연하게 활용될 수 있으며, 테스트가 쉬워진다.
- 시스템 스케일을 유연하게 조절할 수 있으며(Scalability), 기능 확장(Extensibility)이 용이하다.
- 제작자, 구독자는 서로를 모르기 때문에
실습 시나리오

학사정보 (CourseInfo)학생(Student)- 학교는 학사 정보를 관리한다.
- 학사 정보가 변경되면 자동으로 학생에게 공지한다.
- 학생은 학사 정보의 알림 구독을 해지할 수 있다.
- 시나리오 흐름
- 학사 정보 시스템과 3명의 학생이 존재한다.
- 시스템에서 학사 정보를 변경한다.
- 학사 정보가 변경되면 알림을 구독한 학생들에게 변경 내용을 자동으로 전달한다.
언리얼 델리게이트 선언 시 고려사항
인자의 수와 각각의 타입을 설계
- 일대일?
- 일대다?
프로그래밍 환경 설정
- Only C++?
- UFUNCTION으로 지정된 블루프린트(BP) 함수와 함께 사용?
어떤 함수와 연결할 것인가?
- 클래스 외부에 설계된 C++ 함수?
- 전역에 설계된 정적 함수?
- 언리얼 오브젝트의 멤버 함수? (보통 이 방식을 사용)
매크로 구조
DECLARE_{델리게이트유형}DELEGATE{함수정보}
델리게이트 유형
DECLARE_DELEGATE(일대일)DECLARE_MULTICAST(일대다)DECLARE_DYNAMIC(블루프린트 연동, 일대일)DECLARE_DYNAMIC_MULTICAST(블루프린트 연동, 일대다)
함수 정보
DECLARE_DELEGATEDECLARE_DELEGATE_OneParamDECLARE_DELEGATE_RetVal_ThreeParams- 최대 9개 파라미터까지 지원
실습
설계 내용
- 학사정보 변경 시
학교명,내용을 학생에게 전달해야 함 -> 2개의 인자 - 변경된 학사 정보는 다수 인원을 대상으로 발송해야 함 -> MULTICAST
- 오직 C++ 프로그래밍에서만 사용 -> DYNAMIC 사용 X
결론: DECLARE_MULTICAST_DELEGATE_TwoParams 매크로 사용
1. CourseInfo
- 발행자 (Publisher) 역할
- 델리게이트를 정의하고, 학사 정보가 변경될 때
Broadcast로 알림을 발행한다.
CourseInfo.h
// delegate 매크로 선언
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.cpp
UCourseInfo::UCourseInfo()
{
Contents = TEXT("기존 학사 정보");
}
void UCourseInfo::ChangeCourseInfo(const FString& InSchoolName, const FString& InNewContents)
{
// 5. 학사 정보 내용 업데이트
Contents = InNewContents;
UE_LOG(LogTemp, Log, TEXT("[CourseInfo] 학사 정보가 변경되어 알림을 발송합니다."));
// 6. 델리게이트를 통해 알림 발행 (구독자들에게 방송)
OnChanged.Broadcast(InSchoolName, Contents);
}2. Student
- 구독자 (Subscriber) 역할
- 발행자(CourseInfo)가 알림을 보내면 자신이 등록한 함수(
GetNotification)를 자동으로 호출받는다.
Student.h
UCLASS()
class UNREALDELEGATE_API UStudent : public UObject
{
GENERATED_BODY()
public:
// 알림을 받을 함수
void GetNotification(const FString& School, const FString& NewCourseInfo);
private:
FString Name;
};Student.cpp
// 7. 모든 구독자 학생들이 알림을 받음
void UStudent::GetNotification(const FString& School, const FString& NewCourseInfo)
{
UE_LOG(LogTemp, Log, TEXT("[Student] %s 님이 %s로부터 받은 메시지 : %s"), *Name, *School, *NewCourseInfo);
}3. MyGameInstance
- 조율자 (Coordinator) 역할
- 발행자와 구독자를 연결해주는 역할을 한다.
CourseInfo를 생성Student들을 생성한다. (여기서는 3개)- Student들을 CourseInfo의 델리게이트에 바인딩(구독)시킨다.
- 유일하게 두 클래스(
CourseInfo,Student)를 모두include한다.
MyGameInstance.h
private:
UPROPERTY()
TObjectPtr<class UCourseInfo> CourseInfo;MyGameInstance.cpp
void UMyGameInstance::Init()
{
Super::Init();
// CourseInfo는 MyGameInstance의 SubObject가 되고
// MyGameInstance는 CourseInfo의 Outer가 됨
// 1. Composition 관계로 CourseInfo 생성 (발행자)
CourseInfo = NewObject<UCourseInfo>(this);
UE_LOG(LogTemp, Log, TEXT("==============================="));
// 2. Student 객체 생성 (구독자)
UStudent* Student1 = NewObject<UStudent>(this);
Student1->SetName(TEXT("학생1"));
UStudent* Student2 = NewObject<UStudent>(this);
Student2->SetName(TEXT("학생2"));
UStudent* Student3 = NewObject<UStudent>(this);
Student3->SetName(TEXT("학생3"));
// 3. 모든 학생을 구독자로 등록 (Binding)
CourseInfo->OnChanged.AddUObject(Student1, &UStudent::GetNotification);
CourseInfo->OnChanged.AddUObject(Student2, &UStudent::GetNotification);
CourseInfo->OnChanged.AddUObject(Student3, &UStudent::GetNotification);
FString SchoolName = TEXT("언리얼대학교");
// 4. 발행자에게 정보 변경 요청 (이벤트 발생 트리거)
CourseInfo->ChangeCourseInfo(SchoolName, TEXT("변경된 학사 정보"));
}출력 결과
LogTemp: ===============================
LogTemp: [CourseInfo] 학사 정보가 변경되어 알림을 발송합니다.
LogTemp: [Student] 학생3 님이 언리얼대학교로부터 받은 메시지 : 변경된 학사 정보
LogTemp: [Student] 학생2 님이 언리얼대학교로부터 받은 메시지 : 변경된 학사 정보
LogTemp: [Student] 학생1 님이 언리얼대학교로부터 받은 메시지 : 변경된 학사 정보전체 흐름 요약
MyGameInstance:CourseInfo생성 (발행자 준비)MyGameInstance:Student객체 생성 (구독자 준비)MyGameInstance: 학생들을CourseInfo.OnChanged델리게이트에 구독자로 등록 (AddUObject)CourseInfo: 학사 정보 변경 시 델리게이트Broadcast실행 (모든 학생에게 발행)Student: 등록된 함수GetNotification자동 호출 (구독자 알림 수신)
정리
델리게이트 vs 컴포지션
- 델리게이트: 느슨한 결합 (기능적 의존성 제거, 구독자 패턴 활용)
- 컴포지션: 강한 결합 (객체의 생성과 소멸 주기를 완전히 소유)
느슨한 결합 (Loose Coupling)의 장점
- 향후 시스템 변경 사항에 대해 손쉽게 대처할 수 있다.
- 발행-구독 모델의 이점
- 클래스는 자신이 해야 하는 작업에만 집중 가능
- 외부 변경 사항에 영향을 받지 않음
- 자신의 기능을 확장해도 다른 모듈에 영향을 주지 않음
언리얼 C++ 델리게이트 선언시 생각할 점
적합한 매크로를 선택하기 위해 다음을 고려한다
- 인자의 개수: 파라미터가 몇 개 필요한가?
- 동작 방식: 1:1 통신인가, 1
(Multicast) 통신인가? - 블루프린트 연동: 에디터/BP에서 접근이 필요한가? (Dynamic 필요 여부)
기타: UML에서의 표기
- 따로 번외편에서 정리.

