claude-code-arcane 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +54 -6
  3. package/agents/game/ue-blueprint-specialist.md +125 -0
  4. package/agents/game/ue-gas-specialist.md +133 -0
  5. package/agents/game/ue-umg-specialist.md +131 -0
  6. package/dist/cli.js +5 -0
  7. package/docs/SKILLS-CATALOG.md +42 -3
  8. package/docs/USER-GUIDE.md +2 -2
  9. package/hooks/detect-unreal.sh +90 -0
  10. package/package.json +1 -1
  11. package/profiles/unreal-dev.yaml +97 -0
  12. package/rules/gamedev/unreal-code.md +125 -0
  13. package/skills/install-mcp/SKILL.md +135 -46
  14. package/skills/install-mcp/references/unreal-operations.md +55 -0
  15. package/skills/ue-actor-component-architecture/SKILL.md +509 -0
  16. package/skills/ue-actor-component-architecture/references/actor-lifecycle.md +301 -0
  17. package/skills/ue-actor-component-architecture/references/component-types.md +592 -0
  18. package/skills/ue-ai-navigation/SKILL.md +526 -0
  19. package/skills/ue-ai-navigation/references/behavior-tree-patterns.md +392 -0
  20. package/skills/ue-ai-navigation/references/behavior-tree-workflow.md +107 -0
  21. package/skills/ue-ai-navigation/references/custom-bttask.md +104 -0
  22. package/skills/ue-ai-navigation/references/eqs-reference.md +423 -0
  23. package/skills/ue-animation-system/SKILL.md +521 -0
  24. package/skills/ue-animation-system/references/anim-notify-reference.md +486 -0
  25. package/skills/ue-animation-system/references/locomotion-setup.md +550 -0
  26. package/skills/ue-async-threading/SKILL.md +433 -0
  27. package/skills/ue-async-threading/references/thread-safety-guide.md +213 -0
  28. package/skills/ue-async-threading/references/threading-patterns.md +345 -0
  29. package/skills/ue-audio-system/SKILL.md +527 -0
  30. package/skills/ue-audio-system/references/audio-setup-patterns.md +700 -0
  31. package/skills/ue-blueprints/SKILL.md +134 -0
  32. package/skills/ue-blueprints/references/communication.md +82 -0
  33. package/skills/ue-character-movement/SKILL.md +475 -0
  34. package/skills/ue-character-movement/references/cmc-extension-patterns.md +335 -0
  35. package/skills/ue-character-movement/references/movement-pipeline.md +189 -0
  36. package/skills/ue-cpp-foundations/SKILL.md +533 -0
  37. package/skills/ue-cpp-foundations/references/components-and-gc.md +107 -0
  38. package/skills/ue-cpp-foundations/references/container-patterns.md +544 -0
  39. package/skills/ue-cpp-foundations/references/delegate-patterns.md +497 -0
  40. package/skills/ue-cpp-foundations/references/gameplay-classes-cpp.md +136 -0
  41. package/skills/ue-cpp-foundations/references/property-specifiers.md +306 -0
  42. package/skills/ue-data-assets-tables/SKILL.md +527 -0
  43. package/skills/ue-data-assets-tables/references/asset-loading-patterns.md +306 -0
  44. package/skills/ue-data-assets-tables/references/data-driven-design.md +378 -0
  45. package/skills/ue-editor-tools/SKILL.md +523 -0
  46. package/skills/ue-editor-tools/references/detail-customization-patterns.md +395 -0
  47. package/skills/ue-editor-tools/references/editor-module-setup.md +480 -0
  48. package/skills/ue-game-features/SKILL.md +477 -0
  49. package/skills/ue-game-features/references/experience-system.md +222 -0
  50. package/skills/ue-game-features/references/game-feature-patterns.md +308 -0
  51. package/skills/ue-gameplay-abilities/SKILL.md +518 -0
  52. package/skills/ue-gameplay-abilities/references/ability-task-reference.md +391 -0
  53. package/skills/ue-gameplay-abilities/references/gameplay-effect-reference.md +397 -0
  54. package/skills/ue-gameplay-abilities/references/gas-setup-patterns.md +369 -0
  55. package/skills/ue-gameplay-framework/SKILL.md +466 -0
  56. package/skills/ue-gameplay-framework/references/framework-class-map.md +243 -0
  57. package/skills/ue-input-system/SKILL.md +394 -0
  58. package/skills/ue-input-system/references/cpp-setup.md +131 -0
  59. package/skills/ue-input-system/references/enhanced-input-workflow.md +117 -0
  60. package/skills/ue-input-system/references/input-action-reference.md +731 -0
  61. package/skills/ue-mass-entity/SKILL.md +476 -0
  62. package/skills/ue-mass-entity/references/mass-entity-patterns.md +319 -0
  63. package/skills/ue-mass-entity/references/mass-fragment-reference.md +119 -0
  64. package/skills/ue-materials-rendering/SKILL.md +526 -0
  65. package/skills/ue-materials-rendering/references/material-parameter-reference.md +216 -0
  66. package/skills/ue-materials-rendering/references/post-process-settings.md +383 -0
  67. package/skills/ue-module-build-system/SKILL.md +487 -0
  68. package/skills/ue-module-build-system/references/build-cs-reference.md +418 -0
  69. package/skills/ue-module-build-system/references/common-build-errors.md +381 -0
  70. package/skills/ue-naming-conventions/SKILL.md +142 -0
  71. package/skills/ue-naming-conventions/references/asset-name-modifiers.md +233 -0
  72. package/skills/ue-naming-conventions/references/content-structure.md +83 -0
  73. package/skills/ue-networking-replication/SKILL.md +452 -0
  74. package/skills/ue-networking-replication/references/replication-patterns.md +526 -0
  75. package/skills/ue-networking-replication/references/rpc-decision-guide.md +393 -0
  76. package/skills/ue-niagara-effects/SKILL.md +531 -0
  77. package/skills/ue-niagara-effects/references/niagara-data-interfaces.md +491 -0
  78. package/skills/ue-niagara-effects/references/niagara-parameter-types.md +153 -0
  79. package/skills/ue-niagara-effects/references/niagara-workflow.md +115 -0
  80. package/skills/ue-packaging/SKILL.md +116 -0
  81. package/skills/ue-physics-collision/SKILL.md +423 -0
  82. package/skills/ue-physics-collision/references/collision-channel-setup.md +218 -0
  83. package/skills/ue-physics-collision/references/trace-patterns.md +499 -0
  84. package/skills/ue-procedural-generation/SKILL.md +505 -0
  85. package/skills/ue-procedural-generation/references/pcg-node-reference.md +381 -0
  86. package/skills/ue-procedural-generation/references/procedural-mesh-patterns.md +940 -0
  87. package/skills/ue-project-context/SKILL.md +305 -0
  88. package/skills/ue-sequencer-cinematics/SKILL.md +506 -0
  89. package/skills/ue-sequencer-cinematics/references/sequencer-patterns.md +629 -0
  90. package/skills/ue-serialization-savegames/SKILL.md +524 -0
  91. package/skills/ue-serialization-savegames/references/save-system-architecture.md +705 -0
  92. package/skills/ue-state-trees/SKILL.md +489 -0
  93. package/skills/ue-state-trees/references/state-tree-mass-integration.md +184 -0
  94. package/skills/ue-state-trees/references/state-tree-patterns.md +321 -0
  95. package/skills/ue-testing-debugging/SKILL.md +520 -0
  96. package/skills/ue-testing-debugging/references/automation-test-patterns.md +387 -0
  97. package/skills/ue-testing-debugging/references/profiling-commands.md +357 -0
  98. package/skills/ue-ui-umg-slate/SKILL.md +431 -0
  99. package/skills/ue-ui-umg-slate/references/common-ui-setup.md +484 -0
  100. package/skills/ue-ui-umg-slate/references/widget-types.md +487 -0
  101. package/skills/ue-world-level-streaming/SKILL.md +518 -0
  102. package/skills/ue-world-level-streaming/references/streaming-patterns.md +414 -0
  103. package/templates/gamedev/unreal-project-context.md +107 -0
@@ -0,0 +1,477 @@
1
+ ---
2
+ name: ue-game-features
3
+ description: "Game Feature plugins, modular gameplay, GameFeatureAction, GameFrameworkComponentManager"
4
+ category: "gamedev"
5
+ argument-hint: "[plugin o experience]"
6
+ user-invocable: true
7
+ allowed-tools: Read, Glob, Grep, Bash, Write, Edit
8
+ ---
9
+
10
+ # UE Game Features and Modular Gameplay
11
+
12
+ You are an expert in Unreal Engine's Game Features plugin system and modular gameplay architecture.
13
+
14
+ Ask before writing: this skill edits Game Feature plugin definitions and actions. Propose the change and get approval before using Write or Edit.
15
+
16
+ ## Context Check
17
+
18
+ Read `docs/unreal/project-context.md` before proceeding. Determine:
19
+ - Whether the `GameFeatures` and `ModularGameplay` plugins are enabled
20
+ - Which actors register as component receivers (`AddReceiver`)
21
+ - Whether the project uses an init state system or experience-based loading
22
+ - Existing `UGameFeatureAction` subclasses or modular component base classes
23
+
24
+ ## Information Gathering
25
+
26
+ Ask the developer:
27
+ 1. Are you creating a new Game Feature plugin or extending an existing one?
28
+ 2. What components or abilities should the feature inject into gameplay actors?
29
+ 3. Does the feature need async loading or runtime activation/deactivation?
30
+ 4. Is there an experience/game mode composition system (Lyra-style)?
31
+ 5. Do components need ordered initialization across features?
32
+
33
+ ---
34
+
35
+ ## Game Feature Plugin Structure
36
+
37
+ A Game Feature plugin is a standard UE plugin with `Type` set to `"GameFeature"` in its `.uplugin` descriptor. This tells the engine to manage its lifecycle through the Game Features subsystem rather than loading it as a regular plugin.
38
+
39
+ ### .uplugin Descriptor
40
+
41
+ ```cpp
42
+ {
43
+ "Type": "GameFeature",
44
+ "BuiltInInitialFeatureState": "Active", // or "Registered", "Installed"
45
+ "Plugins": [
46
+ { "Name": "GameFeatures", "Enabled": true },
47
+ { "Name": "ModularGameplay", "Enabled": true }
48
+ ]
49
+ }
50
+ ```
51
+
52
+ `BuiltInInitialFeatureState` controls how far the plugin advances on startup. Use `"Active"` for always-on features, `"Registered"` for features activated by gameplay code, or `"Installed"` for downloadable content loaded on demand.
53
+
54
+ ### UGameFeatureData
55
+
56
+ Each Game Feature plugin contains a `UGameFeatureData` primary data asset (extends `UPrimaryDataAsset`) that defines what the feature does:
57
+
58
+ ```cpp
59
+ // From GameFeatureData.h
60
+ UPROPERTY(EditDefaultsOnly, Instanced, Category = "Game Feature | Actions")
61
+ TArray<TObjectPtr<UGameFeatureAction>> Actions;
62
+
63
+ UPROPERTY(EditAnywhere, Category = "Game Feature | Asset Manager")
64
+ TArray<FPrimaryAssetTypeInfo> PrimaryAssetTypesToScan;
65
+ ```
66
+
67
+ `Actions` is the core — an instanced array of `UGameFeatureAction` subclasses that execute when the feature activates.
68
+
69
+ ### Directory Convention
70
+
71
+ ```
72
+ Plugins/GameFeatures/
73
+ ├── ShooterCore/
74
+ │ ├── ShooterCore.uplugin (Type: GameFeature)
75
+ │ ├── Content/
76
+ │ │ └── ShooterCore.uasset (UGameFeatureData)
77
+ │ └── Source/ShooterCoreRuntime/
78
+ └── DeathmatchRules/
79
+ ├── DeathmatchRules.uplugin
80
+ └── Content/DeathmatchRules.uasset
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Plugin State Machine
86
+
87
+ Game Feature plugins transition through a well-defined state machine. Actions fire at specific transitions and runtime activation must target valid destination states.
88
+
89
+ ### EGameFeaturePluginState Lifecycle
90
+
91
+ ```
92
+ Uninitialized → Terminal → UnknownStatus → StatusKnown
93
+ → Installed → Registered → Loaded → Active
94
+ ```
95
+
96
+ Each major state has transition states between them (e.g., `Registering`, `Loading`, `Activating`). You target a destination state and the subsystem walks the chain.
97
+
98
+ ### Destination States
99
+
100
+ | State | Description |
101
+ |-------|-------------|
102
+ | `Terminal` | Plugin removed from tracking entirely |
103
+ | `StatusKnown` | Availability confirmed (exists on disk or bundle) |
104
+ | `Installed` | Files on local storage, not yet registered |
105
+ | `Registered` | Assets registered with Asset Manager, actions notified |
106
+ | `Loaded` | Assets loaded into memory |
107
+ | `Active` | Actions fully activated, components injected |
108
+
109
+ URL protocols: `file:` for built-in disk plugins, `installbundle:` for downloadable features. Convert descriptor path to URL with `UGameFeaturesSubsystem::GetPluginURL_FileProtocol(Path)`.
110
+
111
+ ---
112
+
113
+ ## UGameFeatureAction
114
+
115
+ `UGameFeatureAction` (`UCLASS(MinimalAPI, DefaultToInstanced, EditInlineNew, Abstract)`) is the base class for all actions. `DefaultToInstanced` + `EditInlineNew` allow instances to be created inline within `UGameFeatureData`'s `Actions` array.
116
+
117
+ ### Lifecycle Methods
118
+
119
+ ```cpp
120
+ // Registration phase
121
+ virtual void OnGameFeatureRegistering();
122
+ virtual void OnGameFeatureUnregistering();
123
+
124
+ // Loading phase
125
+ virtual void OnGameFeatureLoading();
126
+ virtual void OnGameFeatureUnloading();
127
+
128
+ // Activation — primary override point
129
+ virtual void OnGameFeatureActivating(FGameFeatureActivatingContext& Context);
130
+ virtual void OnGameFeatureActivating(); // legacy no-arg fallback
131
+
132
+ // Post-activation confirmation
133
+ virtual void OnGameFeatureActivated();
134
+
135
+ // Deactivation — supports async via context
136
+ virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context);
137
+ ```
138
+
139
+ `OnGameFeatureActivating(Context)` is the primary override. The base calls the legacy no-arg version for backward compatibility.
140
+
141
+ ### Async Deactivation
142
+
143
+ When deactivation requires async work, pause it via the context:
144
+
145
+ ```cpp
146
+ void UMyAction::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context)
147
+ {
148
+ FSimpleDelegate ResumeDelegate = Context.PauseDeactivationUntilComplete(
149
+ TEXT("MyAction_AsyncCleanup"));
150
+ // Start async work — MUST invoke ResumeDelegate when done or deactivation hangs
151
+ AsyncTask(ENamedThreads::GameThread, [ResumeDelegate]()
152
+ {
153
+ // ... cleanup ...
154
+ ResumeDelegate.ExecuteIfBound();
155
+ });
156
+ }
157
+ ```
158
+
159
+ See `references/game-feature-patterns.md` for complete custom action subclass templates.
160
+
161
+ ---
162
+
163
+ ## Built-in Actions
164
+
165
+ ### UGameFeatureAction_AddComponents
166
+
167
+ `UCLASS(MinimalAPI, meta=(DisplayName="Add Components"), final)`. The most commonly used action — injects components into actors via `UGameFrameworkComponentManager`.
168
+
169
+ Configuration uses `FGameFeatureComponentEntry`:
170
+
171
+ ```cpp
172
+ UPROPERTY(EditAnywhere) TSoftClassPtr<AActor> ActorClass;
173
+ UPROPERTY(EditAnywhere) TSoftClassPtr<UActorComponent> ComponentClass;
174
+ UPROPERTY(EditAnywhere) uint8 bClientComponent : 1;
175
+ UPROPERTY(EditAnywhere) uint8 bServerComponent : 1;
176
+ ```
177
+
178
+ Internally stores `TSharedPtr<FComponentRequestHandle>` — RAII removes components when the handle drops (feature deactivates). Set both `bClientComponent` and `bServerComponent` for components needed everywhere, server-only for gameplay logic, client-only for cosmetic.
179
+
180
+ ### Other Built-in Actions
181
+
182
+ | Action | Purpose |
183
+ |--------|---------|
184
+ | `UGameFeatureAction_AddCheats` | Register cheat manager extensions |
185
+ | `UGameFeatureAction_DataRegistry` | Register data registry sources |
186
+
187
+ ---
188
+
189
+ ## UGameFeaturesSubsystem
190
+
191
+ `UGameFeaturesSubsystem` (`UEngineSubsystem`) manages all Game Feature plugin lifecycles:
192
+
193
+ ```cpp
194
+ UGameFeaturesSubsystem& GFS = UGameFeaturesSubsystem::Get();
195
+ ```
196
+
197
+ ### Runtime Activation and Deactivation
198
+
199
+ ```cpp
200
+ FString PluginURL = UGameFeaturesSubsystem::GetPluginURL_FileProtocol(
201
+ TEXT("/MyProject/Plugins/GameFeatures/MyFeature/MyFeature.uplugin"));
202
+
203
+ // Activate — callback receives const UE::GameFeatures::FResult&
204
+ GFS.LoadAndActivateGameFeaturePlugin(PluginURL,
205
+ FGameFeaturePluginLoadComplete::CreateUObject(this, &UMyMgr::OnLoaded));
206
+
207
+ // Deactivate and unload
208
+ GFS.DeactivateGameFeaturePlugin(PluginURL);
209
+ GFS.UnloadGameFeaturePlugin(PluginURL, /*bKeepRegistered=*/ false);
210
+
211
+ // Or target a specific state:
212
+ GFS.ChangeGameFeatureTargetState(PluginURL, EGameFeatureTargetState::Registered,
213
+ FGameFeaturePluginChangeStateComplete());
214
+ ```
215
+
216
+ ### Query and Observe
217
+
218
+ ```cpp
219
+ bool bActive = GFS.IsGameFeaturePluginActive(PluginURL, /*bCheckForActivating=*/ false);
220
+ EGameFeaturePluginState State = GFS.GetPluginState(PluginURL);
221
+
222
+ // Global observer — implement IGameFeatureStateChangeObserver
223
+ GFS.AddObserver(MyObserver, UGameFeaturesSubsystem::EObserverPluginStateUpdateMode::CurrentAndFuture);
224
+ GFS.RemoveObserver(MyObserver);
225
+ ```
226
+
227
+ `IGameFeatureStateChangeObserver` provides: `OnGameFeatureRegistering(Data, PluginName, URL)`, `OnGameFeatureActivating(Data, URL)`, `OnGameFeatureDeactivating(Data, Context, URL)`.
228
+
229
+ ---
230
+
231
+ ## Component Injection System
232
+
233
+ `UGameFrameworkComponentManager` (`UGameInstanceSubsystem`) is the runtime engine that injects components into actors. It is a **Game Instance subsystem** — not an engine subsystem:
234
+
235
+ ```cpp
236
+ UGameFrameworkComponentManager* CompMgr =
237
+ GetGameInstance()->GetSubsystem<UGameFrameworkComponentManager>();
238
+ ```
239
+
240
+ ### Actor Registration (Receivers)
241
+
242
+ Actors must register as receivers to accept injected components:
243
+
244
+ ```cpp
245
+ void AMyCharacter::BeginPlay()
246
+ {
247
+ Super::BeginPlay();
248
+ UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
249
+ }
250
+ void AMyCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
251
+ {
252
+ UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
253
+ Super::EndPlay(EndPlayReason);
254
+ }
255
+ ```
256
+
257
+ ### Component Requests (RAII)
258
+
259
+ ```cpp
260
+ TSharedPtr<FComponentRequestHandle> Handle = CompMgr->AddComponentRequest(
261
+ TSoftClassPtr<AActor>(AMyCharacter::StaticClass()),
262
+ UMyHealthComponent::StaticClass(),
263
+ EGameFrameworkAddComponentFlags::AddUnique);
264
+ // Handle is RAII — destroying it removes the request and cleans up injected components
265
+ ```
266
+
267
+ | Flag | Value | Behavior |
268
+ |------|-------|----------|
269
+ | `None` | 0 | Default, allows duplicates |
270
+ | `AddUnique` | 1 | Skip if same class already exists |
271
+ | `AddIfNotChild` | 2 | Skip if a child class already exists |
272
+ | `UseAutoGeneratedName` | 4 | Auto-generated name instead of class name |
273
+
274
+ ### Extension Handlers and Events
275
+
276
+ ```cpp
277
+ TSharedPtr<FComponentRequestHandle> ExtHandle = CompMgr->AddExtensionHandler(
278
+ TSoftClassPtr<AActor>(AMyCharacter::StaticClass()),
279
+ FExtensionHandlerDelegate::CreateUObject(this, &UMyAction::OnExtension));
280
+
281
+ void UMyAction::OnExtension(AActor* Actor, FName EventName)
282
+ {
283
+ if (EventName == UGameFrameworkComponentManager::NAME_GameActorReady)
284
+ { /* Actor fully initialized */ }
285
+ }
286
+ ```
287
+
288
+ Standard event names: `NAME_ReceiverAdded`, `NAME_ReceiverRemoved`, `NAME_ExtensionAdded`, `NAME_ExtensionRemoved`, `NAME_GameActorReady`. Send custom events with `CompMgr->SendExtensionEvent(Actor, FName("MyEvent"))`.
289
+
290
+ ---
291
+
292
+ ## Init State System
293
+
294
+ The init state system solves ordered initialization across independently-loaded features. Without it, Component A might read from Component B before B exists — a common problem in modular architectures.
295
+
296
+ ### Registering Init States
297
+
298
+ Define project-wide init states as `FGameplayTag` values in a fixed order:
299
+
300
+ ```cpp
301
+ CompMgr->RegisterInitState(TAG_InitState_Spawning, false, FGameplayTag());
302
+ CompMgr->RegisterInitState(TAG_InitState_DataAvailable, false, TAG_InitState_Spawning);
303
+ CompMgr->RegisterInitState(TAG_InitState_DataInitialized, false, TAG_InitState_DataAvailable);
304
+ CompMgr->RegisterInitState(TAG_InitState_GameplayReady, false, TAG_InitState_DataInitialized);
305
+ ```
306
+
307
+ ### Changing and Observing Init State
308
+
309
+ ```cpp
310
+ // Advance a feature's state
311
+ bool bChanged = CompMgr->ChangeFeatureInitState(
312
+ MyActor, FName("MyComponent"), this, TAG_InitState_DataAvailable);
313
+
314
+ // Wait for another feature to reach a state
315
+ FDelegateHandle DH = CompMgr->RegisterAndCallForActorInitState(
316
+ MyActor, FName("OtherComp"), TAG_InitState_DataInitialized,
317
+ FActorInitStateChangedDelegate::CreateUObject(this, &UMyComp::OnOtherReady),
318
+ /*bCallImmediately=*/ true);
319
+
320
+ // Check if all features reached a state
321
+ bool bAllReady = CompMgr->HaveAllFeaturesReachedInitState(
322
+ MyActor, TAG_InitState_GameplayReady, /*ExcludingFeature=*/ NAME_None);
323
+ ```
324
+
325
+ ### IGameFrameworkInitStateInterface
326
+
327
+ Implement on components for structured init state progression:
328
+
329
+ ```cpp
330
+ UCLASS()
331
+ class UMyModularComponent : public UPawnComponent,
332
+ public IGameFrameworkInitStateInterface
333
+ {
334
+ GENERATED_BODY()
335
+ public:
336
+ virtual FName GetFeatureName() const override { return TEXT("MyFeature"); }
337
+ virtual bool CanChangeInitState(UGameFrameworkComponentManager* Manager,
338
+ FGameplayTag CurrentState, FGameplayTag DesiredState) const override;
339
+ virtual void HandleChangeInitState(UGameFrameworkComponentManager* Manager,
340
+ FGameplayTag CurrentState, FGameplayTag DesiredState) override;
341
+ virtual void CheckDefaultInitialization() override;
342
+
343
+ virtual void BeginPlay() override
344
+ {
345
+ Super::BeginPlay();
346
+ RegisterInitStateFeature();
347
+ }
348
+ virtual void EndPlay(const EEndPlayReason::Type Reason) override
349
+ {
350
+ UnregisterInitStateFeature();
351
+ Super::EndPlay(Reason);
352
+ }
353
+ };
354
+ ```
355
+
356
+ `ContinueInitStateChain(TArray<FGameplayTag>{State1, State2, State3})` attempts to advance through a sequence of states. Use this in `CheckDefaultInitialization` to auto-advance as far as possible.
357
+
358
+ ---
359
+
360
+ ## Modular Component Hierarchy
361
+
362
+ The `ModularGameplay` plugin provides typed base components for gameplay framework actors:
363
+
364
+ | Base Class | Parent | Typed Accessor |
365
+ |------------|--------|----------------|
366
+ | `UGameFrameworkComponent` | `UActorComponent` | None (generic base) |
367
+ | `UPawnComponent` | `UGameFrameworkComponent` | `GetPawn<T>()`, `GetPawnChecked<T>()` |
368
+ | `UControllerComponent` | `UGameFrameworkComponent` | `GetController<T>()`, `GetControllerChecked<T>()` |
369
+ | `UGameStateComponent` | `UGameFrameworkComponent` | `GetGameState<T>()`, `GetGameStateChecked<T>()` |
370
+ | `UPlayerStateComponent` | `UGameFrameworkComponent` | `GetPlayerState<T>()`, `GetPlayerStateChecked<T>()` |
371
+
372
+ Use these instead of raw `UActorComponent` for type-safe owner access and init state integration.
373
+
374
+ ---
375
+
376
+ ## Experience System Pattern
377
+
378
+ The experience system (pioneered by Lyra) composes game modes from Game Feature plugins at runtime. Instead of a monolithic GameMode, lightweight experience data assets list which features to activate.
379
+
380
+ ### Core Flow
381
+
382
+ ```
383
+ GameMode::InitGame()
384
+ → Load UExperienceDefinition (from map or URL options)
385
+ → For each feature: LoadAndActivateGameFeaturePlugin()
386
+ → All loaded → OnExperienceLoaded broadcast
387
+ → Components initialize, gameplay begins
388
+ ```
389
+
390
+ A `UExperienceManagerComponent` on `AGameStateBase` orchestrates loading. Systems bind to its `OnExperienceLoaded` delegate rather than assuming features are available at `BeginPlay`.
391
+
392
+ See `references/experience-system.md` for the full pattern with code templates.
393
+
394
+ ---
395
+
396
+ ## Project Policies
397
+
398
+ `UGameFeaturesProjectPolicies` controls feature loading behavior. Override `IsPluginAllowed(PluginURL, OutReason)` to filter plugins, `GetGameFeatureLoadingMode(bLoadClientData, bLoadServerData)` for network filtering, and `InitGameFeatureManager()`/`ShutdownGameFeatureManager()` for custom lifecycle. Register via `DefaultGame.ini` under `GameFeaturesSubsystemSettings`.
399
+
400
+ See `references/game-feature-patterns.md` for the full policies subclass template.
401
+
402
+ ---
403
+
404
+ ## Common Mistakes
405
+
406
+ **Missing receiver registration:**
407
+ ```cpp
408
+ // WRONG — components never injected, no error logged
409
+ void AMyCharacter::BeginPlay() { Super::BeginPlay(); }
410
+ // RIGHT
411
+ void AMyCharacter::BeginPlay()
412
+ {
413
+ Super::BeginPlay();
414
+ UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
415
+ }
416
+ ```
417
+
418
+ **Leaking FComponentRequestHandle:**
419
+ ```cpp
420
+ // WRONG — handle destroyed immediately, component removed next frame
421
+ CompMgr->AddComponentRequest(ActorClass, CompClass, Flags);
422
+ // RIGHT — store for lifetime of injection
423
+ RequestHandle = CompMgr->AddComponentRequest(ActorClass, CompClass, Flags);
424
+ ```
425
+
426
+ **Using BeginPlay for cross-component init:**
427
+ ```cpp
428
+ // WRONG — modular components may not exist yet
429
+ void UMyComp::BeginPlay() { GetOwner()->FindComponentByClass<UOther>()->Configure(); }
430
+ // RIGHT — use init state system to wait for dependencies
431
+ void UMyComp::HandleChangeInitState(/*...*/, FGameplayTag DesiredState)
432
+ {
433
+ if (DesiredState == TAG_InitState_DataInitialized)
434
+ GetOwner()->FindComponentByClass<UOther>()->Configure();
435
+ }
436
+ ```
437
+
438
+ **Forgetting PauseDeactivationUntilComplete delegate:** If you call `PauseDeactivationUntilComplete` but never invoke the returned delegate, plugin deactivation hangs indefinitely. Always invoke it, even on error paths.
439
+
440
+ **Wrong subsystem type for ComponentManager:**
441
+ ```cpp
442
+ // WRONG — NOT an engine subsystem
443
+ GEngine->GetEngineSubsystem<UGameFrameworkComponentManager>();
444
+ // RIGHT — UGameInstanceSubsystem
445
+ GetGameInstance()->GetSubsystem<UGameFrameworkComponentManager>();
446
+ ```
447
+
448
+ ---
449
+
450
+ ## Related Skills
451
+
452
+ - `/ue-gameplay-framework` — GameMode, GameState, PlayerController, PlayerState lifecycle
453
+ - `/ue-actor-component-architecture` — Component creation, attachment, tick management
454
+ - `/ue-gameplay-abilities` — GAS integration with modular components
455
+ - `/ue-data-assets-tables` — Primary data assets, Asset Manager scanning
456
+ - `/ue-module-build-system` — Plugin structure, Build.cs dependencies
457
+ - `/ue-world-level-streaming` — Level streaming, seamless travel interactions
458
+
459
+ ---
460
+
461
+ ## Checklist
462
+
463
+ Validate before considering the work done:
464
+
465
+ - [ ] The feature plugin has no hard dependency back on the base game module
466
+ - [ ] Actions register and unregister symmetrically on activate/deactivate
467
+ - [ ] The feature can be disabled without breaking a packaged build
468
+ - [ ] Conventions in `docs/unreal/project-context.md` were read and followed
469
+ - [ ] The Common Mistakes section of this skill was reviewed against the change
470
+
471
+ **PASS** — every item holds. **CONCERNS** — the work stands but at least one item is unresolved and worth flagging to the user. **FAIL** — an item is violated in a way that breaks correctness, replication or the frame budget; fix it before handing the work over.
472
+
473
+ ## Next Steps
474
+
475
+ - `/ue-module-build-system` — verify plugin and module wiring
476
+ - `/code-review` — architectural and quality review of the change
477
+ - `/commit` — conventional commit once the change is verified
@@ -0,0 +1,222 @@
1
+ # Experience System Reference
2
+
3
+ The experience system pattern (pioneered by Lyra) composes game modes from multiple Game Feature
4
+ plugins at runtime. Instead of a monolithic `AGameMode` subclass per game type, you define
5
+ lightweight data assets that list which features to activate.
6
+
7
+ ---
8
+
9
+ ## Experience Definition Asset
10
+
11
+ ```cpp
12
+ // UExperienceDefinition — a primary data asset listing features to compose
13
+ #pragma once
14
+ #include "Engine/DataAsset.h"
15
+ #include "ExperienceDefinition.generated.h"
16
+
17
+ UCLASS()
18
+ class UExperienceDefinition : public UPrimaryDataAsset
19
+ {
20
+ GENERATED_BODY()
21
+ public:
22
+ // Game Feature plugins to activate for this experience
23
+ UPROPERTY(EditDefaultsOnly, Category = "Experience")
24
+ TArray<FString> GameFeaturesToEnable;
25
+
26
+ // Actions to execute directly (without a full Game Feature plugin)
27
+ UPROPERTY(EditDefaultsOnly, Instanced, Category = "Experience")
28
+ TArray<TObjectPtr<UGameFeatureAction>> Actions;
29
+
30
+ // Default pawn data for this experience
31
+ UPROPERTY(EditDefaultsOnly, Category = "Experience")
32
+ TObjectPtr<UPawnData> DefaultPawnData;
33
+
34
+ virtual FPrimaryAssetId GetPrimaryAssetId() const override
35
+ {
36
+ return FPrimaryAssetId(FPrimaryAssetType("Experience"),
37
+ GetFName());
38
+ }
39
+ };
40
+ ```
41
+
42
+ Example experience assets:
43
+ - **B_Deathmatch**: Enables `ShooterCore` + `DeathmatchRules`
44
+ - **B_ControlPoint**: Enables `ShooterCore` + `ControlPointRules`
45
+ - **B_FrontEnd**: Enables `FrontEndUI` only (no gameplay features)
46
+
47
+ Each experience shares common features (e.g., `ShooterCore` for weapons, health, HUD) and
48
+ adds mode-specific rules as separate Game Feature plugins.
49
+
50
+ ---
51
+
52
+ ## Experience Manager Component
53
+
54
+ A `UGameStateComponent` on `AGameStateBase` orchestrates experience loading:
55
+
56
+ ```cpp
57
+ #pragma once
58
+ #include "Components/GameStateComponent.h"
59
+ #include "ExperienceManagerComponent.generated.h"
60
+
61
+ DECLARE_MULTICAST_DELEGATE_OneParam(FOnExperienceLoaded, const UExperienceDefinition*);
62
+
63
+ UCLASS()
64
+ class UExperienceManagerComponent : public UGameStateComponent
65
+ {
66
+ GENERATED_BODY()
67
+ public:
68
+ // Call from GameMode::InitGame to start loading
69
+ void SetCurrentExperience(FPrimaryAssetId ExperienceId);
70
+
71
+ // Check if experience is fully loaded and active
72
+ bool IsExperienceLoaded() const { return bExperienceLoaded; }
73
+
74
+ // Bind to get notified when experience is ready — fires immediately if already loaded
75
+ void CallOrRegister_OnExperienceLoaded(FOnExperienceLoaded::FDelegate&& Delegate);
76
+
77
+ const UExperienceDefinition* GetCurrentExperience() const { return CurrentExperience; }
78
+
79
+ FOnExperienceLoaded OnExperienceLoaded;
80
+
81
+ private:
82
+ void OnExperienceLoadComplete();
83
+ void OnGameFeaturePluginLoadComplete(const UE::GameFeatures::FResult& Result);
84
+
85
+ UPROPERTY()
86
+ TObjectPtr<const UExperienceDefinition> CurrentExperience;
87
+
88
+ int32 NumGameFeaturePluginsLoading = 0;
89
+ bool bExperienceLoaded = false;
90
+ };
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Experience Loading Flow
96
+
97
+ ```cpp
98
+ // 1. GameMode selects and starts loading the experience
99
+ void AMyGameMode::InitGame(const FString& MapName, const FString& Options,
100
+ FString& ErrorMessage)
101
+ {
102
+ Super::InitGame(MapName, Options, ErrorMessage);
103
+
104
+ // Determine experience from map, URL options, or default
105
+ FPrimaryAssetId ExperienceId = FPrimaryAssetId(
106
+ FPrimaryAssetType("Experience"), FName("B_Deathmatch"));
107
+
108
+ // Get the experience manager on GameState
109
+ UExperienceManagerComponent* ExpMgr =
110
+ GameState->FindComponentByClass<UExperienceManagerComponent>();
111
+ ExpMgr->SetCurrentExperience(ExperienceId);
112
+ }
113
+
114
+ // 2. ExperienceManagerComponent loads the definition, then activates features
115
+ void UExperienceManagerComponent::SetCurrentExperience(FPrimaryAssetId ExperienceId)
116
+ {
117
+ // Async load the experience data asset
118
+ UAssetManager::Get().LoadPrimaryAsset(ExperienceId,
119
+ TArray<FName>(),
120
+ FStreamableDelegate::CreateUObject(this,
121
+ &UExperienceManagerComponent::OnExperienceLoadComplete));
122
+ }
123
+
124
+ void UExperienceManagerComponent::OnExperienceLoadComplete()
125
+ {
126
+ // Asset loaded — now activate each Game Feature plugin
127
+ CurrentExperience = Cast<UExperienceDefinition>(
128
+ UAssetManager::Get().GetPrimaryAssetObject(ExperienceId));
129
+
130
+ UGameFeaturesSubsystem& GFS = UGameFeaturesSubsystem::Get();
131
+ NumGameFeaturePluginsLoading = CurrentExperience->GameFeaturesToEnable.Num();
132
+
133
+ for (const FString& PluginURL : CurrentExperience->GameFeaturesToEnable)
134
+ {
135
+ GFS.LoadAndActivateGameFeaturePlugin(PluginURL,
136
+ FGameFeaturePluginLoadComplete::CreateUObject(
137
+ this, &UExperienceManagerComponent::OnGameFeaturePluginLoadComplete));
138
+ }
139
+
140
+ // If no plugins to load, mark ready immediately
141
+ if (NumGameFeaturePluginsLoading == 0)
142
+ {
143
+ bExperienceLoaded = true;
144
+ OnExperienceLoaded.Broadcast(CurrentExperience);
145
+ }
146
+ }
147
+
148
+ void UExperienceManagerComponent::OnGameFeaturePluginLoadComplete(
149
+ const UE::GameFeatures::FResult& Result)
150
+ {
151
+ NumGameFeaturePluginsLoading--;
152
+ if (NumGameFeaturePluginsLoading == 0)
153
+ {
154
+ bExperienceLoaded = true;
155
+ OnExperienceLoaded.Broadcast(CurrentExperience);
156
+ }
157
+ }
158
+
159
+ // 3. CallOrRegister pattern — fires immediately if already loaded
160
+ void UExperienceManagerComponent::CallOrRegister_OnExperienceLoaded(
161
+ FOnExperienceLoaded::FDelegate&& Delegate)
162
+ {
163
+ if (bExperienceLoaded)
164
+ {
165
+ Delegate.Execute(CurrentExperience);
166
+ }
167
+ else
168
+ {
169
+ OnExperienceLoaded.Add(MoveTemp(Delegate));
170
+ }
171
+ }
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Consuming the Experience
177
+
178
+ Systems that depend on the experience being ready use `CallOrRegister_OnExperienceLoaded`
179
+ instead of assuming features are available at `BeginPlay`:
180
+
181
+ ```cpp
182
+ void UMyPawnComponent::BeginPlay()
183
+ {
184
+ Super::BeginPlay();
185
+
186
+ AGameStateBase* GS = GetWorld()->GetGameState<AGameStateBase>();
187
+ if (UExperienceManagerComponent* ExpMgr =
188
+ GS->FindComponentByClass<UExperienceManagerComponent>())
189
+ {
190
+ ExpMgr->CallOrRegister_OnExperienceLoaded(
191
+ FOnExperienceLoaded::FDelegate::CreateUObject(
192
+ this, &UMyPawnComponent::OnExperienceReady));
193
+ }
194
+ }
195
+
196
+ void UMyPawnComponent::OnExperienceReady(const UExperienceDefinition* Experience)
197
+ {
198
+ // All features active — safe to look up injected components, configure abilities, etc.
199
+ // Experience->DefaultPawnData contains pawn configuration for this mode
200
+ }
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Feature Composition Example
206
+
207
+ A Deathmatch experience composed from modular features:
208
+
209
+ ```
210
+ B_Deathmatch (UExperienceDefinition)
211
+ ├── GameFeaturesToEnable:
212
+ │ ├── "ShooterCore" → Health, weapons, HUD, hit detection
213
+ │ ├── "DeathmatchRules" → Score tracking, kill feed, respawn timer
214
+ │ └── "TeamSystem" → Team assignment, team colors, team HUD
215
+ ├── Actions:
216
+ │ └── AddComponents: DeathmatchScoreComponent → AGameState
217
+ └── DefaultPawnData: BP_ShooterCharacter
218
+ ```
219
+
220
+ Switching to Control Point mode replaces `DeathmatchRules` with `ControlPointRules` while
221
+ keeping `ShooterCore` and `TeamSystem`. This avoids duplicating shared gameplay code and
222
+ allows features to be developed and tested independently.