초고교급 희망

[9강] 언리얼 C++ 설계 3 - 델리게이트 본문

Game/Unreal

[9강] 언리얼 C++ 설계 3 - 델리게이트

연모링 2023. 12. 27. 15:44
728x90

 

인프런에서 이득우의 언리얼 프로그래밍 Part 1을 수강하고 작성한 글 입니다.

강의 내용

언리얼 델리게이트를 사용해 클래스 간의 느슨한 결합을 구현하기

강의 목표

  • 느슨한 결합의 장점과 이를 편리하게 구현하도록 도와주는 델리게이트의 이해
  • 발행 구독 디자인 패턴의 이해
  • 언리얼 델리게이트를 활용한 느슨한 결합의 설계와 구현의 학습

강한 결합과 느슨한 결합

  • 강한 결합(Tigth Coupling)
    • 클래스들이 서로 의존성을 가지는 경우
  • 느슨한 결합(Loose Coupling)
    • 실물에 의존하지 말고 추상적 설계에 의존하라. (DIP 원칙)
    • 느슨한 결합 구조는 유지 보수를 손쉽게 만들어줌.

느슨한 결합의 간편한 구현 - 델리게이트(Delegate)

  • 함수를 오브젝트처럼 관리하면 어떨까?
  • 함수를 다루는 방법
    • 함수 포인터를 활용한 콜백(callback) 함수의 구현
    • 가능은 하나 이를 정의하고 사용하는 과정이 꽤나 복잡함
    • 안정성을 스스로 검증해주어야 함
    • C++ 17 규약의 std::bing와 std::function 활용은 느림
  • C#의 델리게이트(delegate) 키워드
    • 함수를 마치 객체처럼 다룰 수 있음
    • 안정적이고 간편한 선언 
  • 언리얼 C++도 델리게이트를 지원함
    • 느슨한 결합 구조를 간편하고 안정적으로 구현할 수 있음

발행 구독 디자인 패턴

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

언리얼 델리게이트(Delegate)

  • 언리얼 엔진은 발행 구독 패턴 구현을 위해 델리게이트 기능을 제공함.
  • 델리게이트의 사전적 의미는 대리자.
    • 예시) 학사정보의 구독과 알림을 대리해주는 객체
  • 시나리오 구현을 위한 설계
    • 학사 정보는 구독과 알림을 대행할 델리게이트를 선언
    • 학생은 학사 정보의 델리게이트를 통해 알림을 구독
    • 학사 정보는 내용 변경 시 델리게이트를 사용해 등록한 학생들에게 알림

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

DECLARE_{델리게이트유형}_DELEGATE{함수정보}
  • 델리게이트 유형 : 어떤 유형의 델리게이트인지
    • DECLARE_DELEGATE : 일대일 형태로 C++만 지원한다면 유형은 공란으로 둔다.
    • DECLARE_MUTICASE : 일대다 형태로 C++만 지원
    • DECLARE_DYNAMIC : 일대일 형태로 블루프린트 지원
    • DECLARE_DYNAMIC_MULTICAST : 일대다 형태로 블루프린트 지원
  • 함수 정보 : 연동 될 함수 형태 지정
    • DECLARE_DELEGATE : 인자가 없고 반환값도 없음
    • DECLARE_DELEGATE_OnreParam : 인자 1개, 반환값 없음
    • DECLARE_DELEGATE _RetVal_ThreeParams : 인자 3개, 반환값 있음 (MUTICAST는 반환값을 지원하지 않음)
    • 파라미터는 최대 9개까지만 지원함

실습 예제

CourseInfo.h

// 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.cpp

// 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, Contents);
}

MyGameInstance.h

// 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()
	TObjectPtr<class UCourseInfo> CourseInfo;

	UPROPERTY()
	FString SchoolName;
};

MyGameInstance.cpp

// 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);

	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("==================="));
}
728x90