3냥 집사이면서 게임 개발자입니다.
언리얼 C++ 설계 3 - 델리게이트 (Delegate) 본문
언리얼 엔진 5의 델리게이트 시스템은 클래스간 의존성을 최소화하기 위해 사용합니다.
클래스간 의존성을 최소화하기 위해 언리얼 엔진이 제공하는 델리게이트 시스템을 정리하고자 합니다.
느슨한 결합의 장점과 이를 편리하게 구현하도록 도와주는 델리게이트의 이해와 발행 구독 디자인 패턴의 이해, 언리얼 델리게이트를 활용한 느슨한 결합의 설계와 구현을 정리해보겠습니다.
강한 결합과 느슨한 결합
- 강한 결합(Tight Coupling)
- 클래스들이 서로 의존성을 가지는 경우를 의미합니다.
- 이전 포스팅을 예시로 들면 Card가 없는 경우 Person이 만들어질 수 없습니다.
- 이 때 Person은 Card에 대한 의존성을 가진다고 합니다.
- 핸드폰에서도 인증할 수 있는 새로운 카드가 도입된다면?
- 느슨한 결합(Loose Coupling)
- 실물에 의존하지 말고 추상적 설계에 의존하라.(DIP원칙)
- 왜 Person은 Card 가 필요한가? -> 출입을 확인해야 하기 때문
- ICheck를 상속받은 새로운 카드 인터페이스를 선언해 해결
- 이러한 느슨한 결합구조는 유지보수를 편리하게 만들어줍니다.
느슨한 결합의 간편한 구현 - 델리게이트 (Delegate)
- 그렇다면 함수를 오브젝트처럼 관리하면 어떨까?
- 함수를 다루는 방법
- 함수 포인터를 활용한 콜백(Callback) 함수의 구현
- 가능은 하나 이를 정의하고 사용하는 과정이 꽤나 복잡합니다.
- 안정성을 스스로 검증해줘야 합니다.
- C++17 구약의 std::bind와 std::function활용은 구현이 느립니다.
- C#의 델리게이트 키워드
- 함수를 마치 객체처럼 다룰 수 있습니다.
- 안정적이고 간편한 선언
- 언리얼 C++도 델리게이트를 지원합니다.
- 느슨한 결합 구조를 간편하고 안정적으로 구현할 수 있습니다.
발행 구독 디자인 패턴
- 푸시형태의 알림을 구현하는데 적합한 디자인 패턴입니다.
- 발생자와 구독자로 구분됩니다.
- 콘텐츠 제작자는 콘텐츠를 생산한다.
- 발행자는 콘텐츠를 배포합니다.
- 구독자는 배포된 콘텐츠를 받아 소비한다.
- 제작자와 구독자가 서로를 몰라도, 발행자를 통해 콘텐츨르 생산하고 전달할 수 있다. (느슨한 결합)
- 발행 구독 디자인 패턴의 장점
- 제작자와 구독자는 서로를 모르기 때문에 느슨한 결합으로 구성된다.
- 유지 보수(Maintenance)가 쉽고, 유연하게 활용될 수 있으며(Flexibility), 테스트가 쉬워진다.
- 시스템 스케일을 유연하게 조절할 수 있으며(Scalability), 기능 확장(Extensibility)이 용이하다.
이를 구현하기 위한 예시입니다.
이전 포스팅에 있는 예제 학교에 학사 정보가 있고 이를 구독한다고 해보겠습니다.
[ 예제를 위한 클래스 생성과 시나리오 ]
- 학교에서 진행하는 온라인 수업 활동 예시
- 학사 정보(CoursInfo)와 학생(Student)
- 학교는 학사 정보를 관리한다.
- 학사 정보가 변경되면 자동으로 학생에게 알려준다.
- 학생은 학사 정보의 알림 구독을 해지할 수 있다.
- 시나리오
1. 학사 정보와 3명의 학생이 있다.
2. 시스템에서 학사 정보를 변경한다.
3. 학사 정보가 변경되면 알림 구독한 학생들에게 변경 내용을 자동으로 전달한다.
이러한 발행 구독 모델을 구현하기 위해 사용되는 것이 언리얼 델리게이트입니다.
언리얼 델리게이트
- 언리얼 엔진은 발행 구독 패턴 구현을 위해 델리게이트 기능을 제공합니다.
- 델리게이트의 사전적 의미는 대리자 입니다.
- 학사정보의 구독과 알림을 대리해주는 객체
- 시나리오 구현을 위한 설계
- 학사 정보는 구독과 알림을 대행할 델리게이트를 선언한다.
- 학생은 학사 정보의 델리게이트를 통해 알림을 구독한다.
- 학사 정보는 내용 변경시 델리게이트를 사용해 등록한 학생들에게 알린다.
학사 정보를 알고 있는 델리게이트는 학생에게 방송을 하고 학생은 델리게이트를 구독한다.
언리얼 델리게이트를 선언한 후에는 학사정보와 학생 정보를 엮습니다. 그렇게 되면 학사 정보가 변경될 때 마다 구독한 학생에게 자동으로 전달됩니다.
언리얼 델리게이트의 선언 방법
언리얼 델리게이트 선언시 고려사항
- 델리게이트를 설계하기 위한 고려 사항
- 어떤 데이터를 전달하고 받을 것인가? 인자의 수와 각각의 타입을 설계
- 몇 개의 인자를 전달할 것인가?
- 어떤 방식으로 전달할 것인가?
- 일대일로 전달
- 일대다로 전달
- 프로그래밍 환경 설정
- C++ 프로그래밍에서만 사용할 것인가?
- UFUNCTION으로 지정된 블루프린트 함수와 사용할 것인가?
- 어떤 함수와 연결할 것인가?
- 클래스 외부에 설계된 C++ 함수와 연결
- 전역에 설계된 정적 함수와 연결
- 언리얼 오브젝트의 멤버 함수와 연결 ( 대부분의 경우에 이 방식을 사용 )
언리얼 델리게이트 선언 매크로
DECLARE_{델리게이트유형}DELEGATE{함수정보}
- 델리게이트 유형 : 어떤 유형의 델리게이트인지 구상한다.
- 일대일 형태로 C++만 지원한다면 유형은 공란으로 둔다. DECLARE_DELEGATE
- 일대다 형태로 C++만 지원한다면 MULTICAST를 선언한다. DECLARE_MULTICAST
- 일대일 형태로 블루프린트를 지원한다면 DYNAMIC을 선언한다. DECLARE_DYNAMIC
- 일대다 형태로 블루프린트를 지원한다면 DYNAMIC과 MULTICAST를 조합한다. DECLARE_DYNAMIC_MULTICAST
- 함수 정보 : 연동 될 함수 형태를 지정한다.
- 인자가 없고 반환값도 없으면 공란으로 둔다. 예) DECLARE_DELEGATE
- 인자가 하나고 반환값이 없으면 OneParam으로 지정한다. 예) DECLARE_DELEGATE_OneParam
- 인자가 세 개고 반환값이 있으면 RetVal_ThreeParams 로 지정한다.
예) DECLARE_DELEGATE_RetVal_ThreeParams (MULTICAST는 반환값을 지원하지 않습니다.)
- 최대 아홉 개까지 지원합니다.
언리얼 델리게이트 매크로 선정 예시
- 학사 정보가 변경되면 알림 주체와 내용을 학생에게 전달한다.
- 두 개의 인자를 갖습니다
- 변경된 학사 정보는 다수 인원을 대상으로 발송합니다.
- MULTICAST를 사용합니다.
- 오직 C++프로그래밍에서만 사용합니다.
- DYNAMIC은 사용하지 않습니다.
=> DECLARE_MULTICAST_DELEGATE_TwoParams 매크로를 사용합니다.
[ 최종 언리얼 델리게이트의 설계 ]
- 학사 정보 클래스와 학생 클래스의 상호 의존성을 최대한 없앤다.
- 하나의 클래스는 하나의 작업에만 집중하도록 설계합니다.
- 학사 정보 클래스는 델리게이트를 선언하고 알림에만 집중합니다.
- 학생 클래스는 알림을 수신하는데만 집중합니다.
- 직원도 알림을 받을 수 있도록 유연하게 설계합니다.
- 학사 정보와 학생은 서로 헤더라르 참조하지 않도록 신경쓸 것입니다.
- 이를 위해 발행과 구독을 컨트롤하는 주체를 설정합니다.
- 학사 정보에서 선언한 델리게이트를 중심으로 구독과 알림을 컨트롤하는 주체를 설정합니다. (연결과 활동의 주체는 GameInstance)
이전 포스팅에서 작업한 예시 코드에서 CourseInfo 라는 학사 정보 클래스를 생성하고 매크로를 선언함으로써 델리게이터를 선언합니다.
학생 클래스에는 알림을 받기 위한 함수를 준비합니다.
학사 정보와 학생을 중재하기 위해 MyGameInstance 클래스에서 사용합니다.
MyGameInstance의 생성자에서 CDO 를 사용하지 않고 외부에서 사용해보겠습니다.
CourseInfo 포인터 변수를 사용해 아우터를 설정합니다. 학생들을 세 명 생성한 뒤
AddUObject 를 사용해 학생 멤버함수를 바인딩합니다.
그 후 학사 정보를 업데이트 하면 구독했던 모든 학생들에게서 정보를 받을 수 있습니다.
[ 정리 ] 언리얼 C++ 델리게이트
1. 느슨한 결합(Loose Coupling)이 가지는 장점
- 향후 시스템 변경 사항에 대해 손쉽게 대처할 수 있습니다.
2. 느슨한 결합으로 구현된 발행 구독 모델의 장점
- 클래스는 자신이 해야 할 작업에만 집중할 수 있습니다.
- 외부에서 발생한 변경 사항에 대해 영향받지 않습니다.
- 자신의 기능을 확장하더라도 다른 모듈에 영향을 주지 않습니다.
3. 언리얼 C++의 델리게이트 선언 방법과 활용
- 몇 개의 인자를 가지는가?
- 어떤 방식으로 동작하는가? (MULTICAST 사용 유무 결정)
- 언리얼 에디터와 함께 연동할 것인가? (DYNAMIC 사용 유무 결정)
- 이를 조합해 적합한 매크로를 선택합니다.
* 데이터 기반의 디자인 패턴을 설계할 때 유용하게 사용하기 좋을 것 같습니다.
참고 사이트 (언리얼 문서)
https://docs.unrealengine.com/5.1/ko/delegates-and-lamba-functions-in-unreal-engine/
'Unreal Engine 5 > 언리얼 C++' 카테고리의 다른 글
언리얼 컨테이너 라이브러리 2 - 구조체와 Map (0) | 2023.07.27 |
---|---|
언리얼 컨테이너 라이브러리 1 - Array, Set (0) | 2023.07.27 |
언리얼 C++ 설계 2 - 컴포지션 (Composition) (0) | 2023.07.26 |
언리얼 C++ 인터페이스 클래스와 C++ 인터페이스 클래스와 차이점 (0) | 2023.07.26 |
언리얼 C++ 설계 1 - 인터페이스 (Interface) (0) | 2023.07.25 |