PART 9 - 강의 3/4

Game Feature Plugin

모듈형 게임플레이 콘텐츠의 런타임 활성화/비활성화

01

Game Feature Plugin이란?

모듈형 게임플레이 콘텐츠를 위한 UE5의 강력한 시스템

핵심 개념

Game Feature Plugin은 런타임에 활성화/비활성화할 수 있는 자체 포함형 게임플레이 콘텐츠 패키지입니다. Epic의 Lyra 프로젝트에서 핵심적으로 사용되는 아키텍처입니다.

모듈형 콘텐츠

캐릭터, 무기, 게임 모드 등을 독립적 단위로 패키징

런타임 제어

게임 실행 중 동적으로 활성화/비활성화

Action 시스템

활성화 시 수행할 작업을 데이터로 정의

DLC 지원

다운로드 콘텐츠를 게임에 원활히 통합

일반 플러그인과의 차이

특성 일반 Plugin Game Feature Plugin
로딩 시점 엔진 시작 시 고정 런타임에 동적 로드/언로드
활성화 제어 프로젝트 설정에서만 코드/BP에서 실시간 제어
콘텐츠 주입 수동 등록 필요 Action으로 자동 주입
사용 사례 유틸리티, 시스템 게임 모드, DLC, 기능 팩
Lyra에서의 활용 예시
  • ShooterCore: 슈터 게임의 기본 메카닉
  • ShooterMaps: 슈터 게임용 맵 콘텐츠
  • TopDownArena: 탑다운 아레나 게임 모드
  • 각 Feature는 독립적으로 활성화/비활성화 가능
02

Game Feature Plugin 설정

.uplugin 설정과 필수 플러그인 활성화

프로젝트 설정

먼저 프로젝트에서 GameFeatures 플러그인을 활성화해야 합니다.

MyProject.uproject { "FileVersion": 3, "EngineAssociation": "5.6", "Plugins": [ { "Name": "GameFeatures", "Enabled": true }, { "Name": "ModularGameplay", "Enabled": true } ] }

Game Feature Plugin 디렉토리

Directory Structure MyProject/ └── Plugins/ └── GameFeatures/ // Game Feature 전용 폴더 (중요!) ├── MyCombatFeature/ │ ├── MyCombatFeature.uplugin │ ├── Source/ │ │ └── MyCombatFeature/ │ └── Content/ │ ├── Abilities/ │ ├── Characters/ │ └── GameFeatureData.uasset │ └── MyQuestFeature/ ├── MyQuestFeature.uplugin ├── Source/ └── Content/
폴더 위치 중요!

Game Feature Plugin은 반드시 Plugins/GameFeatures/ 폴더 안에 위치해야 합니다. 다른 위치에서는 Game Feature로 인식되지 않습니다.

Game Feature .uplugin 설정

MyCombatFeature.uplugin { "FileVersion": 3, "Version": 1, "VersionName": "1.0.0", "FriendlyName": "Combat Feature", "Description": "전투 시스템 기능 팩", "Category": "Game Features", // Game Feature 전용 설정 "ExplicitlyLoaded": true, "BuiltInInitialFeatureState": "Active", "Modules": [ { "Name": "MyCombatFeature", "Type": "Runtime", "LoadingPhase": "Default" } ], "Plugins": [ { "Name": "GameplayAbilities", "Enabled": true }, { "Name": "ModularGameplay", "Enabled": true } ] }
핵심 설정 옵션
  • ExplicitlyLoaded: true - Game Feature로 인식되기 위해 필수
  • BuiltInInitialFeatureState: "Active" | "Registered" | "Installed" | "Loaded"
  • Active: 시작 시 자동 활성화
  • Registered: 등록만 되고 수동 활성화 필요
03

Game Feature Data와 Actions

활성화 시 수행할 작업을 데이터로 정의

Game Feature Data 에셋

UGameFeatureData는 Feature가 활성화될 때 수행할 Action들을 정의합니다. 에디터에서 Content Browser에서 우클릭 - Miscellaneous - Game Feature Data로 생성합니다.

Game Feature Data 구조 // UGameFeatureData에 포함된 내용 GameFeatureData ├── Actions // 활성화 시 수행할 Action 목록 │ ├── AddComponents // 액터에 컴포넌트 추가 │ ├── AddDataRegistry // 데이터 레지스트리 추가 │ ├── AddGameplayCuePath // Gameplay Cue 경로 추가 │ └── CustomAction // 커스텀 Action │ └── Primary Asset Id // 에셋 관리자 ID

기본 제공 Actions

Action 설명 사용 예
AddComponents 특정 액터에 컴포넌트 자동 추가 캐릭터에 능력 컴포넌트 추가
AddDataRegistry Data Registry 소스 추가 아이템 데이터베이스 등록
AddGameplayCuePath Gameplay Cue 경로 등록 이펙트 경로 추가
AddInputBinding 입력 바인딩 추가 새로운 조작키 등록
AddAbilities GAS 어빌리티 부여 캐릭터에 스킬 추가

AddComponents Action 예시

Game Feature Data (Blueprint Config) // AddComponents Action 설정 // 에디터에서 GameFeatureData 에셋을 열어 Actions에 추가 Actions: [0] GameFeatureAction_AddComponents ActorClass: "AMyPlayerCharacter" Components to Add: [0] Component Class: "UCombatComponent" bClientComponent: true bServerComponent: true [1] Component Class: "UComboSystemComponent" bClientComponent: true bServerComponent: true
04

커스텀 Game Feature Action

프로젝트에 맞는 커스텀 Action 구현

커스텀 Action 기본 구조

MyGameFeatureAction.h #pragma once #include "CoreMinimal.h" #include "GameFeatureAction.h" #include "MyGameFeatureAction.generated.h" /** * 커스텀 Game Feature Action * Feature 활성화 시 특정 작업을 수행 */ UCLASS(meta = (DisplayName = "Register Quest System")) class MYCOMBATFEATURE_API UMyGameFeatureAction_RegisterQuestSystem : public UGameFeatureAction { GENERATED_BODY() public: /** Feature 활성화 시 호출 */ virtual void OnGameFeatureActivating( FGameFeatureActivatingContext& Context) override; /** Feature 비활성화 시 호출 */ virtual void OnGameFeatureDeactivating( FGameFeatureDeactivatingContext& Context) override; /** 에디터에서 설정할 데이터 */ UPROPERTY(EditAnywhere, Category = "Quest") TSoftObjectPtr<UDataTable> QuestDataTable; UPROPERTY(EditAnywhere, Category = "Quest") TArray<FName> InitialQuests; };

커스텀 Action 구현

MyGameFeatureAction.cpp #include "MyGameFeatureAction.h" #include "Engine/AssetManager.h" #include "MyQuestSubsystem.h" void UMyGameFeatureAction_RegisterQuestSystem::OnGameFeatureActivating( FGameFeatureActivatingContext& Context) { Super::OnGameFeatureActivating(Context); // 데이터 테이블 로드 if (QuestDataTable.IsValid()) { UDataTable* LoadedTable = QuestDataTable.LoadSynchronous(); // World 반복하며 Subsystem에 등록 for (const FWorldContext& WorldContext : GEngine->GetWorldContexts()) { if (UWorld* World = WorldContext.World()) { if (UGameInstance* GI = World->GetGameInstance()) { if (UMyQuestSubsystem* QuestSS = GI->GetSubsystem<UMyQuestSubsystem>()) { // 퀘스트 데이터 등록 QuestSS->RegisterQuestData(LoadedTable); // 초기 퀘스트 추가 for (const FName& QuestId : InitialQuests) { QuestSS->AddQuest(QuestId); } UE_LOG(LogTemp, Log, TEXT("Quest system registered")); } } } } } } void UMyGameFeatureAction_RegisterQuestSystem::OnGameFeatureDeactivating( FGameFeatureDeactivatingContext& Context) { // 정리 작업 for (const FWorldContext& WorldContext : GEngine->GetWorldContexts()) { if (UWorld* World = WorldContext.World()) { if (UGameInstance* GI = World->GetGameInstance()) { if (UMyQuestSubsystem* QuestSS = GI->GetSubsystem<UMyQuestSubsystem>()) { // 등록 해제 QuestSS->UnregisterQuestData(); } } } } Super::OnGameFeatureDeactivating(Context); }

World Ready Action (월드 준비 후 실행)

WorldReady Action #pragma once #include "GameFeatureAction_WorldActionBase.h" #include "MyWorldReadyAction.generated.h" /** * 월드가 준비된 후 실행되는 Action * 월드 컨텍스트가 필요한 작업에 적합 */ UCLASS() class UMyWorldReadyAction : public UGameFeatureAction_WorldActionBase { GENERATED_BODY() protected: /** 월드에 Feature 추가 시 호출 */ virtual void AddToWorld(const FWorldContext& WorldContext, const FGameFeatureStateChangeContext& ChangeContext) override { UWorld* World = WorldContext.World(); if (!World) { return; } // 월드에 특정 Actor 스폰 FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; World->SpawnActor<AMyFeatureManager>( AMyFeatureManager::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams ); } };
05

런타임 활성화/비활성화

코드와 Blueprint에서 Game Feature 제어하기

C++에서 Game Feature 제어

Runtime Control #include "GameFeaturesSubsystem.h" void UMyGameManager::LoadCombatFeature() { // Game Features Subsystem 가져오기 UGameFeaturesSubsystem& GFS = UGameFeaturesSubsystem::Get(); // Feature 이름 (플러그인 이름) FString FeatureName = TEXT("MyCombatFeature"); // URL 형식으로 변환 FString PluginURL; if (GFS.GetPluginURLByName(FeatureName, PluginURL)) { // 비동기 로드 및 활성화 GFS.LoadAndActivateGameFeaturePlugin( PluginURL, FGameFeaturePluginLoadComplete::CreateUObject( this, &UMyGameManager::OnFeatureLoaded ) ); } } void UMyGameManager::OnFeatureLoaded(const UE::GameFeatures::FResult& Result) { if (Result.HasError()) { UE_LOG(LogTemp, Error, TEXT("Failed to load feature: %s"), *Result.GetError()); } else { UE_LOG(LogTemp, Log, TEXT("Feature loaded successfully")); } } void UMyGameManager::UnloadCombatFeature() { UGameFeaturesSubsystem& GFS = UGameFeaturesSubsystem::Get(); FString PluginURL; if (GFS.GetPluginURLByName(TEXT("MyCombatFeature"), PluginURL)) { // 비활성화 및 언로드 GFS.DeactivateGameFeaturePlugin(PluginURL); GFS.UnloadGameFeaturePlugin(PluginURL); } }

Feature 상태 확인

State Checking bool UMyGameManager::IsFeatureActive(const FString& FeatureName) { UGameFeaturesSubsystem& GFS = UGameFeaturesSubsystem::Get(); FString PluginURL; if (!GFS.GetPluginURLByName(FeatureName, PluginURL)) { return false; } // 상태 확인 EGameFeaturePluginState State = GFS.GetPluginState(PluginURL); switch (State) { case EGameFeaturePluginState::Active: return true; case EGameFeaturePluginState::Registered: case EGameFeaturePluginState::Loaded: case EGameFeaturePluginState::Installed: return false; // 로드됐지만 활성화 안 됨 default: return false; } } // Feature 상태 변화 감지 void UMyGameManager::ListenToFeatureStateChanges() { UGameFeaturesSubsystem& GFS = UGameFeaturesSubsystem::Get(); // 상태 변화 델리게이트 바인딩 GFS.OnGameFeaturePluginStateChanged().AddUObject( this, &UMyGameManager::OnFeatureStateChanged ); } void UMyGameManager::OnFeatureStateChanged( const FString& PluginURL, EGameFeaturePluginState NewState) { UE_LOG(LogTemp, Log, TEXT("Feature %s changed to state %d"), *PluginURL, (int32)NewState); }

Blueprint에서 사용

Blueprint Callable Function // BlueprintCallable 함수로 노출 UCLASS() class UGameFeatureBPLibrary : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UFUNCTION(BlueprintCallable, Category = "Game Features") static void ActivateGameFeature(const FString& FeatureName); UFUNCTION(BlueprintCallable, Category = "Game Features") static void DeactivateGameFeature(const FString& FeatureName); UFUNCTION(BlueprintPure, Category = "Game Features") static bool IsGameFeatureActive(const FString& FeatureName); };
사용 사례
  • 게임 모드 전환: PvP 모드 / PvE 모드 동적 전환
  • DLC 관리: 구매한 DLC만 활성화
  • 시즌 콘텐츠: 특정 기간에만 활성화되는 이벤트
  • 성능 옵션: 저사양 기기에서 일부 기능 비활성화
SUMMARY

핵심 요약

  • Game Feature Plugin은 런타임에 동적으로 활성화/비활성화 가능한 모듈형 게임플레이 콘텐츠
  • 반드시 Plugins/GameFeatures/ 폴더에 위치하고 ExplicitlyLoaded: true 설정 필요
  • Game Feature DataActions를 통해 활성화 시 수행할 작업을 데이터로 정의
  • 기본 제공 Actions: AddComponents, AddAbilities, AddInputBinding 등
  • UGameFeaturesSubsystem을 통해 C++/BP에서 Feature 로드/활성화/비활성화 제어
다음 강의 예고

다음 강의에서는 에디터 확장을 통해 커스텀 에디터 도구, Property Customization, Asset 에디터를 개발하는 방법을 학습합니다.

PRACTICE

도전 과제

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

실습 1: Game Feature Plugin 생성

Game Feature Plugin을 생성하여 RPG의 확장 콘텐츠(DLC 던전)를 모듈화하세요. UGameFeatureAction을 사용하여 플러그인 활성화 시 자동으로 컴포넌트를 추가하는 로직을 구현하세요.

실습 2: Game Feature 런타임 로딩

UGameFeaturesSubsystem::LoadAndActivateGameFeaturePlugin()으로 런타임에 Game Feature를 로드하세요. RPG의 시즌 이벤트 콘텐츠를 Game Feature로 패키징하여 동적으로 활성화/비활성화하세요.

심화 과제: 모듈러 RPG 콘텐츠 아키텍처

기본 게임, PvP 모드, 레이드 던전, 시즌 이벤트를 각각 별도의 Game Feature Plugin으로 분리하세요. 플러그인 간 의존성을 최소화하고, 독립적으로 빌드/배포할 수 있는 구조를 설계하세요.