<Unreal> 모듈 & 플러그인 만들기

2026. 6. 23. 21:13Unreal

모듈과 플러그인

언리얼 엔진은 기능을 모듈 단위로 나눠서 관리한다.

우리가 만드는 게임 프로젝트도 사실 하나의 모듈이다. 이번엔 프로젝트 안에서 새 모듈을 직접 추가하고, 재사용 가능한 플러그인 구조까지 손으로 만들어보려고 한다.


 

1. 프로젝트 생성

언리얼 에디터에서 아래 순서대로 프로젝트를 생성했다.

Game -> Third Person -> C++ -> Scalable

프로젝트 이름 : HW10


2. Test 모듈 생성

source 폴더 안에 Test 폴더를 새로 만들어 모듈 파일을 여기 안에서 생성했다.

Source/
├── HW10/          ← 기존 주 모듈
└── Test/          ← 새로 만든 모듈
    ├── Test.Build.cs
    ├── Test.h
    └── Test.cpp

 

Test.Build.cs

모듈의 의존성을 정의하는 파일이다.

이 파일이 없으면 언리얼 빌드 시스템이 이 폴더를 모듈로 인식하지 못한다.

using UnrealBuildTool;

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

        PublicDependencyModuleNames.AddRange(new string[]
        {
            "Core",
            "CoreUObject",
            "Engine"
        });
    }
}

 

Test.h / Test.cpp

// Test.h
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"

class FTestModule : public IModuleInterface
{
public:
    virtual void StartupModule() override;
    virtual void ShutdownModule() override;
};
// Test.cpp
#include "Test.h"

void FTestModule::StartupModule()
{
    UE_LOG(LogTemp, Warning, TEXT("=== Test Module Started! ==="));
}

void FTestModule::ShutdownModule()
{
    UE_LOG(LogTemp, Warning, TEXT("=== Test Module Shutdown! ==="));
}

IMPLEMENT_MODULE(FTestModule, Test);

 

IMPLEMENT_MODULE 매크로가 없으면 모듈로 등록할 수 없다. 

처음에 cpp파일을 만들고 실행했을 때 오류가 발생했는데, 이 부분이 없어서 그런 것이었다.

 

언리얼이 모듈을 로드할 때 이 매크로가 진입점 역할을 한다.


모듈 등록

파일을 만들고 빌드 시스템과 프로젝트 파일에 알려야한다.

 

Target.cs 수정

source 폴더에 있는 

이 두 파일을 수정해준다.

// 변경 전
ExtraModuleNames.AddRange(new string[] { "HW10" });

// 변경 후
ExtraModuleNames.AddRange(new string[] { "HW10", "Test" });

 

.uproject 수정

HW10.uproject 파일의 내부를 수정해준다.

모듈 배열에 "Test" 모듈을 추가해준다.

LoadingPhase를 PreDefault로 설정한 이유는 주 모듈(HW10)보다 Test모듈이 먼저 로드되어야 주 모듈에서 Test모듈의 클래스를 사용할 수 있기 때문이다.


TestActor 생성

모듈의 동작을 확인하기 위해서 Test모듈 안에 Actor를 만들었다.

// TestActor.cpp
#include "TestActor.h"

ATestActor::ATestActor()
{
    PrimaryActorTick.bCanEverTick = false;
}

void ATestActor::BeginPlay()
{
    Super::BeginPlay();

    UE_LOG(LogTemp, Warning, TEXT("=== TestActor BeginPlay! Test Module Working! ==="));

    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green,
            TEXT("Test Module Working!"));
    }
}

이렇게 이 모듈에서 Actor의 BeginPlay가 실행되면 로그가 찍히도록 했다.


주 모듈에서 TestActor 스폰

HW10.Build.cs에 Test 의존성을 추가해준다.

PublicDependencyModuleNames.AddRange(new string[]
{
    "Core", "CoreUObject", "Engine", "InputCore",
    "EnhancedInput",
    "Test"  // 추가
});

 

HW10Character.cpp BeginPlay에서 스폰

#include "TestActor.h"

void AHW10Character::BeginPlay()
{
    Super::BeginPlay();

    FActorSpawnParameters SpawnParams;
    GetWorld()->SpawnActor<ATestActor>(
        ATestActor::StaticClass(),
        GetActorLocation(),
        FRotator::ZeroRotator,
        SpawnParams
    );
}

 

모듈 결과 확인

여기까지 하면 에디터를 처음 열었을 때, 모듈의 시작로그, 에디터의 실행을 했을 때, 액터의 BeginPlay 로그가 찍혀야 한다.

정상적으로 로그가 나오는 모습을 확인할 수 있다.


Temporary 플러그인 만들기

모듈과 플러그인의 차이에는 재사용성에 있다.

 

모듈은 프로젝트 안에 종속되지만, 플러그인은 독립적인 구조를 가져서 다른 프로젝트에서도 쓸 수 있다.

이번엔 플러그인 폴더를 만들어 본다.


플러그인 폴더 구조 생성

프로젝트 폴더에 Plugins폴더를 만들고 내부를 다음과 같이 구성한다.

Plugins/
└── Temporary/
    ├── Content/
    ├── Source/
    │   └── Temporary/
    │       ├── Temporary.Build.cs
    │       ├── Temporary.h
    │       └── Temporary.cpp
    └── Temporary.uplugin

Temporary.uplugin 작성

플러그인의 메타데이터를 JSON형식으로 기입하는 파일이다.

이 파일이 있어야 언리얼이 폴더를 플러그인으로 인식한다.

CanContatinContent 를 true로 설정해야 콘텐츠 브라우저에서 플러그인 폴더가 보인다.


플러그인 모듈 파일 작성

모듈 구조는 Test모듈과 동일하다.

 

Temporary.Build.cs

PublcIncludePaths.Add(ModuleDirectory); 는 이후에 나올 include 경로의 문제때문에 이를 해결하면서 추가했다.

 

Temporary.h

 

Temporary.cpp


.uproject에 플러그인 등록

기존 uproject 파일에서 Plugins 항목에 Temporary를 추가했다.


플러그인 활용 - CharacterData클래스

Temporary 플러그인 안에 UCharacterData 클래스를 만들어 캐릭터의 데이터를 저장하게 하고, 주 모듈의 캐릭터에서 해당 데이터를 출력해보려고 한다.

 

UCharacterData.h

UCharacterData.cpp

 

UObject를 상속받는 이유는 가비지컬렉터가 메모리를 자동 관리하고, UPROPERTY 매크로를 사용할 수 있기 때문이다.

단순 struct나 일반 class로는 이런 기능을 사용할 수 없다.


 

캐릭터에서 Data 사용

// HW10Character.h
#include "CharacterData.h"

// 클래스 안에 변수 선언
UPROPERTY()
TObjectPtr<UCharacterData> CharacterData;
// HW10Character.cpp BeginPlay
void AHW10Character::BeginPlay()
{
    Super::BeginPlay();

    CharacterData = NewObject<UCharacterData>(this);

    if (IsValid(CharacterData))
    {
        FString Message = FString::Printf(
            TEXT("=== CharacterData: Name: %s | HP: %.0f | Speed: %.0f | Level: %d ==="),
            *CharacterData->CharacterName,
            CharacterData->MaxHealth,
            CharacterData->MoveSpeed,
            CharacterData->Level
        );

        GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Yellow, Message);
        UE_LOG(LogTemp, Warning, TEXT("%s"), *Message);
    }
}

이렇게 하고 실행하면 정확히 로그가 출력되어야 한다.

 

트러블슈팅 - include 경로를 찾지 못하는 문제

HW10Character.cpp 부분에서 에러가 발생했다.

 

Error C1083: Cannot open include file: 'CharacterData.h': No such file or directory

 

이 오류의 원인은 HW10 모듈이 Temporary 플러그인의 헤더 파일 경로를 알지 못해서 나오는 오류이다.

 

다음 두 가지를 수정해서 해결했다.

 

Temporary.Build.cs에 include 경로 추가

using System.IO;  // 추가

// Build.cs 생성자 안에 추가
PublicIncludePaths.Add(ModuleDirectory);

아까 Build.cs 부분에서 잠깐 나왔던 부분이다.

 

ModuleDirectory는 Build.cs가 있는 폴더의 절대 경로를 자동으로 반환하는 내장변수이다.

이렇게 하면 이 모듈에 의존하는 다른 모듈들도 해당 경로를 include 경로로 사용할 수 있다.

 

HW10.Build.cs에 Temporary 의존성 추가

PublicDependencyModuleNames.AddRange(new string[]
{
    "Core", "CoreUObject", "Engine", "InputCore",
    "EnhancedInput", "Test",
    "Temporary"  // 추가
});

최종 결과

모든 설정이 완료된 후 에디터를 켜고, PIE를 실행했을 때의 Output Log이다. 문제없이 잘 출력되는 모습을 확인할 수 있다.


핵심 포인트 정리

  • 언리얼 모듈은 Build.cs, 헤더, cpp 세 파일이 최소 구성이다.
  • 모듈을 추가하면 Target.cs와 .uproject 두 곳에 등록해야 빌드 대상이 된다.
  • LoadingPhase: PreDefault는 주 모듈보다 먼저 로드되어야 하는 모듈에 사용한다.
  • StartupModule()은 에디터 시작 시 실행된다.
  • 플러그인은 .uplugin 파일이 있어야 언리얼이 플러그인으로 인식한다.
  • API매크로는 파일이 속한 모듈의 것을 사용해야 한다.
  • 다른 모듈의 헤더를 include 하려면 PublicIncludePaths와 PublicDependencyModuleNames 두 곳에 등록해야 한다.
  • UObject를 상속받아야 NewObject<> , UPROPERTY(), 가비지컬렉션 자동 관리를 사용할 수 있다.