UE5

언리얼 모듈과 언리얼 게임플레이 모듈

게임 개발 2024. 5. 9. 18:33
언리얼 모듈 (Modules)

모듈이란 무엇이냐?

모듈은 빌딩 블록이다.

 

 

 

그런 모듈을 왜 쓰냐?

모듈은 언리얼 엔진 소프트웨어 아키텍처의 구성 요소로,

코드를 모듈로 청리하여 더 효율적이고 유지보수하기 좋은 프로젝트를 만들 수 있다.

UE에서 모듈은 특정 에티터 툴, 런타임 기능, 라이브러리 등의 기능을 독립된 코드로 캡슐화 한다.

 

모든 프로젝트와 플로그인에는 기본적으로 자체 프라이머리 모듈이 있지만,

모듈을 따로 정의하여 코드를 정리할 수 있다.

 

해당 게시글에서는 모듈의 구와 언리얼 엔진 프로젝트에 사용했을 때의 이점에 대한 개요를 다룬다.

 

 

그럼 게임 플레이 모듈은 무엇이냐?(1)

엔진 자체가 여러 모듈로 이루어져 있는 것과 마찬가지로,

각 게임은 하나 이상의 게임플레이 모듈로 이루어져 있다.

이들은 관련 클래스들의 모음을 담는 그릇이라는 점에서

이전 버전(ue4)에서의 개념과 비슷하다.

UE4에서는 게임플레이가 모두 C++로 처리되기에,

모듈은 별도의 패키치 파일이라기 보다는 사실상 DLL에 가깝다.

 

그럼 UE4와 달리 UE5.4에서는 DLL 개념과는 다른가?

내 생각에는 뭐 비슷한 거 같다.
밑의 설명을 읽다보면 왜 그런지 느낌이 올 것 이다.

물론 나보다 실력이 좋은 프로그래머들이 보기에는 내 이해도의 부족함을 느낄 수 있다.

(그럼 댓글로 살포시 알려주시면 너무 감사하겠습니다. 훈수환영)

 

기본적으로 게임 DLL 모듈성에 대해서는 철학적 선택이 있다.

게임을 다수의 DLL 파일로 나누는 것은 그 이득보다 문제가 많을 수 있지만,

(사실 DLL로 파일을 잘 빼놓으면 컴파일 타임이 확 준다.

이게 DLL의 가장 큰 장점이라고 생각한다.

근데 좋다고 남발하면 세상에 좋은 거 하나도 없다.

그 문제가 뭔지 알아보자.)

 

다중 게임 플레이 모듈을 사용하면 링크 시간이나 코드 반복처리 시간이 빨라지겠지만,

DLL 익스포트 및 인터페이스 클래스를 처리해야 하는 모듈 수가 늘어난다.

 

 

즉 모듈의 핵심이란?

자 그럼 모듈이 뭔지 핵심만 추려보자.

 

  • 언리얼 엔진 소프트웨어 아키텍처의 가장 기본적인 구성 요소
  • 엔진은 대규모 모듈 모음으로 구현되어 있음 
    (= 각 게임은 하나 이상의 게임 플레이 모듈로 이루어져 있음
    대규모 모듈 모음이다.
    이는 게임이 빌딩 블록(모듈)을 구조를 강화하게 된다.
    왜 그러냐? 자체 모듈을 제공하고 그 빌딩 블록(모듈)들의 모음으로 만들어진 엔진이니까)
  • 각 모듈은 기능을 캡슐화하고 다른 모듈에서 사용할 수 있도록 공용 인터페이스 및
    컴파일 환경 (매크로, 포함 경로 등)을 제공할 수 있음

 

모듈의 이점

 

프로젝트를 정리할 때 모듈을 사용하면 다음과 같은 이점이 있다.

 

  • 모듈은 적절한 코드 분리를 강제하여 함수 기능을 캡슐화하고 코드의 내부를 숨긴다.
  • 모듈은 각기 다른 컴파일 유닛으로 컴파일 된다.
    따라서 변경된 모듈만 컴파일하므로 대규모 프로젝트의 빌드 시간이 대폭 단축된다.
    (당연하다 DLL특징이니까,
    여기서 그럼 모듈이 DLL 개념과 분리되지 않는다는 것을 알 수 있다.)
  • 모듈은 종속성 그래프에서 서로 연결되며,
    눈에 보이는 것만 포함하는IWYU
    (종속성 모델을 사용하는 언리얼 엔진 코드 베이스,
    컴파일에 필요한 종속성을 포함한다는 의미,
    사전 컴파일된 헤더를 통해 빠르게 컴파일되는 파일에 의존
    이러한 구조를 남발하고 엔진이 커지면 병목 현상이 발생한다.
    (= 쉽게 말하면 컴퍼일 시간이 너무 느려지더라 라는 말인듯 하다.) )
    표준을 따라 헤더에 실제 사용되는 코드만 포함하도록 제한한다.
    컴파일 하는 동안 프로젝트에 사용하지 않는 모듈을 안전하게 제외할 수 있다는 뜻이다.
  • 런타임 시 지정한 모듈의 로드 및 언로드 시간을 조절할 수 있다.
    덕분에 사용될 수 있거나 활성화할 시스템을 관리하여 
    프로젝트의 퍼포먼스를 최적화할 수 있다.
    (DLL 특징 또 나왔다. 모듈과 DLL은 특징이 거의 혼연일체하다.
    이는 UE5라고 해서 모듈과 DLL과 관여가 없어졌냐?에서는 아니라고 본다.
    이에 대한 이해도는 나중에 OSCS관련 서적을 읽으며 DLL에 대한 개념을 보충하여
    좀 더 확신있게 따로 정리할 기회를 가지면 좋겠다.)
  • 프로젝트를 컴파일할 플랫폼 등 특정 조건에 따라
    프로젝트를 포함하거나 제외할 수 있다.

 

요약하자면, 모듈로 최상의 시나리오를 도출하여

프로젝트의 모든 코드를 단일 모듈에 넣을 때보다 잘 정리하고(당연하다 분리하면 정리 잘됨),
코드의 재사용성을 높일 수 있다.(= 코드의 재사용성 캡슐화 이점)

 

즉 정리하자면,

DLL의 이론을 기반으로 만들어진 모듈은

DLL의 장단점을 생각하면서 잘 이용해야 한다. 

또 캡슐화 이론을 기반으로 만들어진 구조이다.

 

그럼 멀티플레이어 모듈은 무엇인가?(2)

쓸 때 뭘 조심해야하나?

 

이런 모듈을 응용하여 만든 것이 멀티 게임플레이 모듈이다.

이러한 모듈의 특징을 잘 사용하기 위해서는,

컴파일 시간 측면에서 이득을 얻고자 DLL에서 착안한 모듈을

잘 활용하기 위해서는 교차 의존하는 모듈 생성하여 사용할 때 조심해서 사용하자.

그 이유는 컴파일 시간 측면에서 이상적이지 않다 (DLL의 이점을 가져가기 힘들다.)

이러한 이유로 변수 정적인 초기화에 가끔 문제를 일으킬 수 있다.

(왜? 나도 모른다. OSCS 책 더 공부하자.)

 

게임플레이 모듈을 교차 의존적이지 않게 구조를 구성하고 유지하는 것은 어려운 일이지만,

코드는 더욱 깔끔해 질 것이다.

 

어 근데 왜 기본은 DLL이라면서 모듈이라면서,

왜 DLL 익스포트 및 인터페이스 클래스를 처리하는 코드인 모듈의 수가 늘어나는 게

왜 안좋냐?

 

그 이유가 그냥 모듈과 멀티 게임플레이 모듈을 나눈 이유이다.

엔진이나 에디터 코드에서 해당 일을 처리하는 것이 올바른 구조이다.(UE에서는)

각자 할 일이 있는 이유가 다 있다.

 

예를 들어

클라한테 엔진일 맡기면 하긴 하는데

역할을 나눈 이유가 있기 때문에 클라가 엔진일 까지하면 나눈 이유가 의미가 없다.

엔진에서 돌아가야 하는 일이 있고, 클라에서 돌아가야 하는 일이 있는데

하나의 영역에 둘 영역의 일을 합쳐 놓으면 당연히 오류가 터질 것이다.

애초에 그렇게 안돌아가는 것을 지향하며 영역을 나누어 엔진을 설계했기 때문이다.

 

 

 

모듈의 구조 이해하기

 

모든 모듈은 플로그인 혹은 프로젝트의 Source 디렉터리에 배치해야 한다.

모듈의 루트 폴더는 해당 모듈과 이름이 동일해야 한다.

 

또한 루트 폴더의 모듈마다

[ModuleName].Build.cs 파일과 모듈의 C++ 코드가 

Private, Public 폴더에 있어야한다.

 

위 내용 하나만 이해하면 간단하다.

 

추가로 모듈은 캡슐화를 기반으로 만든 것이기에

언리얼에서 권장하는 구조는 다음과 같다.

 

숨길건 숨기고 숨기는 데이터에 접근할 때(보통 cpp)는

숨긴 데이터에 접근을 래핑한 Public에서 접근하도록 유도한다.

(헤더에 접근하도록 유도)

[ModuleName]
Private
[ModuleName]Module.cpp
모든 .cpp 파일 및 프라이빗 헤더
Public
모든 퍼블릭 헤더
[ModuleName].Build.cs

 

즉 캡슐화 구조를 가지는 것만 잘 이해하면

구조 이해하기 처음에 설명한 설정 값은 그렇구나 하면 되는 것이고,

밑의 내용이 중요한 것이다.

캡슐화 구조를 이해해야지 모듈의 구조를 이해하여 응용하며 사용할 수 있는 것이다.

 

 

Build.cs 파일에서 종속성 설정하기

 

언리얼 빌드 시스템 간단 요약

언리얼 빌드 시스템은 IDE에 대한 솔루션 대신

프로젝트의 Target.cs 파일과 모듈의 Build.cs 파이렝 따라 프로젝트를 빌드한다.

 

IDE 솔루션은 코드를 편집할 때 자동으로 생성되지만,

언리얼 빌드 툴 (Unreal Build Tool, UBT)은

프로젝트를 컴파일할 때 IDE 솔루션을 무시한다.

(솔직히 이 말이 정확하게 뭘 무시한다는 말인지 모르겠다.

근데 무시하면 문제가 생기는지 인식하도록 추가해줘야 한다고 공식문서에 써져있다.

IDE가 부족한 것인지, 언리얼 빌드 시스템이 부족한 것인지

좀 더 공부가 필요해 보인다.

내 예상으로는 캡슐화 된 파일의 연결성을 일일해 작업해줘야 한다는 뜻인 거 같다.)

 

 


(++ PLUS ++)
IDE란 무엇인가?
통합 개별 환경(IDE)란
프로그래머가 소프트웨어 코드를 효율적으로 개발하도록 돕는
소프트웨어 어플리케이션이다.
이는 소프트웨어 편집, 빌드, 테스트, 패키징과 같은 기능을 사용하기 쉬운
하나의 애플리케이션에 통합하여 개발자 생산성을 높인다.
작가가 텍스트 편집기를 사용하고,
회계사가 스프레트시트를 사용하는 것처럼
소프트웨어 개발자는 IDE를 사용해 작업을 쉽게 처리한다.

Visual Sudio나 Rider 같은 것들이 IDE라고 보면된다.

++++

 

 

언리얼 빌드 시스템에서 솔루션을 인식하도록 하려면

모든 모듈의 루트 디렉터리에 [ModuleName].Build.cs 파일을 배치해야 한다.

 

[ModuleName].Build.cs 파일 내부에서는 모듈을

ModuleRules 클래스에서 상속받은 클래스로 정의해야 한다.

 

다음은 간단한 Build.cs 파일의 예시이다.

	// Sample (ModuleTest.Build.cs 파일)
    using UnrealBuildTool; 
 
	public class ModuleTest: ModuleRules 
 
	{ 
 
	public ModuleTest(ReadOnlyTargetRules Target) : base(Target) 
 
	{ 
 
	PrivateDependencyModuleNames.AddRange(new string[] {"Core"}); 
 
	} 
 
	}

 

 

Build.cs 파일을 설정할 때는 주로

1) PrivateDependencyModuleNames(Private) ,

2) PublicDependecyModuleNames(Private)

목록을 사용한다.

 

이러한 목록에 모듈 이름을 추가하면

사용할 수 있는 모듈이 모듈 코드에 설정된다.

 

예를 들어 'Slate' 및 'SlateUI' 모듈 이름을 프라이빗 종속성 목록에 추가할 경우,

모듈에 SlateUI 클래스를 추가할 수 있다.

(캡슐화는 접근 하는걸 한번 숨겨주거나 래핑해주는 원리를 기본적으로 가진다.

모듈은 기본적으로 캡슐화의 원리를 기본으로 한다.

Private = 숨겨서 쓴다 = 안전성이 확보된다.

Public = 안숨긴다 = 접근성이 좋다 = 편하게 쓸 수 있다.

해당 특징을 잘 이해하고 어디에 배치해야 할지에 대한 구조 설계가 중요하다.)

 

Private 및 Public 폴더 사용하기

 

모듈이 일반 C++ 모듈일 경우 (ModuleType이 .uproject 혹은 .uplugin 에서 External 로 설정되지 않음),

모듈의 C++ 파일을 모듈의 루트 디렉터리에 속한 Private 및 Public 서브 폴더에 배치해야 한다.

 

이 파일들은 C++ 코드에 속한 Private, Public, Protected 액세스 지정자와 관련이 없다.

대신 다른 모듈의 모듈 코드에 대한 이용성을 제어한다.

이러한 폴더를 사용할 때 모든 .cpp 파일이 Private 폴더에 배치해야 한다.

헤더 (.h) 파일은 아래 가이드라인에 따라 Private 및 Public 폴더에 배치해야 한다.

 

헤더 파일을 Private 폴더에 배치한 경우,

이 콘텐츠는 보유 중인 모듈 외에 다른 모듈에 노출되지 않는다.

이 폴도에서 클래스, 구조체, 열거형은 같은 모듈의 다른 클래스에서 엑서스할 수 있지만,

다른 모듈의 클래스에서는 사용할 수 없다.

 

헤더를 Public 폴더에 배치한 경우,

언리얼 빌드 시스템이 콘텐츠를 현재 모듈에 종속된 모든 모듈에 노출한다.

외부 모듈의 클래스는 Public 폴더에 포함된 클래스를 확장할 수 있으므로,

Public 폴더에 포함된 클래스, 구조체, 열거형을 사용하는 변수 및 레퍼런스를 생성할 수 있다.

Private, Public, Protected 지정자는 평소처럼 함수 및 변수에서 유효하다.

 

다른 대상에 종족되지 않은 모듈을 작업할 경우 Private, Public 폴더를 사용할 필요가 없다.

두 폴더 외부의 모든 코드는 Private 폴더에 있는 것처럼 행동한다.

일반적인 예시로 게임에서 종속성 체인의 끝에 위치하는 프라이머리 모듈을 들 수 있다.

 

Public 및 Private 폴더에 서브 폴더를 생성하여 코드를 추가로 정리할 수도 있다.

Public 폴더에 새 폴더를 생성할 경우, Private 폴더에도 같은 이름의 폴더를 생성한다.

마찬가지로 헤더 파일을 Public 폴더에 배치할 때도

Private 폴더의 동일한 폴더에 .cpp 파일을 배치해야 한다.

 

언리얼 에디터의 신규 클래스 마법사(New Class Wizard)로 새클래스를 생성하면

자동으로 두 폴더 간의 병렬 구조를 형성한다.

 

C++에서 모듈 구현하기

 

모듈을 나머지 C++ 프로젝트에 노출하려면 IModuleInterface 를 확장하는 클래스를 생성 한 뒤

IMPLEMENT_MODULE 매크로를 추가해야 한다.

 

가장 간단한 구현 방법은 Private 디렉터리의 모듈에서 

.cpp 파일을 생성하고 이름을 [ModuleName]Module.cpp로 지정하는 것이다.

여기서 [ModuleNmae]은 모듈의 이름이다.

다른 #include 선언을 전부 끝낸 후 IMPLEMENT_MODULE 매크로를 호출하여

FDefaultModuleImpl 을 클래스로 제공해야 하면 된다.

 

//ModuleTestModule.cpp

	#include "Modules/ModuleManager.h"
 
	IMPLEMENT_MODULE(FDefaultModuleImpl, ModuleTest);

 

 

FDefaultModuleImpl 은 IModuleInterface를 확장하는 빈 클래스이다.

이 .cpp 파일에 구현할 클래스를 직접 작성하면 더 디텔한 구현이 가능하다.

 

IModuleInterface 에는 모듈이 GameInstance 클래스의 Startup, Shutdown 함수처럼

로드/언로드를 수행할 때 트리거되는 여러 함수가 포함된다.

 

 

프로젝트에 모듈 사용하기

 

신규 언리얼 엔진 프로젝트나 플러그인을 생성할 때마다 

자체 프라이머리 모듈을 프로젝트의 Source 폴더에 자동으로 구성한다.

외부 모듈을 프로젝트 내 프라이머리 모듈 Build.cs 파일에 추가하여 프로젝트에 포함할 수 있다.

 

예를 들어 MyProject라는 이름의 프로젝트에

게임플레이 태스크(Gameplay Tasks)시스템을 사용하려는 경우,

MyProject.Build.cs를 실행한 뒤 'GameplayTasks'모듈을 종속성으로 추가해야 한다.

 

언리얼 빌드 툴에서는 컴파일 속도를 최적화하기 위해

프로젝트의 종속성 체인에서 발견한 모듈만 컴파일한다.

즉, 모듈이 프로젝트에서 사용하는 어떤 Build.cs 파일에도 포함되지 않을 경우,

컴파일 중에 해당 모듈은 제외된다.

 

 

모듈 로드 방식 제어하기

 

.uproject 및 .uplugin 파일에는 프로젝트에 포함할 모듈과 로드 방식을 정의하는 Module 목록이 존재한다.

 

프로젝트 파일을 재생성할 때 모듈에 대한 항목이 존재하지 않을 경우,

이 항목을 목록에 자동으로 추가한다.

원래는 모듈 관련 항목이 종속성 체인에 포함되어 있다고 가정하기 때문이다.

이 목록에 포함되는 항목은 다음과 같다.

 

 

	"Modules": [
 
		{
 
			"Name": "ModuleTest",
 
			"Type": "Runtime",
 
	"LoadingPhase": "Default",
 
		},
 
	{
 
	"Name": "ModuleTestEditor",
 
	"Type": "Editor",
 
	}
 
	]

 

 

게임플레이 모듈 대부분 이름(Name)만 목록에 추가되며, 타입(Type)은 Runtime으로 설정된다.

모듈의 로딩 페이즈(LoadingPhase)가 정의되지 않았다면 Default 로 설정된다.

이외에도 다양한 모듈 타입, 로딩 페이즈, 모듈을 로드하거나

로드하지 않을 플랫폼을 제어하는 추가 파라미터가 있다.

 

사용 가능한 모듈 타입에 관한 정보는 ELoadingPhase::Type의 API문서 참고바람.

 

가장 흔한 모듈 타입은 Runtime과 Editor 이며,

각각 인게임 클래스와 에디터 전용 클래스에 사용된다.

 

로딩 페이즈에 관한 자세한 정보는 ELoadingPhase::Type의 API 문서를 참조바람.

 

Default 로딩 페이즈는 프로젝트에서 사용하는 게임플레이 모듈 대부분에 적합하지만,

플러그인은 미리 로딩해야 할 수 있다.

언리얼 에디터에서 플러그인의 C++ 클래스를 찾다가 오류가 자주 발생하면

플러그인 설정을 PreDefault 로 변경해 보자.

 

이 목록에서 사용하는 기타 파라미터는 다음과 같다.

 

파라미터 설명
IncludelistPlatforms /
ExcludelistPlatforms
(배열)
목록에 포함된 플랫폼에서 컴파일할 모듈을 포함하거나 제외한다.
플랫폼 스트링의 예시로
Win32, Win64, Mac, Linux, Android, IOS 가 있다.
IncludelistTargets /
ExcludelistTargets
(배열)
목록에 포함된 빌드 타깃에서 컴파일할 모듈을 포함하거나 제외한다.
사용할 수 있는 빌드 타깃은
Game, Server, Client, Editor, Program 이다.
IncludelistTargetConfigurations /
ExcludelistTargetConfigurations
(배열)
목록에 포함된 빌드 환경설정에서 컴파일할 모듈을 포함하거나 제외한다.
사용할 수 있는 타깃 환경 설정은
Debug, DebugGame, Development, Shipping, Test이다.
IncludelistPrograms /
ExcludelistPrograms
(배열)
특정 프로그램 이름 범위 내에서 컴파일할 모듈을 포함하거나 재외한다.
AdditionalDependencies (배열) 모듈에서 필요로 하는 추가 종속성을 지정해야 한다.
대신 Build.cs 파일에서 지정해야 한다.

 

 

 

 

참고 문서

https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/GameplayArchitecture/Gameplay/

https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/BuildTools/UnrealBuildTool/ModuleFiles/

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gameplay-modules-in-unreal-engine

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/include-what-you-use-iwyu-for-unreal-engine-programming

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/unreal-engine-modules?application_version=5.4

'UE5' 카테고리의 다른 글

서버 연동 프로젝트 KU - 1. 서버 연동하기  (0) 2024.05.10
ESPMode  (0) 2024.05.10
Unreal Engine - 멀티플레이어 게임에서의 이동  (0) 2024.05.04
0309 작업 현황  (0) 2024.03.09
작업 현황 0307  (0) 2024.03.07