[UE5] 언리얼 C++의 델리게이트

seunghyun·2024년 3월 19일
2

UE 5

목록 보기
2/5

이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해 강의를 수강한 후 정리한 내용입니다.


💡 강의 목표

🖥️ 언리얼 C++의 델리게이트를 활용한 발행 구독 디자인 패턴(Publisher-Subscriber Pattern)의 구현

  • 모던 객체 지향 설계에서 느슨한 결합의 장점과 이를 편리하게 구현하도록 도와주는 델리게이트의 이해
  • 발행 구독 디자인 패턴의 이해
  • 발행 구독 디자인 패턴을 구현하면서, 언리얼 델리게이트를 활용한 느슨한 결합의 설계 방법을 학습

💡 느슨한 결합 (Loose Coupling)

강한 결합(Loose Coupling) 과 느슨한 결합(Loose Coupling)

  • 강한 결합
    • 클래스들이 서로 의존성을 가지는 경우를 말한다.
    • 1_8강 강의 예제에서 Person 클래스는 Card 클래스를 가졌는데, 이 때 Person 클래스가 Card 클래스에 대한 의존성을 가졌다.
      • 만약 Card 클래스 대신 핸드폰 인증 서비스가 도입된다면? Peson 클래스뿐만 아니라 이를 상속받는 서브 클래스의 수정이 불가피해진다.
  • 느슨한 결합
    • 실물에 의존하지 말고 추상적 설계에 의존하라. (DIP 원칙)
    • 왜 Person은 Card가 필요한가? 출입을 확인해야 하기 때문.
    • 출입에 관련된 추상적인 설계에 의존하자. (⇒행동에 중심을 둔 추상화 작업)
    • ICheck 를 상속받은 새로운 카드 인터페이스를 선언해 해결한다. (카드가 구현하도록)
      • 그럼 Person은 더 이상 Card에 의존하지 않아도 되고, ICheck에 의존하면 된다.
    • 이러한 느슨한 결합 구조는 유지 보수를 손쉽게 만들어준다.
    • ICheck 인터페이스를 상속받는 출입도구 클래스가 check() 가상함수를 구현하도록 하면, Person 클래스에서는 수정이 필요 없다.
  • 그러나 이처럼 매번 인터페이스를 만드는 것도 번거로울 수 있으니, 행동을 객체처럼 관리해보자는 아이디어가 등장했다.
    • C++에서 함수 포인터를 이용했던 것 대신, 언리얼 C++에서는 델리게이트(Delegate)를 제공해준다.

💡 델리게이트란?

의미

델리게이트는 한국어로 대리자를 의미한다. 델리게이트는 함수를 참조하는 대리자이다. 함수가 아니라 함수 자체를 인자로 넘겨주는 ‘형식’이라는 것에 유의하자.

대리 실행이 왜 필요할까? 클래스 사이 의존성을 약화(=느슨한 결합) 시킬 수 있기 때문이다.

델리게이트

Delegate (델리게이트)로 C++ 오브젝트 상의 멤버 함수 호출을 일반적이고 유형적으로 안전한 방식으로 할 수 있습니다. 델리게이트를 사용하여 임의 오브젝트의 멤버 함수에 동적으로 바인딩시킬 수 있으며, 그런 다음 그 오브젝트에서 함수를 호출할 수 있습니다. 호출하는 곳에서 오브젝트의 유형을 몰라도 말이지요.

위 공식 문서에서 눈에 띄는 특징이 있다면 델리게이트의 안전성에 대해 강조하고 있다는 점. 함수 포인터보다 안정화 되어있고, 항상 참조로 전달해야 함을 알 수 있다.

사용 예시 🕹️

게임 개발에서의 예시로 어떤 것이 있을까 생각해보았다.

델리게이트가 N개의 함수를 대리한다고 했는데, 일종의 ⛓️체인 효과⛓️ 를 줄 수 있다고 생각한다.

예를 들어, Monster클래스가 있다. Monster는 체력(HP)를 가지고 있다. Monster는 플레이어에게 공격을 받아 HP가 깎일 수 있다.

이 때, Monster의 체력바와 피가 튀는 이펙트가 출력된다고 가정하자면,

구현하기 나름이겠지만 체력바는 Monster의 HP가 바뀔 때만 다시 그리면 되고, 피가 튀는 이펙트도 HP가 깎일 때 만들어지면 된다.

이럴 때 그 클래스들은 Monster의 OnDamaged 델리게이트에 해당 함수를 바인드하면 된다. 

코드 예시

void 를 반환하고 FString 을 인자로 가지는 함수 타입을 객체처럼 사용하고 싶다.

class FLogWriter
{
    void WriteToLog( FString );
};

그럴 때는 DECLARE_DELEGATE_OneParam 라는 매크로를 사용해서 위 함수의 형태를 객체처럼 지정한다.

FString 을 인자로 가지는 FStringDelegate 을 만들겠다!

DECLARE_DELEGATE_OneParam( FStringDelegate, FString );

객체처럼 사용하고 싶다고 했으니, 델리게이트 FStringDelegate 을 멤버 변수에 등록해 사용한다.

class FMyClass
{
    FStringDelegate WriteToLogDelegate;
};

엮고자 하는 클래스의 멤버함수를 묶어주는 함수가 BindSP 이다.

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

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

묶은 다음 실행할 때는 Execute 를 사용한다.

이렇게 하면 연결된 객체 정보를 몰라도 원하는 함수를 호출할 수 있게 된다.

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

델리게이트에 함수를 바인딩하기 전 Execute()를 호출하면 assert 가 발동되는데, 이런 경우를 피하고 싶다면 아래와 하는 방법이 있다.

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

관련 함수

공식 문서 페이지에서 찾아보았다.

함수명설명비고
Execute()델리게이트에 바인딩된 함수를 실행하는 함수다. 호출 전에 isBound() 함수를 이용해 델리게이트가 바인딩되어 있는지 확인해야 한다. Single-cast 델리게이트와 Multi-cast 델리게이트 모두에서 사용할 수 있다.델리게이트 실행하기
ExecuteIfBound()델리게이트에 바인딩된 함수를 실행하는 함수다. isBound() 함수를 호출하지 않아도 되며, Single-cast 델리게이트와 Multi-cast 델리게이트 모두에서 사용할 수 있다.델리게이트 실행하기
isBound()델리게이트가 바인딩되어 있는지를 확인하는 함수다. 바인딩되어 있지 않은 델리게이트를 호출하면 앱이 충돌할 수 있으므로, 반드시 호출 가능 여부를 확인해야 한다. Single-cast 델리게이트와 Multi-cast 델리게이트 모두에서 사용할 수 있다.자주 쓸 함수.
아래 코드 참고
AddDynamic()Multi-cast 델리게이트에서 이벤트를 처리할 메소드를 등록하는 함수다. Single-cast 델리게이트에서는 BindUFunction() 함수를 사용한다.자주 쓸 함수
Broadcast()Multi-cast 델리게이트에서 바인딩된 모든 함수를 실행하는 함수다. 바인딩된 함수의 실행 순서는 정의되어 있지 않으므로, 순서에 의존하는 처리를 구현하는 것은 지양해야 한다. 또한, Multi-cast 델리게이트는 값을 반환할 수 없다.델리게이트 실행하기.

자주 쓸 함수.
아래 코드 참고

if(FDelegate.isBound())
	FDelegate.Broadcast(param있을 경우 param입력)

💡 발행 구독 디자인 패턴

https://learn.microsoft.com/en-us/previous-versions/msp-n-p/ff649664(v=pandp.10)?redirectedfrom=MSDN

언리얼 엔진은 발행 구독 패턴 구현을 위해 델리게이트 기능을 제공한다.

그러니 델리게이트의 사용 방법에 대해 더 이해할 수 있도록 이 패턴을 알아보자!

  • 푸시(Push) 형태의 알림(Notification)을 구현하는데 적합한 디자인 패턴
  • 발행자(Publisher)와 구독자(Subscriber)로 구분된다.
    • 콘텐츠 제작자는 콘텐츠를 생산한다.
    • 발행자는 콘텐츠를 배포한다.
    • 구독자는 배포된 콘텐츠를 받아 소비한다.
    • 제작자와 구독자가 서로를 몰라도, 발행자를 통해 콘텐츠를 생산하고 전달할 수 있다. (느슨한 결합)
  • 유지보수가 쉽고, 유연하고, 확장성이 높고, 테스트가 쉬워진다.

💡 학교에서 진행하는 온라인 수업 활동을 예시로 들어보자면!!

  • 학사 정보(CourseInfo)와 학생(Student)
    • 학교는 학사 정보를 관리한다.
    • 학사 정보가 변경되면 자동으로 학생에게 알려준다.
    • 학생은 학사 정보의 알림 구독을 해지할 수 있다.
  • 시나리오
    • 학사 정보와 3명의 학생이 있다.
    • 시스템에서 학사 정보를 변경한다.
    • 학사 정보가 변경되면 알림 구독한 학생들에게 변경 내용을 자동으로 전달한다.

💡 언리얼 델리게이트 선언 방법

설계 고려 사항

  • 어떤 데이터를 전달하고 받을 것인가
    • 몇 개의 인자
    • 어떤 방식
    • 일대일? 일대다?
  • 환경 설정
    • C++ 프로그래밍에서만 사용할 것인가
    • UFUNCTION으로 지정된 블루프린트 함수와 사용할 것인가
  • 어떤 함수와 연결할 것인가
    • 클래스 외부에 설계된 C++ 함수와 연결
    • 전역에 설계된 정적 함수와 연결
    • 언리얼 오브젝트의 멤버 함수와 연결 (대부분의 경우 이 방식을 사용)

선언 매크로

💡 `DECLARE_{델리게이트유형}DELEGATE{함수정보}`
  • 델리게이트 유형
    • 일대일 형태로 C++만 지원한다면 공란

      • DECLARE_DELEGATE
    • 일대다 형태로 C++만 지원한다면 MULTICAST 를 선언

      • DECLARE_MULTICAST
      • MULTICAST가 붙는 델리게이트는 여러 개의 함수를 바인딩하고, 모두를 한 번에 실행할 수 있는 델리게이트입니다.
    • 일대일 형태로 블루프린트를 지원한다면 DYNAMIC 을 선언

      • DECLARE_DYNAMIC
    • 일대다 형태로 블루프린트를 지원한다면 DYNAMIC, MULTICAST 를 조합
      - DECLARE_DYNAMIC_MULTICAST

      C++블루프린트
      일대일DECLARE_DELEGATEDECLARE_DYNAMIC
      일대다DECLARE_MULTICASTDECLARE_DYNAMIC_MULTICAST
  • 함수 정보: 연동될 함수 형태
    • 인자가 없고 반환값도 없으면 공란
      • DECLARE_DELEGATE
    • 인자가 하나, 반환값이 없으면 OneParam
      • DECLARE_DELEGATE_OneParam
    • 인자가 셋, 반환값이 있으면 RetVal_ThreeParams
      • DECLARE_DELEGATE_RetVal_ThreeParams
      • (MULTICAST는 반환값을 지원하지 않는다)
      • 반환값이 있어서 RetVal 을 추가한다.
        다만 이 RetVal 는 블루프린트 형식을 지원하는 DYNAMIC 만 선언할 수 있다.
    • 파라미터는 최대 9개까지 지원된다.

💡 실습

0. 요구 사항 및 설계

💡 학사 정보 알림 서비스 만들기

  • 학사 정보(CourseInfo) 클래스와 학생(Student) 클래스의 상호 의존성을 최대한 없앤다.
    • 하나의 클래스는 하나의 작업에만 집중하도록 설계
    • 학사 정보 클래스는 델리게이트를 선언하고 알림에만 집중
    • 학생 클래스는 알림 수신에만 집중
    • 직원도 알림을 받을 수 있도록 유연하게 설계
    • 학사 정보와 학생은 서로 헤더를 참조하지 않도록 신경쓸 것
  • 이를 위해 발행과 구독을 컨트롤하는 주체를 설정
    • 학사 정보에서 선언한 델리게이트를 중심으로 구독과 알림을 컨트롤하는 주체 설정

1. 언리얼 델리게이트 매크로 선정 ****

  • 학사 정보(CourseInfo)가 변경되면 알림 주체와 내용을 학생(Student)에게 전달한다.
    • 두 개의 인자(알림 주체와 내용)를 가진다.
  • 변경된 학사 정보는 다수 인원을 대상으로 발송한다.
    • MULTICAST 를 사용
  • 오직 C++ 프로그래밍만 사용한다. 블루프린트는 사용하지 않는다.
    • DYNAMIC 은 사용하지 않는다.
  • 이런 경우에는 **DECLARE_MULTICAST_DELEGATE_TwoParams** 매크로를 사용한다.

2. 델리게이트 사용 준비하기

학사 정보를 관리하기 위해, 먼저 UObject를 상속받는 CourseInfo 클래스를 생성해준다.

그리고 앞서 정한 선언 매크로 **DECLARE_MULTICAST_DELEGATE_TwoParams** 를 이용해서 델리게이트를 정의하는데,

보통 언리얼 엔진에서 델리게이트의 이름을 지을 때 Signature 라는 접미사를 붙여준다.

또한 델리게이트 자료형 이름 앞에 F 접두사를 붙이지 않으면 에러가 발생한다.

// CourseInfo.h
DECLARE_MULTICAST_DELEGATE_TwoParams(FCourseInfoOnChangedSignature, const FString&, const FString&);

이렇게 선언된 FCourseInfoOnChangedSignature 델리게이트는 마치 객체처럼, 멤버 변수로 선언할 수 있다.

OnChanged 을 선언해주었다.

// CourseInfo.h
UCLASS()
class UEPARTONE_API UCourseInfo : public UObject
{
	GENERATED_BODY()
	
public:
	UCourseInfo();
	FCourseInfoOnChangedSignature OnChanged;
};

그 다음으로 FCourseInfoOnChangedSignature델리게이트에 어떤 함수가 바인딩이 되면 송출해줘야 하는데, 내용이 변경되면 송출한다고 했다.

외부에서 학사 정보를 변경할 때 지정하는 함수ChangeCourseInfo와, 학사 정보 콘텐츠가 담길 변수 Contents를 선언해주었다.

// CourseInfo.h
UCLASS()
class UEPARTONE_API UCourseInfo : public UObject
{
	GENERATED_BODY()
	
public:
	UCourseInfo();
	FCourseInfoOnChangedSignature OnChanged;

	// 외부에서 학사 정보를 변경할 때
	void ChangeCourseInfo(const FString& InSchoolName, const FString& InNewContents);

private:
	FString Contents; // 학사 정보 내용
};

새로운 학사 정보가 들어오면 변경되도록 ChangeCourseInfo 함수를 구현해준다.

그리고 OnChanged라는 Multi-cast 델리게이트에서 바인딩된 모든 함수를 실행하도록 한다.

// CourseInfo.cpp
#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); // OnChanged에 연결된 모든 함수에 브로드캐스트한다.
}

이번에는 Student 클래스를 수정해본다.

학사 알림 델리게이트의 구독자로서, 똑같은 형식을 가지는 함수를 선언해줘야 한다.

// Student.h
public:
	void GetNotification(const FString& School, const FString& NewCourseInfo);

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

3. 델리게이트 사용하기

지금까지의 과정을 돌아보면, Student, CourseInfo 클래스는 어디에도 서로에 대한 헤더를 포함하지 않고 있다.

⇒ 각 클래스는 개별적으로 자신이 해야 할 작업에만 집중할 수 있다는 장점을 발휘 중.

중간에서 이를 중재하는 학교 역할을 하는 객체MyGameInstance 로 해주자.

학교 MyGameInstance 는 학사 시스템 CourseInfo 을 소유하도록 한다.

앞선 1_8강: 언리얼 C++ 설계2 - 컴포지션 강의에서 언리얼 오브젝트를 생성할 때 컴포지션 정보를 자동으로 구축할 수 있다 고 하였다. 이를 활용해보자. NewObject<UCourseInfo>()this를 매개변수로 전달함으로써, 생성되는 CourseInfo 객체의 아우터로 현재 객체 this를 설정해줄 수 있다. 이로써 MyGameInstanceCourseInfo를 가지는 컴포지션 관계가 설정된다.

// MyGameInstance.h
private:
	UPROPERTY()
	TObjectPtr<class UCourseInfo> CourseInfo;

// MyGameInstance.cpp
CourseInfo = NewObject<UCourseInfo>(this); // 컴포지션 관계

Student1 객체의 경우 해당 구문을 실행하면 자동으로 소멸되므로 굳이 Outer 를 설정해주지 않아도 괜찮다.

AddUObject 함수를 이용하면 Student 클래스 인스턴스와 멤버 변수를 묶어줄 수 있다.

이로 인해 구독을 위한 연결이 되었다.

마지막으로 ChangeCourseInfo 으로 변경된 학사 정보를 발행하도록 하자.

// MyGameInstance.cpp

#include "MyGameInstance.h"
#include "Student.h"
#include "CourseInfo.h"

void UMyGameInstance::Init()
{
	Super::Init();
	
	CourseInfo = NewObject<UCourseInfo>(this); // CourseInfo의 Outer로 MyGameInstance를 설정해서, MyGameInstance가 CourseInfo를 가지는 컴포지션 관계를 설정한다. 

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

💡 오늘 배운 내용

데이터 기반의 디자인 패턴을 설계할 때 사용할 수 있는 델리게이트에 대해 배웠다.

  • 느슨한 결합은 향후 시스템 변경 사항에 대해 손쉽게 대처할 수 있도록 해준다.
  • 느슨한 결합으로 구현된 발행 구독 모델의 장점은 아래와 같다.
    • 클래스는 자신이 해야 할 작업에만 집중할 수 있다.
    • 외부에서 발생한 변경 사항에 영향받지 않는다.
    • 자신의 기능을 확장하더라도 다른 모듈에 영향을 주지 않는다.
  • 언리얼 C++ 델리게이트 선언 방법
    • 몇 개의 인자를 가지는가?
    • 어떤 방식으로 동작하는가? (MULTICAST 사용 유무)
    • 언리얼 에디터와 함께 연동하는가? (DYNAMIC 사용 유무⇒블루프린트)
    • 이를 조합해 적합한 매크로를 선택한다.

💡 강의 과제

1️⃣ 언리얼 델리게이트를 활용한 예제를 고안하고 직접 구현해보세요.

  • 게임 데이터를 관리하는 클래스가 있다고 가정합시다.
  • 게임 데이터를 화면에 출력하는 UI 클래스가 있다고 가정해봅시다.
  • 게임 데이터가 변경될 때 자동으로 UI도 변경되도록 만들어보세요.
  • 실제 UI를 구현하지 않고 로그로 표현하세요

위에서 예시를 들었던 것처럼, Monster 클래스가 Hero(Tanker, Dealer) 에게 공격받을 때 체력(HP) 데이터가 깎이는 것을 로그로 출력해보겠습니다.

1. 발행자(publisher), 델리게이트 선언

먼저 Monster 클래스를 만들어보겠습니다.

Monster를 공격할 수 있는 Hero 인스턴스는 Tanker, Dealer 총 2개이며,

인자는 Monster 의 체력을 나타내는 HP, Monser를 공격한 Hero의 이름으로 총 두 개입니다.

이름
Monster를 공격할 수 있는 HeroTanker, Dealer2
인자HP, InName2

아래와 같은 델리게이트 매크로를 이용해서 FMonsterDamagedSignature 델리게이트를 선언했습니다.

// Monster.h
DECLARE_MULTICAST_DELEGATE_TwoParams(FMonsterDamagedSignature, int32, const FString&);
// Monster.h
UCLASS()
class UEPARTONE_API UMonster : public UObject
{
	GENERATED_BODY()
	
public:
	UMonster();
	FMonsterDamagedSignature OnDamaged;
	void  ChangeOnDamaged(int32 Damage, const FString& InName);

private:
	int32 HP;
};

몬스터의 초기 체력을 설정해주고, OnDamaged에 연결된 모든 함수에 브로드캐스트가 가능하도록 설정했습니다.

// Monster.cpp
UMonster::UMonster()
{
	HP = 100;
	UE_LOG(LogTemp, Log, TEXT("=========================================="));
	UE_LOG(LogTemp, Log, TEXT("[Monster] %dHP 몬스터가 스폰되었습니다."), HP);

}

void UMonster::ChangeOnDamaged(int32 Damage, const FString& InName)
{
	HP -= Damage;
	UE_LOG(LogTemp, Log, TEXT("[Monster] 몬스터가 %s 로부터 %d데미지을 받아서 %dHP가 되었습니다."), *InName, Damage, HP);
	OnDamaged.Broadcast(Damage, InName); // OnDamaged에 연결된 모든 함수에 브로드캐스트한다.
}

2. 구독자(Subscriber)

다음으로 Hero 클래스와 이를 상속받는 Tanker, Dealer 클래스입니다.

// Hero.h
UCLASS()
class UEPARTONE_API UHero : public UObject
{
	GENERATED_BODY()
	
public:
	UHero();
	void GetNotificationOnAttack(int32 Damages, const FString& InNames);
	FORCEINLINE const FString& GetName() const { return Name; }
	FORCEINLINE void SetName(const FString& InName) { Name = InName; }
	FORCEINLINE int32 GetDamage() { return Damage; }
	FORCEINLINE void SetDamage(int32 InDamage) { Damage = InDamage; }

protected:
	UPROPERTY()
	FString Name;

	UPROPERTY()
	int32 Damage;
};

Monster HP알림 델리게이트의 구독자로서, 똑같은 형식을 가지는 함수를 선언해줍니다.

// Hero.cpp
void UHero::GetNotificationOnAttack(int32 Damages, const FString& InNames)
{
	UE_LOG(LogTemp, Log, TEXT("[Hero %s] Monster 가 %d 의 데미지를 받았습니다."), *Name, Damages);
}

탱커의 이름을 홍탱커로, 탱커의 공격력을 5로 정해줍니다.

// Tanker.cpp
UTanker::UTanker()
{
	Name = TEXT("홍탱커");
	Damage = 5;
}

딜러의 이름을 홍딜러로, 딜러의 공격력을 10으로 정해줍니다.

// Dealer.cpp
UDealer::UDealer()
{
	Name = TEXT("홍딜러");
	Damage = 10;
}

3. 브로커(Broker)

Monster, Hero 가 존재하는 게임의 필드는 MyGameInstance입니다.

// MyGameInstance.h	
UPROPERTY()
	TObjectPtr<class UMonster> Monster;

OnDamaged 델리게이트에 이벤트를 할당해줍니다.

void UMyGameInstance::Init()
{
	Super::Init();
	
	UTanker* Tanker = NewObject<UTanker>();
	UDealer* Dealer = NewObject<UDealer>();
	Monster = NewObject<UMonster>();

	Monster->OnDamaged.AddUObject(Tanker, &UHero::GetNotificationOnAttack);
	Monster->OnDamaged.AddUObject(Dealer, &UHero::GetNotificationOnAttack);

	TArray<UHero*> Heroes = { Tanker, Dealer };
	for (auto Hero : Heroes) 
	{
		UE_LOG(LogTemp, Log, TEXT("=========================================="));
		Monster->ChangeOnDamaged(Hero->GetDamage(), Hero->GetName());
	}
	UE_LOG(LogTemp, Log, TEXT("=========================================="));
}

4. 결과 출력

홍탱커, 홍딜러가 Monster 를 각각 공격해서 HP 데이터가 5, 10 만큼 감소되는 것이 출력되었습니다.

또한 ChangeOnDamaged , GetNotificationOnAttack 으로 홍탱커, 홍딜러에게 메시지가 각각 출력되는 것을 확인할 수 있습니다.


참고하면 좋을 사이트

UE4 C++ Delegate 정리 & 샘플 프로젝트

0개의 댓글