3냥 집사이면서 게임 개발자입니다.
언리얼 오브젝트 본문
언리얼 엔진의 근간을 이루는 언리얼 오브젝트 체계와 이를 생성하는 방법에 대해서 이야기하고자 합니다.
게임 프로그래밍이 가지는 특수성과 언리얼 오브젝트의 필요성에 대해 이해하고자 했으며,
언리얼 오브젝트의 선언과 엔진 내부 컴파일 과정에 대해 이해한 내용을 다룹니다.
게임 프로그래밍의 특수성
- 사용자 : 쾌적한 경험을 위해 단일 컴퓨터에서 최대 성능을 뽑아 내야 한다.
- 개발자 : 게임의 규모가 커질수록 방대하고 복잡한 기능을 안정적으로 관리해야 한다.
Native 접근 C++ 언어 (안정성보다 성능 중시)
- 메모리 직접 제어
- Cache의 활용 극대화
- 저수준 API의 직접 호출
- 복사 작업의 최소화
하이레벨 OOP 언어 (성능보다 안정성 중시)
- 유지보수성 향상
- 크래시로부터 보호
- 자동 메모리 관리
- 고질적 실수 예방
C++ 언어의 단점
- 1970년대에 개발된 C++ 언어
- 객체 지향 프로그래밍의 선두 주자
- 지속적으로 개선해왔지만, 익혀야 할 내용이 많아 초급자가 학습하기 어려움
- 하드웨어에 직접 접근하기 때문에, 잘못 사용하면 프로그램에 큰 영향을 미침
- 1990년 중반이후 C++ 의 단점을 보완한 후발 언어의 등장 ( Java, C# )
- C++의 불필요한 기능을 걷어내고, 최대한 명확하고, 간결하게 설계
- 성능보다 안정성과 생산성을 중요시
- 하드웨어에 직접 접근하지 않고, 가상 머신을 통해 간접적으로 접근
모던 객체 지향 설계 원칙
- 디자인 패턴을 필두로 안정적인 설계 방법이 연구됨
- 현재 시점에서 모던(Modern)하다는 뜻은 아님
- 유지보수와 유연함, 확장성 향상을 위한 객체 지향 프로그래밍 원칙 (SOLID)
- Single responsibility principle : 하나의 클래스는 하나의 책임만 가져야한다.
- Open/closed principle : 클래스 설계를 변경하지 않고 동작을 확장할 수 있어야 한다.
- Liskov substitution principle : 자식 클래스는 부모 클래스를 대체 사용할 수 있어야 한다.
- Interface segregation principle : 작고 명확한 인터페이스들로 분리해 관리해야 한다.
- Dependency inversion principle : 구현을 배제시킨 상위 정책을 바라보며 설계해야 한다.
- 후발 언어(C#, Java)등이 보완한 새로운 기능
- 인터페이스(Interface) : 객체 설계의 틀을 제공하는 추상 클래스
- 리플렉션(Reflection) : 런타임에서 객체의 구조를 파악하고 객체에 메타데이터를 부여
- 델리게이트(Delegate) : 프로그램에서 발생한 이벤트를 다수의 객체에 효과적으로 전달하는데 활용
** 게임의 규모가 대형화되면서 모던 객체 지향 설계 도입이 필요해졌습니다.
언리얼 엔진의 선택
- 성능을 위해 기존 C++ 언어를 포기할 수 없었습니다.
- 기존 C++ 언어를 확장해 모던 객체 지향 설계를 가능하도록 만들었습니다.
- 모던 객체 지향 설계를 위한 새로운 시스템을 구축했습니다.
언리얼 C++ (기존 C++ 과 후발주자 언어의 이점을 모두 취함)
- 메모리 직접 제어
- Cache의 활용 극대화
- 저수준 API의 직접 호출
- 복사 작업의 최소화
- 유지보수성 향상
- 크래시로부터 보호
- 자동 메모리 관리
- 고질적 실수 예방
언리얼 C++ 의 핵심은 언리얼 오브젝트라 불리는 독특한 객체 시스템에 있습니다.
- 언리얼 엔진이 설계한 새로운 시스템의 단위 오브젝트(객체)
- 기존 C++ 오브젝트에 모던 객체 지향 설계를 위한 다양한 기능을 추가한 오브젝트입니다.
- 일반 C++ 오브젝트와 언리얼 오브젝트의 두 객체를 모두 사용할 수 있습니다.
- 구분을 위해 일반 C++ 오브젝트는 F, 언리얼 오브젝트는 접두사 U을 사용합니다.
- 각 오브젝트의 사용 용도
- C++ 오브젝트 : 저수준의 빠른 처리를 위한 기능 구현에 사용합니다.
- 언리얼 오브젝트 : 콘텐츠 제작에 관련된 복잡한 설계 구현에 사용합니다. 위에 정리한 이점들을 모두 취할 수 있습니다.
언리얼 엔진은 C++를 토대로 제작되었기 때문에, 일반 C++ 언어에서 사용했던 객체 시스템을 그대로 사용할 수 있지만,
언리얼 오브젝트라는 새로운 언리얼만의 객체 규약을 사용해서 객체를 설계할 수 있습니다.
언리얼 오브젝트가 가지는 특징 (간단히 정리하고 넘어가겠습니다. 뒤에서 자세히 다룰 예정입니다.)
- 클래스 기본 객체 (CDO) : 클래스의 기본 값과 타입 정보의 제공
- 리플렉션 (Reflection) : 런타임에서 클래스 정보의 참조 기능
- 인터페이스 (Interface) : 모던 객체 지향 언어가 제공하는 인터페이스의 제공
- 향상된 열거형 : 보다 향상된 열거형의 지원
- 델리게이트 (Deligate) : 객체간의 결합을 낮출 수 있는 델리게이트 기능의 제공
- 가비지컬렉션 (Garbage Collection) : 자동 메모리 관리
- 향상된 구조체 (Struct) : 리플렉션이 가능한 구조체의 지원
- 직렬화 (Serialization) : 객체 정보를 바이트 스트림으로 저장, 전송, 불러들이는 기능
언리얼 오브젝트의 선언 (어떻게 컴파일되며 만들어지는지 알아보겠습니다.)
언리얼에서 Object 를 상속받아 C++ 클래스를 생성합니다.
이는 언리얼 오브젝트를 선언하기 위한 가장 기본적인 템플릿이라고 볼 수 있습니다.
일반적인 클래스 선언과 다른 점에 대해 알아봅니다.
- 언리얼 오브젝트가 되기 위한 헤더들이 포함되어 있습니다.
- MyObject라는 객체 이름에 .generated.h 라는 헤더가 포함되어 있습니다.
- 접두사 U가 붙은 MyObject라고 클래스 이름이 선언되어있습니다. 파일 이름의 경우에는 접두사를 붙이지 않고 씁니다.
- 언리얼 오브젝트 선언임을 명시하기 위해 클래스 상단에 UCLASS()라는 매크로가 명시되어있습니다.
- UNREALOBJECT_API 라는 매크로가 작성되어있음을 확인할 수 있는데, 이는 프로젝트 이름을 따서 작성됩니다.
이 매크로는 MyObject라는 객체가 다른 곳에서도 사용할 수 있도록 개방하는 키워드입니다.
다른 DLL, 다른 모듈이라 불리는 언리얼에서의 라이브러리 단위가 있는데, 해당 매크로를 제거하면
다른 모듈에서 참조하지 않고, 해당하는 언리얼 오브젝트 모듈에서만 사용할 수 있게된다는 특징이 있습니다. - GENERATED_BODY() 라는 매크로가 선언되어 있습니다. 이 매크로는 해당 언리얼 오브젝트 클래스가 정상적인
언리얼 소스 코드로 작동할 수 있도록 설계되어있습니다. F12를 눌러 어떤 식으로 치환되어있는지 확인해보면
BODY_MACRO_COMBINE 으로 치환되어 있고, 이 매크로 또한 BODY_MACRO_COMBINE_INNER 로 치환되어 있음을 확인할 수 있고,
네 개의 인자를 받고 이어붙여 하나의 긴 문장을 만드는 매크로입니다.
사용자가 직접 인자를 전달하는 것이 아닌, 언리얼 엔진에서 제공하는 기능입니다.
좀 더 자세하게 알아보려면 MyObject 클래스 헤더에 Include 되어있는 특수한 헤더파일을 확인해야 합니다.
이 특수한 헤더 파일은 소스 코드 폴더가 아닌 Intermediate-Build-Win64-UnrealEditor-Inc-UnrealProject-UHT 라는 복잡한 경로 안에 저장되어있습니다.
(굳이 들어가서 헤더파일을 수정하지 말라는 의미이기도 합니다.)
해당 헤더를 확인해보면, CURRENT_FILE_ID 라는 매크로가 긴 문장 (MyObject 클래스의 경로)으로 치환되어 있는 것을 확인할 수 있습니다.
사용자가 직접 수정하는 헤더가 아닌, 객체 지향 설계를 위해 제공되는 좋은 기능들을 언리얼 엔진이 자동으로 생성해 지원하는 부분이니 적당히 이해하고 넘어가도 좋습니다. 자세한 설명이 있는 문서는 따로 찾아봐야겠습니다.
정리하자면, 이 특수한 헤더 파일은 MyObject 의 소스 코드와 매크로들의 조합으로 생성됩니다.
MyObject 헤더에 작성된 소스 코드를 수정하고 빌드하면 바로 컴파일 되는 것이 아니라, 언리얼 오브젝트의 코드를 분석하는 단계가 먼저 실행되고 언리얼 헤더 툴에 의해 소스 코드를 자동으로 생성(generated.h를 생성)한 뒤에 이 파일을 포함(Include)해서 최종 빌드를 진행합니다.
generated.h 를 통해 정상적인 언리얼 오브젝트 클래스로의 동작을 수행할 수 있습니다.
지금까지 언리얼 오브젝트가 뭔지, 왜 필요한지에 대해 정리해봤습니다.
게임이 대형화되면서 성능과 유지보수 두 가지가 모두 중요해졌습니다.
언리얼 엔진은 C++언어를 확장한 언리얼 오브젝트라는 객체 구조를 고안합니다.
지정된 매크로를 사용해 빌드를 수행하면, 추가 코드가 자동으로 만들어지는 구조를 가집니다.
이 언리얼 헤더 툴은 언리얼 엔진에서 제공하는 툴이며, 자동으로 추가 코드를 생성해서 언리얼 오브젝트에서
다양한 기능이 지원되도록 도와주는 역할을 합니다. 개발자는 단순히 지정된 매크로만 입력해주면 되고,
언리얼 헤더 툴이 자동으로 생성하는 헤더파일에는 접근할 이유가 없습니다.
이러한 언리얼 오브젝트는 대규모 게임 제작을 안정적으로 설계하고 구현하는데 큰 도움을 주기 때문에
언리얼 엔진 기술의 핵심이라고 볼 수 있습니다.
참고 사이트 (언리얼 문서)
https://docs.unrealengine.com/5.1/ko/objects-in-unreal-engine/
'Unreal Engine 5 > 언리얼 C++' 카테고리의 다른 글
언리얼 오브젝트 리플렉션 시스템 2 (0) | 2023.07.25 |
---|---|
언리얼 오브젝트 리플렉션 시스템 1 (0) | 2023.07.25 |
문자열 사용하기 (0) | 2023.07.20 |
언리얼 C++ 의 이해 (0) | 2023.07.19 |
언리얼 c++ 코딩 규칙 (0) | 2023.07.19 |