Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

3냥 집사이면서 게임 개발자입니다.

언리얼 C++ 설계 2 - 컴포지션 (Composition) 본문

Unreal Engine 5/언리얼 C++

언리얼 C++ 설계 2 - 컴포지션 (Composition)

훙이야 2023. 7. 26. 13:23

언리얼 C++ 만의 컴포지션 기법을 사용해 오브젝트의 포함 관계를 설계하는 방법과 언리얼 C++ 이 제공하는 확장 열거형 타입의 선언과 활용 방법을 정리하고자 합니다.

 

컴포지션(Composition)

- 객체 지향 설계에서 상속이 가진 Is-A 관계만 의존해서는 설계와 유지보수가 어렵습니다.

- 컴포지션은 객체 지향 설계에서 Has-A 관계를 구현하는 설계 방법입니다.

- 컴포지션의 활용

     - 복합적인 기능을 가진 거대한 클래스를 효과적으로 설계하는데 유용하게 사용할 수 있습니다.

 

[ 예시 코드 ]

Class Card
{
	public:
    	Card(int InId) : Id(InId) {}       
 };
 
 class Person
 {
 	public:
    	Person(Card InCard) : InCard(InCard) {}
        
    protected:
    	Card IdCard;
  }

모던 객체 설계 기법과 컴포지션

- 좋은 객체 지향 설계 패턴을 제작하기 위한 모던 객체 설계 기법 (SOLID)

- Single Responsibility Principle (단일 책임 원칙)

     - 하나의 객체는 하나의 의무만 가지도록 설계한다. 

- Open-Closed Principle (개발 폐쇄 원칙)

     - 기존에 구현된 코드를 변경하지 않으면서 새로운 기능을 추가할 수 있도록 설계한다. 

- Liskov Substitution Principle (리스코프 치환원칙)

     - 자식 객체를 부모 객체로 변경해도 작동에 문제 없을 정도로 상속을 단순히 사용한다.

- Interface Segregation Design (인터페이스 분리 원칙)

     - 객체가 구현해야 할 기능이 많다면 이들을 여러 개의 단순한 인터페이스들로 분리해 설계한다.

- Dependency Injection Principle (의존성 역전 원칙)

     - 구현된 실물보다 구축해야 할 추상적 개념에 의존한다.

 

* 모던 객체 설계 기법의 설계 핵심은 상속을 단순화하고, 단순한 기능을 가진 다수의 객체를 조합해 복잡한 객체를 구성하는데 있습니다.

 

[ 컴포지션 설계 예시 ]

- 학교 구성원 시스템의 설계 예시 

     - 학교 구성원을 위해 출입증을 만들기로 한다.

     - 출입증은 Person에서 구현해 상속시킬 것인가? 아니면 컴포지션으로 분리할 것인가?

- Person에서 직접 구현해 상속시키는 경우의 문제

     - 새로운 형태의 구성원이 등장한다면(예를 들어 출입증이 없는 외부 연습생) Person을 수정할 것인가?

     - 상위 클래스 Person을 수정하면, 하위 클래스들의 동작은 문제 없음을 보장할 수 있는가? 

- 따라서 설계적으로 출입증은 컴포지션으로 분리하는 것이 바람직합니다.

- 그렇다면 컴포지션으로만 포함시키면 모든 것이 해결될 수 있는가?

 

* 효과적인 설계를 위해 프로그래밍 언어가 제공하는 고급 기법을 활용해야 합니다.

 

[ 예제를 위한 클래스 생성 ]

- 학교 구성원임을 증명하는 출입증 카드의 부여

     - 학생, 교사, 직원 모두가 상시 지니고 있습니다.

     - 향후 확장성을 고려해 컴포지션으로 구현합니다.

 

Card     ->     Person -> Student

                                  -> Teacher

                                  -> Staff 

 

언리얼 엔진에서의 컴포지션 구현 방법

- 하나의 언리얼 오브젝트에는 항상 클래스 기본 오브젝트 CDO가 있습니다.

- 언리얼 오브젝트간의 컴포지션은 어떻게 구현할 것인가? 

- 언리얼 오브젝트에 다른 언리얼 오브젝트를 조합할 때 다음의 선택지가 존재합니다.

     - 방법 1 : CDO에 미리 언리얼 오브젝트를 조합할 때 다음의 선택지가 존재합니다. (필수적 포함)

     - 방법 2 : CDO에 빈 포인터만 넣고 런타임에서 언리얼 오브젝트를 생성해 조합합니다. (선택적 포함)

- 언리얼 오브젝트를 생성할 때 컴포지션 정보를 구축할 수 있다. 

     - 내가 소유한 언리얼 오브젝트를 SubObject라고 합니다.

     - 나를 소유한 언리얼 오브젝트를 Outer라고 합니다. 

 

기존의 Person, Student, Teacher, Staff 클래스를 새로운 프로젝트에서 사용하며, Card 클래스를 새로 생성해 출입증이 있는지 확인할 수있도록 Enum Class 로 Type을 나누었습니다. UMETA 를 사용해 각 데이터 필드에 텍스트를 추가할 수 있습니다. 

Person 클래스의 멤버 변수 코드입니다. TObjectPtr 을 사용해 클래스 포인터를 감싸주어 변수를 선업합니다.

Person 출입증 카드를 갖도록 하기 위해 Card 객체를 소유하고, CDO 에서 구현할 때 CreateDefaultSubobject 라는 API를 사용합니다.

그리고 Name이라는 식별자를 넣어줘야 하는데 이것은 고유한 이름이면 됩니다. 

FNAME인 것을 명시하기 위해 NAME_Card라고 지정했습니다. 

 

이후에 상속받은 클래스들에도 Card 에 각자의 CardType 을 대입합니다. 

Teacher, Staff 와 동일합니다.

 

Person에 속한 자식 객체들이 갖고 있는 CardType 정보를 출력해봤습니다. 

그리고 메타 데이터로 저장한 DisplayName 도 정상적으로 출력할 수 있는지 확인해봤습니다. 

언리얼 엔진 5 에디터 출력 로그

 

[정리] 컴포지션을 활용한 언리얼 오브젝트 설계

1. 언리얼 C++은 컴포지션을 구현하는 독특한 패턴이 있습니다.

2. 클래스 기본 객체를 생성하는 생성자 코드를 사용해 복잡한 언리얼 오브젝트를 생성할 수 있습니다.

3. 언리얼 C++ 컴포지션의 Has-A 관계에 사용되는 용어

     - 내가 소유한 하위 오브젝트 : Subobject

     - 나를 소유한 상위 오브젝트 : Outer

4. 언리얼 C++ 이 제공하는 확장 열거형을 사용해 다양한 메타 정보를 넣고 활용할 수 있습니다.

 

* 언리얼 C++ 의 컴포지션 기법은 게임의 복잡한 객체를 설계하고 생성할 때 유용하게 사용된다.