PART 1 · 강의 1/3

에디터 모듈 아키텍처

UE5 에디터 확장의 시작점 — 모듈 시스템의 구조와 생명주기를 이해하고 첫 에디터 모듈을 구축합니다

01

UE5 모듈 시스템 개요

언리얼 엔진의 모듈 기반 아키텍처와 에디터 모듈의 역할

언리얼 엔진은 모듈(Module) 단위로 기능이 분리됩니다. 하나의 프로젝트는 여러 모듈로 구성되며, 각 모듈은 독립적인 DLL(Windows) 또는 공유 라이브러리로 컴파일됩니다.

모듈이란?

모듈은 UE5의 코드 구성 단위입니다. 각 모듈은 자체적인 .Build.cs 파일을 가지며, 의존성 선언, 포함 경로, 빌드 설정을 독립적으로 관리합니다.

모듈의 종류

모듈 타입 설명 예시 패키징 포함
Runtime 게임 런타임에 로드되는 모듈 Core, Engine, MyGame O
Editor 에디터에서만 로드되는 모듈 UnrealEd, MyGameEditor X
Developer 개발 중에만 사용되는 유틸리티 AutomationController X
ThirdParty 외부 라이브러리 래퍼 모듈 PhysX, Steamworks 경우에 따라
에디터 전용 코드 분리

에디터 확장 코드는 반드시 Editor 모듈에 작성해야 합니다. Runtime 모듈에 에디터 코드를 넣으면 패키징 시 참조 오류가 발생합니다. #if WITH_EDITOR 가드보다 모듈 분리가 더 안전하고 권장되는 방식입니다.

02

모듈 로딩 페이즈(Loading Phase)

모듈이 언제 로드되는지 결정하는 핵심 설정

.uproject 또는 .uplugin 파일의 LoadingPhase 설정은 모듈이 엔진 초기화의 어느 시점에 로드될지를 결정합니다.

LoadingPhase 시점 용도
EarliestPossible 가능한 최초 시점 로깅, 기본 시스템
PostConfigInit 설정 시스템 초기화 후 설정 의존 모듈
PreDefault 기본 로딩 직전 다른 모듈에 먼저 등록 필요 시
Default 일반 로딩 시점 대부분의 게임플레이 모듈
PostDefault 기본 로딩 직후 다른 모듈 의존 시
PostEngineInit 엔진 완전 초기화 후 에디터 UI 확장, 슬레이트 의존
None 자동 로드 안 함 수동 로드 필요 모듈
주의: PostEngineInit

에디터 UI를 확장하는 모듈은 PostEngineInit를 사용해야 합니다. Slate 시스템이 초기화되기 전에 UI를 등록하면 크래시가 발생할 수 있습니다.

.uproject 모듈 선언 예시

MyProject.uproject { "Modules": [ { "Name": "MyGame", "Type": "Runtime", "LoadingPhase": "Default" }, { "Name": "MyGameEditor", "Type": "Editor", "LoadingPhase": "PostEngineInit" } ] }
03

IModuleInterface와 모듈 생명주기

에디터 모듈의 진입점과 생명주기 콜백

모든 UE5 모듈은 IModuleInterface를 구현합니다. 이 인터페이스는 모듈의 시작과 종료 시 호출되는 콜백을 정의합니다.

핵심 가상 함수

함수 호출 시점 주요 작업
StartupModule() 모듈 로드 시 리소스 초기화, 확장 등록, 델리게이트 바인딩
ShutdownModule() 모듈 언로드 시 리소스 정리, 확장 해제, 델리게이트 언바인딩
IsGameModule() 쿼리 시 핫 리로드 대상 여부 반환

에디터 모듈 기본 구현

MyGameEditor.h #pragma once #include "CoreMinimal.h" #include "Modules/ModuleInterface.h" class FMyGameEditorModule : public IModuleInterface { public: // IModuleInterface virtual void StartupModule() override; virtual void ShutdownModule() override; virtual bool IsGameModule() const override { return true; } private: // 확장 핸들 저장 (해제 시 필요) TSharedPtr<FExtensibilityManager> MenuExtensibilityManager; };
MyGameEditor.cpp #include "MyGameEditor.h" #include "Modules/ModuleManager.h" void FMyGameEditorModule::StartupModule() { // 에디터 확장 등록 UE_LOG(LogTemp, Log, TEXT("MyGameEditor module started")); // 에디터 UI가 준비된 후 확장을 등록 if (FSlateApplication::IsInitialized()) { // Slate UI 확장 등록 (메뉴, 툴바 등) } } void FMyGameEditorModule::ShutdownModule() { // 등록한 모든 확장 해제 UE_LOG(LogTemp, Log, TEXT("MyGameEditor module shut down")); } // 모듈 등록 매크로 IMPLEMENT_MODULE(FMyGameEditorModule, MyGameEditor)
IMPLEMENT_MODULE vs IMPLEMENT_GAME_MODULE

IMPLEMENT_MODULE은 일반 모듈 등록에 사용합니다. 게임 모듈(핫 리로드 지원)에는 IMPLEMENT_GAME_MODULE을 사용할 수 있지만, 에디터 전용 모듈에는 IMPLEMENT_MODULE이 적합합니다.

04

Build.cs 설정

에디터 모듈의 빌드 의존성과 디렉터리 구조

디렉터리 구조

프로젝트 구조 Source/ ├── MyGame/ // Runtime 모듈 │ ├── MyGame.Build.cs │ ├── MyGame.h │ ├── MyGame.cpp │ └── ... └── MyGameEditor/ // Editor 모듈 ├── MyGameEditor.Build.cs ├── MyGameEditor.h ├── MyGameEditor.cpp ├── Public/ │ └── ... └── Private/ └── ...

에디터 모듈 Build.cs

MyGameEditor.Build.cs using UnrealBuildTool; public class MyGameEditor : ModuleRules { public MyGameEditor(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "MyGame", // 게임 런타임 모듈 참조 }); PrivateDependencyModuleNames.AddRange(new string[] { "UnrealEd", // 에디터 핵심 "Slate", // Slate UI 프레임워크 "SlateCore", // Slate 코어 "EditorStyle", // 에디터 스타일 "InputCore", // 입력 처리 "ToolMenus", // 메뉴 시스템 "PropertyEditor", // 프로퍼티 에디터 "LevelEditor", // 레벨 에디터 확장 "EditorFramework", // 에디터 프레임워크 }); } }
핵심 에디터 모듈 의존성

에디터 확장 시 자주 사용하는 모듈:

  • UnrealEd — 에디터 기본 기능 (FAssetEditorToolkit, GEditor 등)
  • Slate / SlateCore — UI 위젯 시스템
  • ToolMenus — UE5 메뉴/툴바 시스템
  • PropertyEditor — 디테일 패널 커스터마이징
  • LevelEditor — 레벨 에디터 확장점
  • EditorFramework — 에디터 탭/독 시스템

Target.cs에 에디터 모듈 등록

MyGameEditor.Target.cs using UnrealBuildTool; public class MyGameEditorTarget : TargetRules { public MyGameEditorTarget(TargetInfo Target) : base(Target) { Type = TargetType.Editor; DefaultBuildSettings = BuildSettingsVersion.V5; IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_5; ExtraModuleNames.AddRange(new string[] { "MyGame", "MyGameEditor" }); } }
05

실습: 첫 에디터 모듈 생성

단계별로 에디터 모듈을 생성하고 동작을 확인합니다

Step 1: 디렉터리 생성

Source/MyGameEditor/ 디렉터리를 생성하고 Public/, Private/ 서브디렉터리를 만듭니다.

Step 2: Build.cs 작성

위의 MyGameEditor.Build.cs 예제를 참고하여 빌드 파일을 작성합니다.

Step 3: 모듈 헤더/소스 작성

IModuleInterface를 구현하는 모듈 클래스를 작성합니다. StartupModule()에 로그를 추가하여 모듈 로딩을 확인합니다.

Step 4: .uproject에 모듈 등록

등록 확인 포인트 // .uproject의 Modules 배열에 추가 { "Name": "MyGameEditor", "Type": "Editor", "LoadingPhase": "PostEngineInit" } // Editor.Target.cs의 ExtraModuleNames에 추가 ExtraModuleNames.Add("MyGameEditor");

Step 5: 빌드 및 확인

Output Log 확인 // 에디터 실행 후 Output Log에서 확인 LogTemp: MyGameEditor module started
빌드 오류 체크리스트
  • Build.cs에 필요한 모듈 의존성이 모두 선언되었는지 확인
  • .uproject에 모듈이 올바른 Type과 LoadingPhase로 등록되었는지 확인
  • Editor.Target.cs에 모듈 이름이 추가되었는지 확인
  • IMPLEMENT_MODULE 매크로의 모듈 이름이 Build.cs 이름과 일치하는지 확인
SUMMARY

핵심 요약

  • UE5는 모듈 기반 아키텍처로 구성되며, 에디터 확장 코드는 반드시 Editor 타입 모듈에 분리해야 합니다
  • LoadingPhase가 모듈 로딩 시점을 결정하며, 에디터 UI 확장에는 PostEngineInit가 권장됩니다
  • IModuleInterfaceStartupModule()ShutdownModule()이 모듈 생명주기의 진입/종료 포인트입니다
  • Build.cs에서 UnrealEd, Slate, SlateCore, ToolMenus 등 에디터 핵심 모듈에 대한 의존성을 선언합니다
  • .uproject, Editor.Target.cs, Build.cs, IMPLEMENT_MODULE 매크로가 모두 올바르게 설정되어야 모듈이 정상 로드됩니다
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 에디터 모듈 생성 및 로그 확인

Source 폴더에 MyProjectEditor 모듈을 새로 생성하세요. IModuleInterface를 구현하고, StartupModule()에서 UE_LOG로 로딩 메시지를 출력하세요. .uproject의 Modules 배열에 Type=Editor, LoadingPhase=PostEngineInit로 등록하고 에디터에서 로그를 확인하세요.

실습 2: 에디터 모듈 의존성 설정

Build.cs에 UnrealEd, Slate, SlateCore, ToolMenus, PropertyEditor 의존성을 추가하세요. 각 모듈의 역할을 주석으로 문서화하고, 불필요한 의존성을 포함했을 때 빌드 시간에 미치는 영향을 측정하세요.

심화 과제: 핫 리로드 지원 에디터 플러그인 모듈

IModuleInterface를 확장하여 에디터 모듈의 핫 리로드를 지원하는 플러그인 구조를 구현하세요. StartupModule에서 에디터 확장을 등록하고, ShutdownModule에서 모든 확장을 정리하여 메모리 누수 없이 핫 리로드가 가능한 구조를 만드세요.