ue-game-features — скилл для Claude Code: что делает и как установить
Настраивает модульный геймплей и плагины Game Features для Unreal Engine 5.8. Помогает писать действия UGameFeatureAction и внедрять компоненты в акторы.
npx skills add quodsoler/unreal-engine-skills --skill ue-game-features -a claude-codeЧто делает ue-game-features
Скилл содержит правила архитектуры модульного геймплея под Unreal Engine 5.8. Он описывает структуру плагинов в директории Plugins/GameFeatures/ и конфигурацию дескриптора .uplugin, где обязателен флаг ExplicitlyLoaded: true. По расположению в этой папке движок распознаёт плагин как фичу через настройки UGameFeaturesSubsystemSettings.
Внутри разобрана работа с ассетом UGameFeatureData и внедрение логики через UGameFrameworkComponentManager. Скилл объясняет стейт-машину жизненного цикла фичи: от состояний Installed и Registered до Loaded и Active. Переход между состояниями запускает привязанные объекты UGameFeatureAction.
Учтите, что подсистемы Game Features и Modular Gameplay в версии 5.8 имеют статус Beta. Скилл ориентирован на заголовочные файлы 5.8, поэтому устаревшие API прошлых версий движка здесь исключены.
Какие задачи решает
- настроить дескриптор
.upluginдля модульной фичи - создать ассет
UGameFeatureDataсо списком действий - внедрить компоненты в акторы через
UGameFrameworkComponentManager - отследить переходы состояний в
UGameFeaturesSubsystem
Примеры запросов агенту
- Создай .uplugin для плагина Game Feature с модулем runtime под UE 5.8
- Как настроить UGameFeatureAction для добавления компонента в Pawn?
- Помоги зарегистрировать и активировать фичу через UGameFeaturesSubsystem
Как установить ue-game-features
1Через Skills CLI (Vercel)
npx skills add quodsoler/unreal-engine-skills --skill ue-game-features -a claude-code2Вручную: положить файлы
~/.claude/skills/ue-game-features/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/ue-game-features/
1Через Skills CLI (Vercel)
npx skills add quodsoler/unreal-engine-skills --skill ue-game-features -a codex2Вручную: положить файлы
.agents/skills/ue-game-features/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add quodsoler/unreal-engine-skills --skill ue-game-features -a cursor2Вручную: положить файлы
.cursor/skills/ue-game-features/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install quodsoler/unreal-engine-skills ue-game-features2Вручную: положить файлы
.github/skills/ue-game-features/SKILL.md1Через Skills CLI (Vercel)
npx skills add quodsoler/unreal-engine-skills --skill ue-game-features -a gemini-cli2Командой платформы
gemini skills install https://github.com/quodsoler/unreal-engine-skills --path skills/ue-game-featuresВручную: положить файлы
.devin/skills/ue-game-features/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add quodsoler/unreal-engine-skills --skill ue-game-features -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Вопросы о ue-game-features
Как установить ue-game-features в Claude Code?
Выполните в терминале команду npx skills add quodsoler/unreal-engine-skills --skill ue-game-features -a claude-code.
Что делает скилл ue-game-features?
Он даёт ассистенту правила работы с модульным геймплеем и плагинами Game Features в Unreal Engine 5.8. Скилл подсказывает структуру дескрипторов .uplugin, создание ассетов UGameFeatureData и внедрение компонентов.
Почему плагин фичи должен лежать именно в папке Plugins/GameFeatures/?
Движок определяет фичу именно по пути: метод UGameFeaturesSubsystemSettings::IsValidGameFeaturePlugin сверяет расположение дескриптора. Отдельного поля Type в схеме .uplugin для этого нет.
Исходный текст
SKILL.md · MIT
---
name: ue-game-features
description: "Use when building, activating or debugging Game Feature plugins and modular gameplay. Also use when the user mentions 'GameFeatureAction', 'UGameFeatureData', 'GameFeaturesSubsystem', 'LoadAndActivateGameFeaturePlugin', 'BuiltInInitialFeatureState', 'ExplicitlyLoaded', '.uplugin', 'GameFrameworkComponentManager', 'AddReceiver', 'AddComponentRequest', 'AddExtensionHandler', 'NAME_GameActorReady', 'init state', 'UPawnComponent', 'ModularGameplay', 'experience system' or 'Lyra-style modular architecture'. For plugin modules and Build.cs wiring, see ue-module-build-system; for component fundamentals, see ue-actor-component-architecture."
metadata:
version: "2.0.0"
engine: "5.8"
---
# UE Game Features and Modular Gameplay
Target engine: **UE 5.8**. APIs below are verified against the 5.8 headers; older forms are listed under "Deprecated — do not use".
Game Features (Beta in 5.8) packages gameplay as self-contained plugins under `Plugins/GameFeatures/`. `UGameFeaturesSubsystem` (a `UEngineSubsystem`) drives each plugin through a state machine; a `UGameFeatureData` primary data asset inside the plugin holds an instanced list of `UGameFeatureAction` objects that run at registration, load, activation and deactivation. Modular Gameplay (Beta in 5.8) supplies `UGameFrameworkComponentManager` (a `UGameInstanceSubsystem`) that injects components into opted-in actors, plus the `UGameFrameworkComponent` bases and the init-state system that orders initialization across independently loaded features. Build.cs modules: `GameFeatures` and `ModularGameplay`. The `GameFeatures` module already depends publicly on `ModularGameplay` and `DataRegistry`, so code that only consumes actions needs `GameFeatures` alone.
## Context
Read `.agents/ue-project-context.md` if it exists (module names, conventions, enabled plugins, GAS/networking setup). Do not stop if it is missing.
Identify the area from the request and the codebase. Ask only when two plausible readings would produce different code.
| Request is about… | Go to |
|---|---|
| Creating a feature plugin, `.uplugin` keys, folder layout, `UGameFeatureData` | [Plugin Layout and Descriptor](#plugin-layout-and-descriptor) |
| Activating or unloading a feature at runtime, plugin URLs, plugin states | [Plugin States and the Subsystem](#plugin-states-and-the-subsystem) |
| Writing an action that runs when a feature turns on or off | [Writing a UGameFeatureAction](#writing-a-ugamefeatureaction) |
| Picking a ready-made action instead of writing one | [Engine-Provided Actions](#engine-provided-actions) |
| Adding components to actors from a feature, extension events | [Component Injection](#component-injection) |
| Ordering initialization between components that load separately | [Init State System](#init-state-system) |
| Choosing a base class for a Pawn/Controller/PlayerState/GameState component | [Modular Component Bases](#modular-component-bases) |
| Filtering which plugins load, client/server data, state observers | [Project Policies and Observers](#project-policies-and-observers) |
| Lyra-style experience composition (sample code, not engine API) | [references/experience-system.md](references/experience-system.md) |
| Full templates: custom action, receiver setup, policies subclass | [references/game-feature-patterns.md](references/game-feature-patterns.md) |
## Plugin Layout and Descriptor
A Game Feature plugin is an ordinary plugin that lives under `<Project>/Plugins/GameFeatures/` or under `<AdditionalPluginDirectory>/GameFeatures/`. **That location is what makes it a Game Feature plugin** — `UGameFeaturesSubsystemSettings::IsValidGameFeaturePlugin` matches the descriptor path against those folders. There is no plugin-level `"Type"` key in the `.uplugin` schema.
```
Plugins/GameFeatures/
├── MyFeature/
│ ├── MyFeature.uplugin
│ ├── Content/MyFeature.uasset (UGameFeatureData, named after the plugin)
│ └── Source/MyFeatureRuntime/
└── MyFeatureRules/
├── MyFeatureRules.uplugin
└── Content/MyFeatureRules.uasset
```
```json
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "My Feature",
"Description": "Adds my gameplay feature",
"Category": "Game Features",
"CreatedBy": "",
"CanContainContent": true,
"ExplicitlyLoaded": true,
"BuiltInInitialFeatureState": "Active",
"Modules": [
{ "Name": "MyFeatureRuntime", "Type": "Runtime", "LoadingPhase": "Default" }
],
"Plugins": [
{ "Name": "GameFeatures", "Enabled": true },
{ "Name": "ModularGameplay", "Enabled": true }
]
}
```
| Key | Meaning |
|---|---|
| `ExplicitlyLoaded` | The plugin manager does not mount the plugin at startup; the feature's state machine mounts and unmounts it. Always `true` for a feature plugin. |
| `CanContainContent` | Plugin ships a `Content/` directory (needed for the `UGameFeatureData` asset). |
| `BuiltInInitialFeatureState` | How far a built-in plugin advances at startup: `"Installed"`, `"Registered"`, `"Loaded"` or `"Active"`. Unknown values log an error and fall back to `Active`. |
| `EnabledByDefault` | Plugin is enabled without a `.uproject` entry. |
| `Modules[].Type` | `ModuleHostType` name: `Runtime`, `RuntimeNoCommandlet`, `Editor`, `Developer`, `Program`, … |
| `Modules[].LoadingPhase` | `ModuleLoadingPhase` name: `Default`, `PreDefault`, `PostConfigInit`, `PostEngineInit`, … |
A content-only feature plugin omits `Modules` entirely. Adding a module means adding a `Build.cs`; see `ue-module-build-system`.
### UGameFeatureData
`UGameFeatureData : UPrimaryDataAsset` is the asset the subsystem loads for the plugin. Its two authored properties are protected; read them through accessors:
```cpp
const TArray<UGameFeatureAction*>& FeatureActions = FeatureData->GetActions();
const TArray<FPrimaryAssetTypeInfo>& ScanTypes = FeatureData->GetPrimaryAssetTypesToScan();
```
`Actions` is an `Instanced` array of `UGameFeatureAction` subclasses created inline in the asset. `PrimaryAssetTypesToScan` tells the Asset Manager which primary asset types inside the plugin to scan when the feature registers; see `ue-data-assets-tables`.
Subclass `UGameFeatureData` only to change `GetPrimaryAssetTypesToScan` or, in the editor, `GetDisallowedActions`.
## Plugin States and the Subsystem
Destination states are fully ordered; transition and error states sit between them. `EGameFeaturePluginState` is a plain enum generated from the `GAME_FEATURE_PLUGIN_STATE_LIST` macro.
| Destination state | Meaning |
|---|---|
| `Terminal` | Final state before the state machine is removed. |
| `UnknownStatus` | Only the URL is known. |
| `StatusKnown` | Availability confirmed (on disk or in a bundle). |
| `Uninstalled` | Local data for the plugin has been removed. |
| `Installed` | Files are in local storage, not registered. |
| `Registered` | Assets discovered and registered; actions received `OnGameFeatureRegistering`. |
| `Loaded` | Plugin code and content are in memory, not yet affecting the game. |
| `Active` | Actions applied; the feature affects the game. |
Transition states that show up in logs and in `GetPluginState` results include `CheckingStatus`, `Downloading`, `Mounting`, `Registering`, `Loading`, `ActivatingDependencies`, `Activating`, `Deactivating`, `Unloading`, `Unregistering`, `Unmounting`, `Releasing`, `Uninstalling`, plus the matching `Error*` states.
Two other enums: `EGameFeatureTargetState` (`Installed`, `Registered`, `Loaded`, `Active`) is what you pass to `ChangeGameFeatureTargetState`; `EBuiltInAutoState` (`Invalid`, `Installed`, `Registered`, `Loaded`, `Active`) is what `BuiltInInitialFeatureState` parses into.
### Driving a plugin from code
```cpp
#include "GameFeaturesSubsystem.h"
void UMyFeatureManager::EnableMyFeature()
{
UGameFeaturesSubsystem& GFS = UGameFeaturesSubsystem::Get();
FString PluginURL;
if (!GFS.GetPluginURLByName(TEXT("MyFeature"), PluginURL))
{
UE_LOG(LogMyGame, Error, TEXT("MyFeature is not a discovered game feature plugin"));
return;
}
GFS.LoadAndActivateGameFeaturePlugin(PluginURL,
FGameFeaturePluginLoadComplete::CreateUObject(this, &UMyFeatureManager::HandleFeatureLoaded));
}
void UMyFeatureManager::HandleFeatureLoaded(const UE::GameFeatures::FResult& Result)
{
if (Result.HasError())
{
UE_LOG(LogMyGame, Error, TEXT("MyFeature failed: %s"), *Result.GetError());
}
}
```
`GetPluginURLByName(FStringView PluginName, FString& OutPluginURL)` is the safe way to build a URL for a built-in plugin. The static builders `UGameFeaturesSubsystem::GetPluginURL_FileProtocol(PluginDescriptorPath)` and `GetPluginURL_InstallBundleProtocol(PluginName, BundleName)` produce `file:` and `installbundle:` URLs (`EGameFeaturePluginProtocol::File`, `::InstallBundle`).
| Call | Effect |
|---|---|
| `LoadGameFeaturePlugin(URL, CompleteDelegate)` | Advance to `Loaded`. |
| `LoadAndActivateGameFeaturePlugin(URL, CompleteDelegate)` | Advance to `Active`. |
| `RegisterGameFeaturePlugin(URL, CompleteDelegate)` | Advance to `Registered`. |
| `ChangeGameFeatureTargetState(URL, EGameFeatureTargetState::Registered, CompleteDelegate)` | Move to any destination state. |
| `DeactivateGameFeaturePlugin(URL)` | Back down to `Loaded`. |
| `UnloadGameFeaturePlugin(URL, /*bKeepRegistered=*/ false)` | Back down to `Registered` or `Installed`. |
| `TerminateGameFeaturePlugin(URL)` | Tear the state machine down. |
| `CancelGameFeatureStateChange(URL)` | Abort an in-flight transition. |
Every completion delegate above is an alias of `FGameFeaturePluginChangeStateComplete`, i.e. `DECLARE_DELEGATE_OneParam(..., const UE::GameFeatures::FResult&)`. Overloads taking `TConstArrayView<FString> PluginURLs` report through `FMultipleGameFeaturePluginsLoaded`, a `TDelegate<void(const TMap<FString, UE::GameFeatures::FResult>&)>`.
Queries: `GetPluginState(URL)`, `IsGameFeaturePluginActive(URL, bCheckForActivating)`, `IsGameFeaturePluginActiveByName(PluginName, bCheckForActivating)`, `IsGameFeaturePluginRegistered(URL, bCheckForRegistering)`, `IsGameFeaturePluginLoaded(URL)`, `IsGameFeaturePluginInErrorState(URL)`, `GetActivePluginNames()`.
Console commands for testing (`GameFeaturesSubsystem.cpp:583-661`, take a plugin name or URL): `ListGameFeaturePlugins [-activeonly] [-csv]`, `LoadGameFeaturePlugin`, `DeactivateGameFeaturePlugin`, `UnloadGameFeaturePlugin`, `ReleaseGameFeaturePlugin`, `CancelGameFeaturePlugin`, `TerminateGameFeaturePlugin`. The `LoadGameFeaturePlugin` console command calls `LoadAndActivateGameFeaturePlugin` and is `ECVF_Cheat`, unlike the C++ function of the same name, which only reaches `Loaded`.
## Writing a UGameFeatureAction
`UGameFeatureAction` is `UCLASS(MinimalAPI, DefaultToInstanced, EditInlineNew, Abstract)` deriving from `UObject`. `DefaultToInstanced` and `EditInlineNew` are what let instances be created inline in the `Actions` array. The overridable virtuals, verbatim:
```cpp
virtual void OnGameFeatureRegistering() {}
virtual void OnGameFeatureUnregistering() {}
virtual void OnGameFeatureLoading() {}
virtual void OnGameFeatureUnloading() {}
virtual void OnGameFeatureActivating(FGameFeatureActivatingContext& Context);
virtual void OnGameFeatureActivating() {}
virtual void OnGameFeatureActivated() {}
virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context) {}
```
Override the context version of `OnGameFeatureActivating`; the base implementation of that one calls the no-arg version, so overriding both runs your code twice. `IsDataValid` is a `UObject` override, not a `UGameFeatureAction` member, and is editor-only.
```cpp
// MyGameFeatureAction_GrantTag.h
#pragma once
#include "GameFeatureAction.h"
#include "GameplayTagContainer.h"
#include "MyGameFeatureAction_GrantTag.generated.h"
struct FGameFeatureActivatingContext;
struct FGameFeatureDeactivatingContext;
struct FWorldContext;
UCLASS(meta = (DisplayName = "My Grant Tag"))
class MYGAME_API UMyGameFeatureAction_GrantTag : public UGameFeatureAction
{
GENERATED_BODY()
public:
virtual void OnGameFeatureActivating(FGameFeatureActivatingContext& Context) override;
virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context) override;
#if WITH_EDITOR
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
#endif
UPROPERTY(EditAnywhere, Category = "Tags")
FGameplayTag TagToGrant;
private:
void ApplyToWorld(const FWorldContext& WorldContext);
};
```
An action instance is shared by every world the feature applies to, so never keep per-world state in a bare member. Filter worlds with the context and key state by `FObjectKey`:
```cpp
void UMyGameFeatureAction_GrantTag::OnGameFeatureActivating(FGameFeatureActivatingContext& Context)
{
for (const FWorldContext& WorldContext : GEngine->GetWorldContexts())
{
if (Context.ShouldApplyToWorldContext(WorldContext))
{
ApplyToWorld(WorldContext);
}
}
}
```
`FGameFeatureActivatingContext` and `FGameFeatureDeactivatingContext` both derive from `FGameFeatureStateChangeContext`, which supplies `ShouldApplyToWorldContext(const FWorldContext&)`, `ShouldApplyUsingOtherContext(const FGameFeatureStateChangeContext&)` and `SetRequiredWorldContextHandle(FName)`.
Deactivation that needs async work must hold the state machine open. `FGameFeatureDeactivatingContext::PauseDeactivationUntilComplete(FString InPauserTag)` returns an `FSimpleDelegate` you must execute on the game thread, on every path:
```cpp
void UMyGameFeatureAction_GrantTag::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context)
{
FSimpleDelegate Resume = Context.PauseDeactivationUntilComplete(TEXT("MyGrantTag_Cleanup"));
StartAsyncCleanup(Resume); // must call Resume.ExecuteIfBound() when finished
}
```
Full templates, including world tracking and the RAII handle patterns, are in [references/game-feature-patterns.md](references/game-feature-patterns.md).
## Engine-Provided Actions
| Action | Purpose |
|---|---|
| `UGameFeatureAction_AddComponents` | Adds actor↔component spawn requests to `UGameFrameworkComponentManager`. |
| `UGameFeatureAction_AddCheats` | Adds cheat manager extensions for each player. |
| `UGameFeatureAction_AddActorFactory` | Registers an actor factory when the plugin registers. |
| `UGameFeatureAction_AddChunkOverride` | Cooks the plugin's assets into a specified chunk id. |
| `UGameFeatureAction_AddWPContent` | Adds World Partition content as a content bundle. |
| `UGameFeatureAction_AddWorldPartitionContent` | Adds World Partition content for the plugin. |
| `UGameFeatureAction_DataRegistry` | Loads and initializes a list of Data Registries. |
| `UGameFeatureAction_DataRegistrySource` | Adds source assets to existing Data Registries. |
| `UGameFeatureAction_AudioActionBase` | `Abstract` base for actions that affect the audio engine. |
| `UIrisFilterGameFeatureAction` | Creates an Iris network filter. |
`UGameFeatureAction_AddComponents` is the one most features need. It holds `TArray<FGameFeatureComponentEntry> ComponentList`, and each `FGameFeatureComponentEntry` carries `TSoftClassPtr<AActor> ActorClass` (`meta=(AllowAbstract="True")`), `TSoftClassPtr<UActorComponent> ComponentClass`, the bitfields `uint8 bClientComponent:1` and `uint8 bServerComponent:1`, and `uint8 AdditionFlags` (`meta=(Bitmask, BitmaskEnum="/Script/ModularGameplay.EGameFrameworkAddComponentFlags")`).
Set both `bClientComponent` and `bServerComponent` for components needed everywhere, server-only for authoritative gameplay logic, client-only for cosmetics. The action keeps a `TSharedPtr<FComponentRequestHandle>` per game instance and drops them on deactivation, which removes the injected components.
## Component Injection
`UGameFrameworkComponentManager` is a **`UGameInstanceSubsystem`**. Get it from an actor:
```cpp
#include "Components/GameFrameworkComponentManager.h"
UGameFrameworkComponentManager* CompMgr = UGameFrameworkComponentManager::GetForActor(MyActor);
```
`GetForActor(const AActor* Actor, bool bOnlyGameWorlds = true)` returns null outside game worlds. `GetGameInstance()->GetSubsystem<UGameFrameworkComponentManager>()` also works where a game instance is in hand.
### Receivers
An actor receives injected components only after it registers itself:
```cpp
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
}
void AMyCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
Super::EndPlay(EndPlayReason);
}
```
The static helpers resolve the manager for the actor and forward to `AddReceiver(AActor* Receiver, bool bAddOnlyInGameWorlds = true)` and `RemoveReceiver(AActor* Receiver)`, which you can also call directly on the manager.
### Requests and extension handlers
```cpp
TSharedPtr<FComponentRequestHandle> RequestHandle = CompMgr->AddComponentRequest(
TSoftClassPtr<AActor>(AMyCharacter::StaticClass()),
UMyHealthComponent::StaticClass(),
EGameFrameworkAddComponentFlags::AddUnique);
```
`FComponentRequestHandle` is RAII: keep it alive for as long as the injection should last, and destroy it to remove the request and the components it created. It also exposes `IsValid()`, which returns false once the owning manager is gone.
| `EGameFrameworkAddComponentFlags` | Value | Behavior |
|---|---|---|
| `None` | 0 | Always create the component on the receiver. |
| `AddUnique` | `0b00000001` | Skip if the receiver already has a component of that class. |
| `AddIfNotChild` | `0b00000010` | Skip if the requested class is a child of an existing component's class. |
| `UseAutoGeneratedName` | `0b00000100` | Generate a new name instead of reusing the class name. |
Extension handlers observe receivers instead of adding components. The delegate is declared inside the manager as `DECLARE_DELEGATE_TwoParams(FExtensionHandlerDelegate, AActor*, FName)`:
```cpp
ExtensionHandle = CompMgr->AddExtensionHandler(
TSoftClassPtr<AActor>(AMyCharacter::StaticClass()),
UGameFrameworkComponentManager::FExtensionHandlerDelegate::CreateUObject(
this, &UMyFeatureManager::HandleActorExtension));
```
```cpp
void UMyFeatureManager::HandleActorExtension(AActor* Actor, FName EventName)
{
if (EventName == UGameFrameworkComponentManager::NAME_GameActorReady)
{
// Actor has finished its default initialization
}
}
```
The five `static FName` event constants are `NAME_ReceiverAdded`, `NAME_ReceiverRemoved`, `NAME_ExtensionAdded`, `NAME_ExtensionRemoved` and `NAME_GameActorReady`. Send your own with `CompMgr->SendExtensionEvent(Receiver, EventName, bOnlyInGameWorlds)` or the static `UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(Receiver, EventName, bOnlyInGameWorlds)`.
## Init State System
Init states solve ordered initialization when components arrive from different plugins. States are `FGameplayTag` values registered in order on the manager; for how to declare the tags themselves see `ue-gameplay-tags-messaging`.
```cpp
CompMgr->RegisterInitState(TAG_InitState_Spawned, false, FGameplayTag());
CompMgr->RegisterInitState(TAG_InitState_DataAvailable, false, TAG_InitState_Spawned);
CompMgr->RegisterInitState(TAG_InitState_DataInitialized, false, TAG_InitState_DataAvailable);
CompMgr->RegisterInitState(TAG_InitState_GameplayReady, false, TAG_InitState_DataInitialized);
```
`RegisterInitState(FGameplayTag NewState, bool bAddBefore, FGameplayTag ExistingState)` inserts relative to an existing state, so the engine ships no fixed state list — the project owns it. Register the states once, for example from a `UGameInstanceSubsystem` or from the project's policies class.
| Manager call | Use |
|---|---|
| `ChangeFeatureInitState(Actor, FeatureName, Implementer, FeatureState)` | Advance one feature; returns `bool`. |
| `GetInitStateForFeature(Actor, FeatureName)` | Current state tag. |
| `HasFeatureReachedInitState(Actor, FeatureName, FeatureState)` | Test one feature. |
| `HaveAllFeaturesReachedInitState(Actor, RequiredState, ExcludingFeature)` | Test the whole actor. |
| `IsInitStateAfterOrEqual(FeatureState, RelativeState)` | Compare two tags in registration order. |
| `GetImplementerForFeature(Actor, FeatureName, RequiredState)` | Find the object implementing a feature. |
| `RegisterAndCallForActorInitState(Actor, FeatureName, RequiredState, Delegate, bCallImmediately)` | Wait on one actor; returns `FDelegateHandle`. |
| `RegisterAndCallForClassInitState(ActorClass, FeatureName, RequiredState, Delegate, bCallImmediately)` | Wait on every actor of a class; returns `FDelegateHandle`. |
| `UnregisterActorInitStateDelegate(Actor, Handle)` / `UnregisterClassInitStateDelegate(ActorClass, Handle)` | Remove a native binding (takes `FDelegateHandle&`). |
The native delegate is `DECLARE_DELEGATE_OneParam(FActorInitStateChangedDelegate, const FActorInitStateChangedParams&)`. `FActorInitStateChangedParams` is a `USTRUCT` with `OwningActor`, `FeatureName`, `Implementer` and `FeatureState`.
### IGameFrameworkInitStateInterface
Implement `IGameFrameworkInitStateInterface` (UINTERFACE `UGameFrameworkInitStateInterface`, `NotBlueprintable`) on a component to get the standard progression. The virtuals worth overriding, verbatim from the header:
```cpp
virtual FName GetFeatureName() const { return NAME_None; }
virtual bool CanChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) const { return true; }
virtual void HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) {}
virtual void CheckDefaultInitialization() {}
virtual void OnActorInitStateChanged(const FActorInitStateChangedParams& Params) {}
```
The rest you call rather than override:
| Call | Effect |
|---|---|
| `RegisterInitStateFeature()` | Registers this implementer with the manager (call from `OnRegister`). |
| `UnregisterInitStateFeature()` | Removes the registration and the bound delegate (call from `EndPlay`). |
| `BindOnActorInitStateChanged(FeatureName, RequiredState, bCallIfReached)` | Binds `OnActorInitStateChanged`, storing the handle in `ActorInitStateChangedHandle`. |
| `TryToChangeInitState(DesiredState)` | Runs `CanChangeInitState`, then `HandleChangeInitState`, then notifies the manager. |
| `ContinueInitStateChain(const TArray<FGameplayTag>& InitStateChain)` | Walks the chain from the current state, running `CanChangeInitState` and `HandleChangeInitState` for each step and notifying the manager; returns the furthest state reached. |
| `CheckDefaultInitializationForImplementers()` | Calls `CheckDefaultInitialization` on every other feature of the same actor. |
| `HasReachedInitState(DesiredState)` / `GetInitState()` | Query this feature's state through the manager. |
A `CheckDefaultInitialization` override normally calls `CheckDefaultInitializationForImplementers()` and then `ContinueInitStateChain` with the project's ordered tag list; a full component implementation is in [references/game-feature-patterns.md](references/game-feature-patterns.md).
## Modular Component Bases
| Base class | Owner it expects | Typed accessors |
|---|---|---|
| `UGameFrameworkComponent` | any `AActor` | `GetGameInstance<T>()`, `GetGameInstanceChecked<T>()`, `HasAuthority()` |
| `UPawnComponent` | `APawn` | `GetPawn<T>()`, `GetPawnChecked<T>()`, `GetPlayerState<T>()`, `GetController<T>()` |
| `UControllerComponent` | `AController` | `GetController<T>()`, `GetControllerChecked<T>()`, `GetPawn<T>()`, `GetViewTarget<T>()`, `GetPawnOrViewTarget<T>()`, `GetPlayerState<T>()`, `GetPlayer<T>()` |
| `UPlayerStateComponent` | `APlayerState` | `GetPlayerState<T>()`, `GetPlayerStateChecked<T>()` |
| `UGameStateComponent` | `AGameStateBase` | `GetGameState<T>()`, `GetGameStateChecked<T>()`, `GetGameMode<T>()` |
All five live in `ModularGameplay` under `#include "Components/<Name>.h"`. `UGameFrameworkComponent` derives from `UActorComponent` and is `Blueprintable, BlueprintType`. Prefer these over raw `UActorComponent` for injected components: the accessors are `static_assert`-checked against the owner type and the init-state interface is designed around them.
## Project Policies and Observers
`UGameFeaturesProjectPolicies` decides which plugins may load and what data each build type loads. Select the subclass in project settings, which writes:
```ini
[/Script/GameFeatures.GameFeaturesSubsystemSettings]
GameFeaturesManagerClassName=/Script/MyGame.MyGameFeaturesProjectPolicies
```
Useful virtuals: `InitGameFeatureManager()`, `ShutdownGameFeatureManager()`, `IsPluginAllowed(const FString& PluginURL, FString* OutReason) const`, `GetGameFeatureLoadingMode(bool& bLoadClientData, bool& bLoadServerData) const`, `ShouldReadPluginDetails(const FString& PluginDescriptorFilename) const`, `GetPreloadBundleStateForPlugin(const FString& PluginName) const`, `IsLoadingStartupPlugins() const`. `UDefaultGameFeaturesProjectPolicies` is the fallback implementation. A subclass template is in [references/game-feature-patterns.md](references/game-feature-patterns.md).
`UGameFeaturesSubsystemSettings::LoadStateClient` and `::LoadStateServer` are the `FName` bundle states used for client/server asset filtering; the settings class also carries `EnabledPlugins`, `DisabledPlugins` and `AdditionalPluginMetadataKeys`.
For cross-cutting reactions that are not tied to one feature's data asset, implement `IGameFeatureStateChangeObserver` and register it:
```cpp
UGameFeaturesSubsystem::Get().AddObserver(MyObserver,
UGameFeaturesSubsystem::EObserverPluginStateUpdateMode::CurrentAndFuture);
```
`EObserverPluginStateUpdateMode` is `FutureOnly` or `CurrentAndFuture`; the latter replays current plugin states at add time and is expensive when a project has many plugins. Remove with `RemoveObserver(MyObserver)`. Observer hooks include `OnGameFeatureRegistering(const UGameFeatureData*, const FString& PluginName, const FString& PluginURL)`, `OnGameFeatureLoading(const UGameFeatureData*, const FString& PluginURL)`, `OnGameFeatureActivating(const UGameFeatureData*, const FString& PluginURL)`, `OnGameFeatureDeactivating(const UGameFeatureData*, FGameFeatureDeactivatingContext& Context, const FString& PluginURL)` and the download and mounting hooks. Prefer a `UGameFeatureAction` on the feature's data asset whenever data is involved.
## Deprecated — do not use
| Do not emit | Use in 5.8 | Source |
|---|---|---|
| `"Type": "GameFeature"` in a `.uplugin` | Place the plugin under `Plugins/GameFeatures/` and set `"ExplicitlyLoaded": true` | No plugin-level `Type` key in `PluginDescriptor.cs` |
| `"BuiltInAutoRegister"` / `"BuiltInAutoLoad"` / `"BuiltInAutoActivate"` | `"BuiltInInitialFeatureState"` | Legacy fallback in `GameFeaturesSubsystem.cpp:5079-5090` |
| `AddObserver(Observer)` | `AddObserver(Observer, EObserverPluginStateUpdateMode::CurrentAndFuture)` | `UE_DEPRECATED(5.7)` in `GameFeaturesSubsystem.h:488` |
| `IsPluginAllowed(PluginURL)` | `IsPluginAllowed(PluginURL, OutReason)` | `UE_DEPRECATED(5.6)` in `GameFeaturesProjectPolicies.h:102` |
| `GetStreamingAssetInstallBundles(PluginURL)` | `GetStreamingAssetInstallModes(PluginURL, InstallBundleNames)` | `UE_DEPRECATED(5.6)` in `GameFeaturesProjectPolicies.h:154` |
| `FInstallBundlePluginProtocolOptions::ReleaseInstallBundleFlags` | Nothing — release flags are applied internally | `UE_DEPRECATED(5.6)` in `GameFeaturesSubsystem.h:409` |
| `FString`-based plugin details (`FPluginString`) | Set `UE_GAME_FEATURE_PLUGIN_DETAILS_USE_UTF8_STRING=1` and use the UTF8 forms | `UE_DEPRECATED(5.8)` in `GameFeaturesSubsystem.h:258` |
| Overriding `OnGameFeatureActivating()` with no argument | Override `OnGameFeatureActivating(FGameFeatureActivatingContext& Context)` | "Older-style activation function" in `GameFeatureAction.h:38-39` |
| `GetGameFeaturePluginState(URL)` | `GetPluginState(URL)` | Not an engine function; the real call is at `GameFeaturesSubsystem.h:731` |
| `UGameFeatureData::Actions` touched directly | `GetActions()` | `Actions` is protected in `GameFeatureData.h:120`; getter `:102` |
| `ULyraExperienceDefinition`, `ULyraExperienceManagerComponent`, `UGameFeatureAction_AddInputContextMapping` | Lyra sample code, not engine API — write your own equivalents, see [references/experience-system.md](references/experience-system.md) | Not in any 5.8 engine header |
## Common Mistakes
**Inventing a plugin `Type`:** a `.uplugin` has no plugin-level `Type` key, so `"Type": "GameFeature"` is silently ignored and the plugin never becomes a feature; put it under `Plugins/GameFeatures/` and set `"ExplicitlyLoaded": true`.
**Never registering the receiver:** components are injected only into registered actors, and nothing is logged when you forget.
```cpp
// WRONG — no components are ever added
void AMyCharacter::BeginPlay() { Super::BeginPlay(); }
// RIGHT
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
}
```
**Dropping `FComponentRequestHandle`:** the handle is RAII, so discarding it removes the request immediately.
```cpp
// WRONG — the temporary destructs at the end of the statement
CompMgr->AddComponentRequest(ActorClass, UMyHealthComponent::StaticClass(), AdditionFlags);
// RIGHT — store it for the lifetime of the injection
RequestHandle = CompMgr->AddComponentRequest(ActorClass, UMyHealthComponent::StaticClass(), AdditionFlags);
```
**Fetching the component manager as an engine subsystem:** it is a `UGameInstanceSubsystem`, so `GEngine->GetEngineSubsystem<UGameFrameworkComponentManager>()` returns null; use `UGameFrameworkComponentManager::GetForActor(MyActor)`.
**Not resuming a paused deactivation:** if `PauseDeactivationUntilComplete` is...
© quodsoler · MIT · quodsoler/unreal-engine-skills