본문 바로가기

플러그인 개발

언리얼 플러그인의 디렉터리와 모듈 구조를 이해하고 Build.cs와 모듈 인터페이스를 구성합니다.

프로젝트가 커지면 여러 게임에서 함께 쓰고 싶은 기능이 생깁니다. 인벤토리 시스템, 대화 시스템, 에디터에서 레벨을 검사하는 도구 같은 것들입니다. 이런 기능을 게임 모듈 안에 섞어 두면 다른 프로젝트로 옮길 때 코드를 골라내야 하고, 엔진 소스를 직접 고쳐 넣으면 엔진을 업그레이드할 때마다 고친 부분을 다시 맞춰야 합니다. 언리얼은 이런 기능을 독립된 단위로 묶어 붙이고 뗄 수 있게 하는 플러그인(plugin)을 제공합니다.

10장의 플랫폼별 빌드와 패키징부터 배포와 운영을 다뤘다면, 11장에서는 엔진을 넓히는 방법을 살펴봅니다. 이 글에서는 플러그인의 개념과 종류, 디렉터리와 모듈 구조, Build.cs와 모듈 인터페이스를 구성하는 방법, 외부 라이브러리 통합과 배포까지 차례로 정리합니다. 플러그인을 잘 나누면 재사용성, 유지 보수성, 팀 협업 효율을 함께 높일 수 있습니다.


플러그인이란

플러그인은 언리얼의 기능을 넓히거나, 특정 기능 묶음을 다시 쓸 수 있는 형태로 감싸 두는 독립적인 단위입니다. 엔진 자체를 고치지 않고도 새 기능을 더하거나 기존 기능을 바꿀 수 있게 해 줍니다.

플러그인을 쓰면 특정 기능을 독립된 단위로 떼어 다른 프로젝트에서도 쉽게 다시 쓸 수 있습니다. 엔진 소스 코드를 직접 고칠 필요 없이 프로젝트에 플러그인을 더하거나 빼는 것만으로 기능을 켜고 끌 수 있고, 엔진 소스를 건드리지 않으므로 엔진을 업데이트할 때 충돌 위험이 적고 옮기기도 쉽습니다. 만든 기능을 Fab 같은 배포 채널에서 다른 개발자와 나누거나 판매할 수도 있습니다.


플러그인의 종류

플러그인은 기능에 따라 여러 종류로 나눌 수 있습니다. 런타임 플러그인은 패키지 게임에서도 필요한 기능을 제공하며, 게임플레이 로직, 새로운 렌더링 기능, 네트워크 기능 같은 데 쓰입니다. 에디터 없이도 동작하는 모듈이며, 에디터에서도 로드될 수 있습니다. 에디터 플러그인은 언리얼 에디터의 기능을 넓히는 것으로, 커스텀 에셋 에디터, 툴바 버튼, 메뉴 확장, 작업 자동화에 쓰입니다. 에디터 모듈은 패키지 게임에서 로드되지 않지만, 그 도구로 만든 에셋과 설정은 게임 결과에 영향을 줄 수 있습니다.

그 밖에 개발 과정을 돕는 개발 도구, 블루프린트에서 쓸 새 노드나 함수를 제공하는 블루프린트 라이브러리 플러그인, VR 헤드셋이나 입력 장치 같은 하드웨어와 연동하는 하드웨어 통합 플러그인이 있습니다. 개발 도구처럼 용도로 부르는 이름과, 모듈에 지정하는 Type은 다른 개념이므로, 대상 버전에서 지원하는 Runtime, Developer, Editor 같은 실제 모듈 유형을 골라야 합니다.

플러그인 하나에 여러 종류의 기능이 함께 들어갈 수도 있습니다. 예를 들어 게임 실행 중에 동작하는 기능과, 그 기능을 설정하는 에디터 UI를 함께 제공하는 플러그인입니다.


플러그인 만들기와 구조

플러그인은 에디터에서 쉽게 만들 수 있습니다. 프로젝트를 열고 Edit 메뉴의 Plugins를 고르면 플러그인 창이 열리는데, 여기서 새 플러그인을 만드는 버튼(버전에 따라 New Plugin이나 + Add로 표시됩니다)을 눌러 새 플러그인 마법사를 엽니다. 템플릿은 가장 기본적인 빈 플러그인인 Blank, 콘텐츠만 담는 Content Only, 외부 라이브러리를 통합하기 위한 Third Party Library, 에디터에 독립 창을 더하는 Editor Standalone Window 등이 있습니다. 템플릿을 고르고 플러그인 이름과 위치를 정한 뒤 Create Plugin을 누릅니다. 새 플러그인 폴더는 보통 프로젝트 루트의 Plugins 폴더에 생기며, 엔진 설치 폴더의 Engine/Plugins에 둘 수도 있습니다.

만들어진 플러그인 폴더는 다음과 같은 기본 구조를 가집니다.

MyPlugin.uplugin (플러그인 메타데이터 파일)
MyPlugin.cpp
MyPluginModule.cpp
MyPlugin.h
IMyPluginModule.h
MyPlugin.Build.cs (빌드 설정 파일)
MyPluginEditor.Build.cs
Icon128.png

.uplugin 파일은 플러그인의 메타데이터를 담은 JSON 파일로, 플러그인 이름, 버전, 설명, 로딩 단계(LoadingPhase), 그리고 들어 있는 모듈의 목록을 정의합니다. Source/[PluginName]/ 폴더는 플러그인의 C++ 코드와 빌드 설정을 담은 모듈 폴더입니다. 그 안의 Public/에는 다른 모듈이 쓸 수 있는 공개 헤더, 즉 플러그인의 API를 두고, Private/에는 내부 구현을 둡니다. [PluginName].Build.cs는 모듈의 빌드 설정 파일로, PublicDependencyModuleNames와 PrivateDependencyModuleNames 같은 의존성, 매크로 정의, 라이브러리 포함 경로를 지정합니다.


플러그인 개발하기

Build.cs 설정

플러그인의 중심은 Build.cs 파일이며, 여기서 다른 언리얼 모듈과의 의존성을 정의합니다. Build.cs는 플러그인 전체가 아니라 그 안의 모듈 하나에 대한 빌드 규칙이라서, 모듈마다 하나씩 두고 클래스 이름을 모듈 이름과 맞춥니다. 아래 파일에서 영어 주석만 있는 빈 목록은 플러그인 템플릿이 만들어 두는 자리이므로, 필요할 때만 채우면 됩니다. 실제로 볼 곳은 세 가지 의존성 목록이 각각 어떤 경우에 쓰이는지입니다.

MyPlugin.Build.cs
using UnrealBuildTool;

public class MyPlugin : ModuleRules
{
    public MyPlugin(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;

        PublicIncludePaths.AddRange(
            new string[] {
                // ... add public include paths required here ...
            }
            );

        PrivateIncludePaths.AddRange(
            new string[] {
                // ... add other private include paths required here ...
            }
            );

        PublicDependencyModuleNames.AddRange( // 다른 모듈에서 이 플러그인의 public 헤더를 사용할 때 필요한 종속성
            new string[] {
                "Core",
                "CoreUObject",
                "Engine",
                "UMG" // UMG 위젯을 사용한다면 추가
                // ... add other public dependencies that you statically link with here ...
            }
            );

        PrivateDependencyModuleNames.AddRange( // 이 플러그인 내부에서만 사용하는 종속성
            new string[] {
                // "Slate", // 에디터 플러그인이 UI를 사용한다면
                // "SlateCore",
                // ... add private dependencies that you statically link with here ...
            }
            );

        DynamicallyLoadedModuleNames.AddRange( // 이 대상에서 사용할 수 있고 명시적으로 로드하는 모듈
            new string[] {
                // ... add any modules that your module may dynamically load here ...
            }
            );
    }
}

PublicDependencyModuleNames에는 이 모듈의 공개 헤더가 쓰는 모듈을 넣어, 이 플러그인을 쓰는 다른 모듈도 그 모듈을 쓸 수 있게 합니다. PrivateDependencyModuleNames에는 이 모듈의 내부 구현에서만 쓰는 모듈을, DynamicallyLoadedModuleNames에는 실행 중에 명시적으로 불러올 모듈을 넣습니다.

.uplugin의 Modules에는 Name: "MyPlugin", Type: "Runtime", LoadingPhase: "Default"처럼 모듈을 등록합니다. 이 이름은 Source 폴더 이름, Build.cs의 클래스 이름, IMPLEMENT_MODULE의 식별자와 같아야 합니다. 에디터 모듈은 Modules에 따로 항목을 두고 Type을 Editor로 정합니다. 다른 플러그인에 의존한다면 Build.cs의 모듈 의존성뿐 아니라 .uplugin의 Plugins 항목에도 그 플러그인을 선언했는지 확인합니다.

더 깊이: 모듈은 엔진 시작의 어느 시점에 로드될까

.uplugin의 LoadingPhase는 엔진이 시작하는 과정의 어느 시점에 모듈을 자동으로 로드할지 정합니다. ELoadingPhase의 API 문서에 따르면 Default는 시작 과정의 기본 로드 시점으로 엔진 초기화 도중에 해당하고, 그 바로 앞이 PreDefault, 바로 뒤가 PostDefault입니다. PostEngineInit은 엔진 초기화가 끝난 뒤입니다. 더 이른 단계로는 설정 시스템이 초기화된 직후인 PostConfigInit, 로딩 화면이 시작되기 전에 끼어들어야 하는 모듈을 위한 PreLoadingScreen, 압축 형식처럼 파일을 읽는 데 필요한 플러그인을 위한 EarliestPossible이 있습니다. None으로 두면 자동으로 로드하지 않습니다.

Epic의 Modules 문서는 프로젝트의 게임플레이 모듈 대부분에 Default가 알맞지만, 플러그인은 더 일찍 로드되어야 할 수 있으므로 그때는 PreDefault를 시도해 보라고 안내합니다. 단계가 이르다는 것은 그만큼 준비되지 않은 엔진 기능이 많다는 뜻이기도 합니다. 예를 들어 PostConfigInit 단계는 엔진이 완전히 초기화되기 전이므로, 이 단계에서 로드되는 모듈의 StartupModule이 월드나 게임플레이 객체가 있다고 가정해서는 안 됩니다.

모듈 인터페이스 정의하기

플러그인의 각 모듈은 IModuleInterface를 상속해 구현합니다. 이 인터페이스는 모듈이 로드될 때 불리는 StartupModule과 언로드될 때 불리는 ShutdownModule을 정의합니다. 먼저 다른 모듈이 쓸 공개 인터페이스를 선언합니다.

Public/IMyPluginModule.h
#pragma once

#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"

// 플러그인 모듈 인터페이스를 선언합니다.
class MYPLUGIN_API IMyPluginModule : public IModuleInterface
{
public:
    /**
     * 모듈의 인스턴스를 가져옵니다.
     */
    static inline IMyPluginModule& Get()
    {
        return FModuleManager::LoadModuleChecked<IMyPluginModule>("MyPlugin");
    }

    /**
     * 모듈이 로드되었는지 확인합니다.
     */
    static inline bool IsAvailable()
    {
        return FModuleManager::Get().IsModuleLoaded("MyPlugin");
    }

    // 여기에 플러그인이 외부에 노출할 함수를 선언합니다.
    virtual void MyCustomFunction() = 0;
};

Get()은 FModuleManager::LoadModuleChecked로 모듈을 불러와 돌려주고, IsAvailable()은 모듈이 이미 로드되었는지 확인합니다. MyCustomFunction은 플러그인이 바깥에 공개할 함수로, 순수 가상 함수로 선언했습니다.

Private/MyPluginModule.cpp
#include "IMyPluginModule.h"

// 공개 인터페이스를 구현하는 클래스는 이 번역 단위에서만 사용합니다.
class FMyPluginModule final : public IMyPluginModule
{
public:
    virtual void StartupModule() override;
    virtual void ShutdownModule() override;
    virtual void MyCustomFunction() override;
};

#define LOCTEXT_NAMESPACE "FMyPluginModule"

// 모듈을 구현하고 등록합니다.
void FMyPluginModule::StartupModule()
{
    // 모듈 시작 시 수행할 초기화 로직
    UE_LOG(LogTemp, Log, TEXT("MyPlugin Module Started!"));
    // Add custom functionality here
}

void FMyPluginModule::ShutdownModule()
{
    // 모듈 종료 시 수행할 정리 로직
    UE_LOG(LogTemp, Log, TEXT("MyPlugin Module Shut Down!"));
    // Clean up any resources here
}

void FMyPluginModule::MyCustomFunction()
{
    UE_LOG(LogTemp, Warning, TEXT("My Custom Function called from MyPlugin!"));
}

#undef LOCTEXT_NAMESPACE

// 언리얼 엔진에 모듈을 등록하는 매크로
IMPLEMENT_MODULE(FMyPluginModule, MyPlugin)

구현 클래스 FMyPluginModule은 이 cpp 파일 안에서만 쓰며, StartupModule과 ShutdownModule에서 초기화와 정리를 하고, MyCustomFunction을 구현합니다. 마지막 줄의 IMPLEMENT_MODULE(FMyPluginModule, MyPlugin)이 이 클래스를 MyPlugin이라는 이름의 모듈로 엔진에 등록합니다.

플러그인의 기능 구현하기

플러그인의 실제 기능은 이 모듈 안에 만듭니다. 새 액터 컴포넌트, 블루프린트 라이브러리 함수, 커스텀 에디터 모드 같은 것을 만들 수 있습니다. 다음은 블루프린트 라이브러리 함수를 더하는 예입니다.

Public/MyPluginBPLibrary.h
#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyPluginBPLibrary.generated.h"

UCLASS()
class MYPLUGIN_API UMyPluginBPLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintCallable, Category = "MyPluginFunctions")
    static void PrintMyPluginMessage(FString Message);
};
Private/MyPluginBPLibrary.cpp
#include "MyPluginBPLibrary.h"

void UMyPluginBPLibrary::PrintMyPluginMessage(FString Message)
{
    UE_LOG(LogTemp, Warning, TEXT("MyPlugin Message: %s"), *Message);
}

UBlueprintFunctionLibrary를 상속한 클래스의 static 함수에 BlueprintCallable을 붙이면, 어느 블루프린트에서나 부를 수 있는 노드가 됩니다. 이 함수는 받은 문자열을 로그로 남깁니다.

이 로그 함수 자체에는 UMG가 필요하지 않습니다. 앞의 Build.cs에 적힌 UMG는 위젯을 쓸 때 더하라는 예일 뿐입니다. 실제로 공개 헤더에 드러나는 타입의 모듈은 PublicDependencyModuleNames에, cpp에서만 쓰는 모듈은 PrivateDependencyModuleNames에 넣습니다. 또 Slate와 SlateCore는 런타임 UI에도 쓰이므로, 이름만 보고 모두 에디터 전용으로 분류하지 않습니다.


에디터 모듈과 런타임 모듈 나누기

UNREAL C++ · CH11-1

에디터 모듈이 런타임 모듈을 사용하도록 나눕니다

MyPlugin과 MyPluginEditor를 분리한 설계 예시입니다. 화살표는 Build.cs의 모듈 의존 방향이며 폴더 포함 관계나 Startup 실행 순서가 아닙니다.

MyPluginEditor에서 MyPlugin으로 향하는 모듈 의존Editor 모듈은 공용 Runtime 모듈을 사용합니다. Runtime은 에디터 없이 필요한 기능을 담습니다.MyPluginEditorType: Editor공용 타입·기능 사용MyPluginType: Runtime
에디터에서 런타임으로 향하는 모듈 의존Editor 모듈의 하단에서 Runtime 모듈 상단으로 향하는 한 의존이다. Runtime에서Editor로향하는의존이나두모듈의실행순서를뜻하지않는다.
MyPluginEditorType: Editor
공용 타입기능 사용공용 타입 · 기능 사용MyPluginEditor · Type: Editor → MyPlugin · Type: Runtime
MyPluginType: Runtime

에디터 도구가 런타임의 공개 타입·기능을 사용합니다. 실제 사용한 모듈은 public/private 노출 범위에 맞춰 선언합니다.

패키지 게임의 경계

Runtime → UnrealEd처럼 에디터 전용 모듈을 런타임에 끌어들이지 않습니다. 편집 도구와 게임 실행 기능을 각각의 모듈로 분리합니다.

Runtime 모듈은 에디터에서도 로드될 수 있습니다. Editor 모듈은 패키지 게임에서 로드할 대상이 아닙니다.

Slate·SlateCore는 런타임 UI에도 쓰입니다. UI 의존성이 있다는 사실만으로 모듈을 Editor 전용으로 분류하지 않습니다.

이름과 설정도 함께 확인합니다

.uplugin의 Modules 이름·Type, Source 폴더·Build.cs 클래스, IMPLEMENT_MODULE 식별자를 맞춥니다. 다른 플러그인의 사용은 모듈 의존성과 descriptor의 Plugins 선언을 함께 확인합니다.

이 예시는 의존 방향을 설명합니다. 프로젝트의 실제 모듈 등록·로드·패키징 성공은 해당 대상 빌드로 확인해야 합니다.

위 그림은 MyPlugin과 MyPluginEditor를 나눈 설계의 예입니다. 화살표는 Build.cs의 모듈 의존 방향으로, Type: Editor인 MyPluginEditor가 Type: Runtime인 MyPlugin의 공개 타입과 기능을 씁니다. 런타임 모듈은 에디터 없이 필요한 기능을 담고, 에디터 도구는 그 기능을 가져다 씁니다. 이 화살표는 폴더의 포함 관계나 StartupModule의 실행 순서가 아니며, 런타임에서 에디터로 향하는 의존도 아닙니다. 실제로 쓴 모듈은 공개 범위에 맞춰 public이나 private으로 선언합니다.

그림의 아래쪽은 패키지 게임과 관련된 주의를 정리합니다. 런타임 모듈이 UnrealEd 같은 에디터 전용 모듈에 의존하게 만들면 안 되며, 편집 도구와 게임 실행 기능은 각각의 모듈로 나눕니다. 런타임 모듈은 에디터에서도 로드될 수 있지만, 에디터 모듈은 패키지 게임에서 로드되지 않습니다. 또 Slate와 SlateCore는 런타임 UI에도 쓰이므로, UI 의존성이 있다는 이유만으로 모듈을 에디터 전용으로 분류하지 않습니다.

이름과 설정도 함께 맞춰야 합니다. 그림의 설명처럼 .uplugin의 Modules 이름과 Type, Source 폴더와 Build.cs 클래스, IMPLEMENT_MODULE의 식별자가 같은 모듈을 가리켜야 하고, 다른 플러그인을 쓸 때는 모듈 의존성과 .uplugin의 Plugins 선언을 함께 확인합니다. 이 그림은 의존 방향을 설명할 뿐이며, 실제 모듈의 등록, 로드, 패키징이 성공하는지는 대상 빌드로 확인해야 합니다.

앞의 MyPlugin을 게임에서도 쓰는 런타임 모듈이라고 정하고 이 기준을 대입해 봅시다. Source/MyPlugin 폴더, MyPlugin.Build.cs의 클래스 이름, .uplugin의 Modules.Name, IMPLEMENT_MODULE의 식별자가 모두 같은 모듈을 가리키는지 확인합니다. 공개 헤더가 다른 모듈의 타입을 드러낸다면 그 모듈을 public 의존성으로, cpp 안에서만 쓴다면 private 의존성으로 검토합니다. include를 추가하는 것과 빌드 의존성을 선언하는 것은 서로 다른 일입니다. 편집 도구가 필요하면 MyPluginEditor라는 에디터 모듈을 만들어 런타임의 공개 기능을 쓰도록 나누며, Epic의 Plugins 문서도 플러그인 하나에 여러 모듈을 둘 수 있다고 설명합니다. 플러그인과 모듈이 늘 같은 단위인 것은 아닙니다.

에디터 모듈을 만든다면 Build.cs에서 UnrealEd, Slate, SlateCore 같은 에디터 관련 모듈에 의존성을 더합니다. 에디터의 메뉴, 툴바, 컨텍스트 메뉴는 FExtensibilityManager, FMenuBuilder, FToolBarBuilder 같은 클래스로 넓힐 수 있으며, 언리얼 엔진 5에서는 UToolMenus 시스템으로 메뉴를 확장하는 방식도 널리 씁니다. 커스텀 에셋 타입은 IAssetTypeActions를 구현해 등록할 수 있고, 최신 버전에서는 UAssetDefinition을 쓰는 방식도 있으므로 대상 엔진 버전의 문서를 확인합니다. 에디터 확장은 에디터 확장과 커스텀 툴에서 자세히 다룹니다.


빌드하고 켜기

새 모듈을 더했거나 프로젝트 구조가 바뀌었다면 IDE의 프로젝트 파일을 갱신합니다. 프로젝트 폴더에서 .uproject 파일을 마우스 오른쪽 버튼으로 누르고 Generate Visual Studio project files(Mac이라면 Xcode 프로젝트 생성)를 고른 뒤, 솔루션 파일을 열어 프로젝트를 빌드합니다. cpp의 구현만 고쳤을 때마다 다시 생성해야 하는 것은 아니며, 쓰는 IDE의 통합 방식도 확인합니다.

빌드가 성공하면 에디터의 플러그인 창에서 새 플러그인을 찾아 켤 수 있고, 켠 뒤에는 에디터를 다시 시작해야 반영됩니다.


외부 라이브러리 통합하기

플러그인으로 SDL, OpenCV, FFmpeg 같은 외부 C++ 라이브러리를 프로젝트에 통합할 수도 있습니다. 먼저 ThirdParty 폴더 구조를 정해 두고, Build.cs의 링크 설정과 플랫폼별 패키징 확인을 되풀이하면 통합 실패를 빨리 줄일 수 있습니다.

Build.cs에서는 PublicAdditionalLibraries, PublicDelayLoadDLLs, PublicIncludePaths, RuntimeDependencies 같은 설정으로 라이브러리의 .lib, .dll, .h 파일 경로를 지정합니다. 라이브러리 파일은 보통 플러그인 안에 ThirdParty/[LibraryName]/ 폴더를 만들고 Win64, Linux, Android 같은 플랫폼별로 나눠 둡니다. 그리고 Build.cs에서 Target.Platform으로 분기해 플랫폼마다 다른 라이브러리 파일을 쓰게 합니다.

플러그인은 코드가 컴파일된다고 끝나지 않습니다. 런타임과 에디터 모듈의 분리, 플랫폼별 패키징, 예제 프로젝트, 문서, 버전 호환성을 함께 점검해야 실제로 다시 쓸 수 있는 배포물이 됩니다.


배포하기

만든 플러그인은 Fab 같은 배포 채널에 올려 수익을 낼 수 있습니다. 배포 채널의 제출 기준(코드 표준, 문서화, 테스트 등)을 지키고, 사용자가 쉽게 쓸 수 있도록 자세한 문서와 예제 프로젝트를 제공하며, 여러 엔진 버전과 플랫폼, 다양한 프로젝트 환경에서 꼼꼼히 시험해야 합니다.

배포하기 전에는 다음 항목을 맞춰 둡니다.

항목확인할 내용
공개 API공개 헤더, export 매크로, 공개 의존성
런타임과 에디터런타임 모듈이 UnrealEd 같은 에디터 모듈을 참조하지 않는지
외부 라이브러리대상마다의 ABI, 링크, 실행 파일 배치, RuntimeDependencies
모듈 수명StartupModule에서 등록한 자원을 ShutdownModule에서 정리하는지
사용하는 프로젝트새 프로젝트에서 켜기, 빌드, 패키징, 예제 실행이 되는지

이 글의 코드는 학습용 모듈 구성의 예이며, 실제로 엔진을 빌드한 결과를 보여 주지 않습니다. 지원하는 엔진과 플랫폼, 필요한 모듈을 분명히 적고, 배포 채널의 현재 제출 기준에 맞춰 문서와 예제 프로젝트를 준비합니다.


정리

  • 플러그인은 기능을 독립된 단위로 묶어 엔진 소스를 고치지 않고 붙이고 뗄 수 있게 하며, 하나에 여러 모듈을 담을 수 있습니다.
  • 런타임 모듈은 패키지 게임에서도 쓰이고 에디터에서도 로드될 수 있으며, 에디터 모듈은 패키지 게임에서 로드되지 않습니다.
  • 플러그인은 Edit > Plugins 창의 새 플러그인 버튼으로 만들며, .uplugin에 모듈을 등록하고, 모듈 이름은 폴더, Build.cs 클래스, IMPLEMENT_MODULE과 같아야 합니다.
  • Build.cs에서 공개 헤더가 쓰는 모듈은 public으로, 내부에서만 쓰는 모듈은 private으로 의존성을 선언하며, include를 더하는 것과 의존성 선언은 다릅니다.
  • 각 모듈은 IModuleInterface를 상속해 StartupModule과 ShutdownModule을 구현하고, 에디터 모듈이 런타임 모듈을 쓰도록 의존 방향을 정합니다.
  • 외부 라이브러리는 ThirdParty 폴더와 Build.cs의 경로, 플랫폼 분기, 런타임 파일 전달을 함께 맞추고, 배포 전에는 대상 프로젝트에서 빌드와 패키징을 확인합니다.

연습 문제

1. 런타임 모듈에 에디터 타입을 넣으면

런타임 모듈의 공개 헤더에 에디터 전용 타입을 드러내고 UnrealEd 의존성을 넣었습니다. 에디터에서 빌드가 잘 된다면 패키지 게임에서도 문제가 없다고 볼 수 있을까요?

해설 보기

볼 수 없습니다. 에디터에서 빌드가 된다는 사실은, 런타임 모듈에 에디터 전용 기능을 끌어들인 설계가 괜찮다는 뜻이 아닙니다. 에디터 모듈은 패키지 게임에 들어가지 않으므로, 런타임 모듈이 그것에 의존하면 패키지 빌드에서 문제가 생길 수 있습니다.

편집 코드는 에디터 모듈로 옮기고, 런타임 모듈의 공개 타입에서 에디터 타입을 드러내는 부분을 없앨 수 있는지 검토합니다. 이것은 설정을 검토하는 연습이며, 실제로 UBT로 빌드해 본 결과는 아닙니다.

2. 링크는 됐는데 실행이 안 된다

외부 라이브러리의 컴파일과 링크는 성공했는데, 패키지 게임을 실행하니 필요한 DLL을 찾지 못한다며 시작하지 않습니다. 무엇을 놓쳤을까요?

해설 보기

실행에 필요한 DLL을 대상 산출물에 함께 전달하지 않았습니다. 빌드에서 라이브러리를 연결하는 것과, 실행할 때 필요한 런타임 파일을 패키지에 넣고 불러오는 것은 서로 다른 일입니다. Build.cs의 RuntimeDependencies로 DLL을 패키지에 포함하도록 지정하고, 필요하면 PublicDelayLoadDLLs와 실행 중 로드 경로를 확인합니다. 대상 플랫폼이 그 라이브러리를 지원하는지도 함께 확인해야 합니다.

3. 모듈 이름이 다르면

.uplugin의 Modules에는 MyPlugin으로 등록했는데, cpp 파일의 마지막 줄을 IMPLEMENT_MODULE(FMyPluginModule, MyPluginRuntime)으로 썼습니다. 어떤 문제가 생길까요?

해설 보기

IMPLEMENT_MODULE의 식별자는 엔진이 모듈을 찾는 이름이므로, .uplugin과 폴더, Build.cs가 가리키는 MyPlugin과 다르면 엔진이 그 모듈을 제대로 등록하고 불러오지 못합니다. 빌드나 실행 중에 모듈을 찾을 수 없다는 오류가 날 수 있습니다. 네 곳의 이름을 모두 MyPlugin으로 맞춥니다.

다음 글 에디터 확장과 커스텀 툴에서는 에디터 모듈로 메뉴와 툴바, 커스텀 창 같은 에디터 도구를 만드는 방법을 살펴봅니다.