게임 엔진/Unreal

[Unreal] 델리게이트

겜도리도리 2024. 1. 15. 14:35
반응형

앞서

이 포스팅은 이득우의 언리얼 프로그래밍 Part1을 수강하고 작성한 내용입니다.

 

개요

언리얼 엔진에서 델리게이트를 사용하는 방법에 대해 서술한다.

델리게이트를 사용하면 느슨한 결합을 구현할 수 있다.

 

강한 결합 vs 느슨한 결합

강한 결합은 클래스들이 서로 의존성을 가지는 경우를 말한다.

앞선 예제에서는 Person이 Card 클래스를 가지고 있었다.

 

느슨한 결합은 추상적 설계에 의존한다.

Card 클래스의 쓰임이 출입 체크라면 Person에서 Card를 직접 가지는 것이 아니라 ICheck라는 인터페이스를 가지고,

Card가 ICheck 인터페이스를 상속받게 만들도록 구현한다.

이렇게 구현하면 Card 말고 다른 클래스로 출입 체크 기능을 구현해야 할 때, 그 클래스가 ICheck를 상속받게 구현하고 Card 대신 그 클래스를 넘겨주면 Person 클래스를 수정하지 않고도 변경이 가능하다. 따라서 의존성을 낮출 수 있다.

 

근데 매번 이렇게 인터페이스를 만드는 것은 번거롭다. 따라서 함수를 오브젝트처럼 관리하는 기법을 사용한다.

std::bind와 std::function을 활용할 수 있으나 퍼포먼스가 느려서 게임에서 사용하기는 쉽지 않다.

C#에서는 delegate라는 키워드를 사용할 수 있는데, 언리얼에서도 delegate를 사용할 수 있다.

 

발행 구독 디자인 패턴(옵저버 패턴)

제작자, 발행자, 구독자로 구별된다.

제작자는 콘텐츠를 생산한다. 발행자는 콘텐츠를 배포하고, 구독자는 콘텐츠를 소비한다.

제작자와 구도작가 서로를 몰라도 발행자를 통해 콘텐츠 생산 및 전달을 할 수 있다.

 

장점

느슨한 결합 구현이 가능하다.

유지 보수와 테스트가 쉽고, 유연하게 활용이 가능하다.

 

앞서 있던 클래스에 학사 정보를 추가하고, 발행 - 구독 디자인 패턴을 적용시킨다고 가정해 보자.

학교는 학사 정보를 관리하고 학생은 학사 정보를 사용한다.

델리게이트를 사용하면 학교와 학생은 서로 알 필요가 없이 학교는 학사 정보를 관리하는데만 집중하고, 학생은 학사 정보를 사용하는데만 집중할 수 있다.

 

델리케이트 선언

델리게이트를 선언할 때는 여러 고려 사항들을 확인해야 한다.

어떤 데이터를 주고받을지, 인자는 얼마나 있고 각각의 타입은 무엇인지, 일대일과 일대다 중에 어떤 방식으로 전달할지 등을 고려해야 한다. 이외에도 블루프린트에서의 사용 여부와 어떤 함수와 연결될 지도 고려해야 한다.

 

DECLARE_{델리게이트 타입}_DELEGATE_{함수 정보}의 형태를 가지게 된다.

 

델리게이트 타입

델레게이트 타입 예시

일대일 C++ : DECLARE_DELEGATE

일대다 C++ : DECLARE_MULTICAST_DELEGATE

일대일 블루프린트 : DECLARE_DYNAMIC _DELEGATE

일대다 블루프린트 : DECLARE_ DYNAMIC_MULTICAST _DELEGATE

 

함수 정보

인자가 없고 반환값도 없으면 공란으로 놔둔다.

인자가 있고 반환값이 없으면 _NumParam을 더 붙여준다. (인자 하나 : _OneParam, 인자 두 개 : _TwoParam)

인자는 최대 9개까지 지원한다.

반환값이 있으면 Param 전에 RetVal을 붙여준다.

 

예를 들어 일대일이고, C++에서만 사용하면서 인자가 세 개고, 반환값이 있는 델리게이트를 선언하고 싶다면

DECLARE_DELEGATE_RetVal_ThreeParams와 같이 지정해 주면 된다.

MULTICAST에서는 반환값을 지원하지는 않는다.

예시

앞서 들었던 예시처럼 학사 정보를 추가하면서 학생과의 상호 의존성은 최대한 낮추고자 한다.

학사 정보는 알림에만 집중한다. 반면 학생 클래스는 알림을 수신하는데만 집중한다.

따라서 학사 정보와 학생은 서로의 헤더를 참조하지 않도록 한다.

학사 정보의 알림과 학생의 구독은 MyInstance에서 처리한다.

 

우리가 사용할 델리게이트는 멀티캐스트이며, C++에서만 사용하면서 리턴 값은 없고 2개의 인자를 가진다.

 

따라서 위와 같이 델리게이트를 선언해 주면 된다. 델리게이트는 보통 Signature라는 접미사를 자주 사용한다.

 

델리게이트를 변수처럼 사용할 수 있게 되었다.

외부에서 학사 정보가 바뀌면 OnChagned를 호출한다고 하자.

 

ChangeCourseInfo가 호출되면 학사 정보를 바꾸어주고, OnChanged에 바인딩된 함수를 Broadcast 한다.

반대로 학생에서는 알림을 수신하는 기능만 구현한다.

 

알림을 수신하면 GetNotification 함수를 호출해 메시지를 받았다는 걸 Log에 띄운다.

이렇게 구현하면 학생에서는 학사 정보를 몰라도 되고 (헤더에 포함하지 않아도 되고) 학사 정보는 학생을 몰라도 된다. (헤더에 포함하지 않아도 됨)

 

MyInstance에서는 학사 시스템을 소유하고, 정보를 변경하는 코드를 넣어준다.

 

UCourseInfo는 UObject이므로 전방 선언을 통해 의존성을 낮춰준다.

 

Student 3개를 선언하고 AddUObject를 활용해 각각 인스턴스의 멤버 함수를 CourseInfo의 OnChanged에 바인딩해 준다.

그리고 CourseInfo의 ChangeCourseInfo 함수를 호출해 주면 각각 Student 인스턴스의 GetNotification 함수들이 호출될 것이다.

결과

 

Student 1~3의 GetNotification 함수가 호출되는 걸 확인할 수 있다.

Student와 CourseInfo가 서로에 대한 의존성 없이 MyInstance에서 처리되는 걸 볼 수 있다.

반응형

'게임 엔진 > Unreal' 카테고리의 다른 글

[Unreal] 캐릭터와 입력 시스템  (0) 2024.02.25
[Unreal] 메모리 관리  (0) 2024.01.19
[Unreal] 언리얼 컴포지션과 UENUM  (0) 2024.01.12
[Unreal] 인터페이스  (0) 2023.12.27
[Unreal] 언리얼 리플렉션  (0) 2023.12.24