에디터 모듈 아키텍처
UE5 에디터 확장의 시작점 — 모듈 시스템의 구조와 생명주기를 이해하고 첫 에디터 모듈을 구축합니다
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 가드보다 모듈 분리가 더 안전하고 권장되는 방식입니다.
모듈 로딩 페이즈(Loading Phase)
모듈이 언제 로드되는지 결정하는 핵심 설정
.uproject 또는 .uplugin 파일의 LoadingPhase 설정은 모듈이 엔진 초기화의 어느 시점에 로드될지를 결정합니다.
| LoadingPhase | 시점 | 용도 |
|---|---|---|
EarliestPossible |
가능한 최초 시점 | 로깅, 기본 시스템 |
PostConfigInit |
설정 시스템 초기화 후 | 설정 의존 모듈 |
PreDefault |
기본 로딩 직전 | 다른 모듈에 먼저 등록 필요 시 |
Default |
일반 로딩 시점 | 대부분의 게임플레이 모듈 |
PostDefault |
기본 로딩 직후 | 다른 모듈 의존 시 |
PostEngineInit |
엔진 완전 초기화 후 | 에디터 UI 확장, 슬레이트 의존 |
None |
자동 로드 안 함 | 수동 로드 필요 모듈 |
에디터 UI를 확장하는 모듈은 PostEngineInit를 사용해야 합니다. Slate 시스템이 초기화되기 전에 UI를 등록하면 크래시가 발생할 수 있습니다.
.uproject 모듈 선언 예시
{
"Modules": [
{
"Name": "MyGame",
"Type": "Runtime",
"LoadingPhase": "Default"
},
{
"Name": "MyGameEditor",
"Type": "Editor",
"LoadingPhase": "PostEngineInit"
}
]
}
IModuleInterface와 모듈 생명주기
에디터 모듈의 진입점과 생명주기 콜백
모든 UE5 모듈은 IModuleInterface를 구현합니다. 이 인터페이스는 모듈의 시작과 종료 시 호출되는 콜백을 정의합니다.
핵심 가상 함수
| 함수 | 호출 시점 | 주요 작업 |
|---|---|---|
StartupModule() |
모듈 로드 시 | 리소스 초기화, 확장 등록, 델리게이트 바인딩 |
ShutdownModule() |
모듈 언로드 시 | 리소스 정리, 확장 해제, 델리게이트 언바인딩 |
IsGameModule() |
쿼리 시 | 핫 리로드 대상 여부 반환 |
에디터 모듈 기본 구현
#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;
};
#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은 일반 모듈 등록에 사용합니다. 게임 모듈(핫 리로드 지원)에는 IMPLEMENT_GAME_MODULE을 사용할 수 있지만, 에디터 전용 모듈에는 IMPLEMENT_MODULE이 적합합니다.
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
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에 에디터 모듈 등록
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"
});
}
}
실습: 첫 에디터 모듈 생성
단계별로 에디터 모듈을 생성하고 동작을 확인합니다
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에서 확인
LogTemp: MyGameEditor module started
- Build.cs에 필요한 모듈 의존성이 모두 선언되었는지 확인
- .uproject에 모듈이 올바른 Type과 LoadingPhase로 등록되었는지 확인
- Editor.Target.cs에 모듈 이름이 추가되었는지 확인
IMPLEMENT_MODULE매크로의 모듈 이름이 Build.cs 이름과 일치하는지 확인
핵심 요약
- UE5는 모듈 기반 아키텍처로 구성되며, 에디터 확장 코드는 반드시 Editor 타입 모듈에 분리해야 합니다
- LoadingPhase가 모듈 로딩 시점을 결정하며, 에디터 UI 확장에는 PostEngineInit가 권장됩니다
- IModuleInterface의
StartupModule()과ShutdownModule()이 모듈 생명주기의 진입/종료 포인트입니다 - Build.cs에서 UnrealEd, Slate, SlateCore, ToolMenus 등 에디터 핵심 모듈에 대한 의존성을 선언합니다
- .uproject, Editor.Target.cs, Build.cs, IMPLEMENT_MODULE 매크로가 모두 올바르게 설정되어야 모듈이 정상 로드됩니다
도전 과제
배운 내용을 직접 실습해보세요
Source 폴더에 MyProjectEditor 모듈을 새로 생성하세요. IModuleInterface를 구현하고, StartupModule()에서 UE_LOG로 로딩 메시지를 출력하세요. .uproject의 Modules 배열에 Type=Editor, LoadingPhase=PostEngineInit로 등록하고 에디터에서 로그를 확인하세요.
Build.cs에 UnrealEd, Slate, SlateCore, ToolMenus, PropertyEditor 의존성을 추가하세요. 각 모듈의 역할을 주석으로 문서화하고, 불필요한 의존성을 포함했을 때 빌드 시간에 미치는 영향을 측정하세요.
IModuleInterface를 확장하여 에디터 모듈의 핫 리로드를 지원하는 플러그인 구조를 구현하세요. StartupModule에서 에디터 확장을 등록하고, ShutdownModule에서 모든 확장을 정리하여 메모리 누수 없이 핫 리로드가 가능한 구조를 만드세요.