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,306 @@
1
+ # Asset Loading Patterns
2
+
3
+ Reference guide for async loading, `FStreamableManager`, load grouping, and handle lifecycle in Unreal Engine 5.
4
+
5
+ ---
6
+
7
+ ## FStreamableManager Access
8
+
9
+ `FStreamableManager` is a member of `UAssetManager`. Do not create your own instances in game code — share the one owned by the Asset Manager.
10
+
11
+ ```cpp
12
+ // Preferred access (UE5):
13
+ FStreamableManager& SM = UAssetManager::GetStreamableManager();
14
+ // Equivalent to: UAssetManager::Get().StreamableManager
15
+ ```
16
+
17
+ Module dependency required in `Build.cs`:
18
+ ```csharp
19
+ PublicDependencyModuleNames.AddRange(new string[] { "Engine", "AssetManager" });
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Pattern 1: Single Asset, Callback on Completion
25
+
26
+ ```cpp
27
+ // Header
28
+ TSharedPtr<FStreamableHandle> LoadHandle;
29
+
30
+ UPROPERTY(EditDefaultsOnly)
31
+ TSoftObjectPtr<UStaticMesh> WeaponMeshSoft;
32
+
33
+ // Load
34
+ void AMyCharacter::BeginLoadWeaponMesh()
35
+ {
36
+ FStreamableManager& SM = UAssetManager::GetStreamableManager();
37
+
38
+ LoadHandle = SM.RequestAsyncLoad(
39
+ WeaponMeshSoft.ToSoftObjectPath(),
40
+ FStreamableDelegate::CreateUObject(this, &AMyCharacter::OnWeaponMeshReady));
41
+ }
42
+
43
+ void AMyCharacter::OnWeaponMeshReady()
44
+ {
45
+ // At callback time, IsValid() returns true if successfully loaded.
46
+ if (WeaponMeshSoft.IsValid())
47
+ {
48
+ WeaponMeshComp->SetStaticMesh(WeaponMeshSoft.Get());
49
+ }
50
+ // Handle can now be released; assets stay in memory while referenced by component.
51
+ LoadHandle.Reset();
52
+ }
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Pattern 2: Multiple Assets, Single Callback
58
+
59
+ Load a batch and receive one callback when all are ready.
60
+
61
+ ```cpp
62
+ void UMyLoadSystem::LoadUIAssets(const TArray<TSoftObjectPtr<UTexture2D>>& IconRefs)
63
+ {
64
+ TArray<FSoftObjectPath> Paths;
65
+ Paths.Reserve(IconRefs.Num());
66
+ for (const TSoftObjectPtr<UTexture2D>& Ref : IconRefs)
67
+ {
68
+ if (!Ref.IsNull())
69
+ {
70
+ Paths.Add(Ref.ToSoftObjectPath());
71
+ }
72
+ }
73
+
74
+ if (Paths.IsEmpty()) { return; }
75
+
76
+ FStreamableManager& SM = UAssetManager::GetStreamableManager();
77
+ UILoadHandle = SM.RequestAsyncLoad(
78
+ Paths,
79
+ FStreamableDelegate::CreateUObject(this, &UMyLoadSystem::OnUIAssetsLoaded));
80
+ }
81
+
82
+ void UMyLoadSystem::OnUIAssetsLoaded()
83
+ {
84
+ // Retrieve loaded assets from handle.
85
+ TArray<UTexture2D*> LoadedIcons;
86
+ UILoadHandle->GetLoadedAssets(LoadedIcons);
87
+
88
+ for (UTexture2D* Icon : LoadedIcons)
89
+ {
90
+ if (Icon) { /* bind to widget */ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Pattern 3: High Priority / Synchronous Fallback
98
+
99
+ For assets needed immediately (e.g., during level transition):
100
+
101
+ ```cpp
102
+ // Option A: RequestSyncLoad — blocks until done.
103
+ // Use only for small assets or during loading screens.
104
+ TSharedPtr<FStreamableHandle> Handle = SM.RequestSyncLoad(
105
+ AssetPath,
106
+ false, // bManageActiveHandle
107
+ TEXT("LevelTransition sync load"));
108
+
109
+ UObject* Asset = Handle->GetLoadedAsset();
110
+
111
+ // Option B: WaitUntilComplete on an already-started async load.
112
+ if (LoadHandle.IsValid() && LoadHandle->IsLoadingInProgress())
113
+ {
114
+ LoadHandle->WaitUntilComplete(5.f); // Timeout seconds; 0 = wait forever.
115
+ }
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Pattern 4: Lambda Callback with Captured State
121
+
122
+ ```cpp
123
+ FPrimaryAssetId AssetId(TEXT("WeaponDefinition"), TEXT("DA_Rifle"));
124
+
125
+ TSharedPtr<FStreamableHandle> Handle = UAssetManager::Get().LoadPrimaryAsset(
126
+ AssetId,
127
+ TArray<FName>{ TEXT("Game") },
128
+ FStreamableDelegate::CreateLambda([this, AssetId]()
129
+ {
130
+ UWeaponDefinition* Def =
131
+ UAssetManager::Get().GetPrimaryAssetObject<UWeaponDefinition>(AssetId);
132
+ if (Def && IsValid(this))
133
+ {
134
+ EquipWeapon(Def);
135
+ }
136
+ }));
137
+ ```
138
+
139
+ Important: Lambdas capturing `this` are not safe if the object is destroyed before the callback fires. Use `CreateUObject` (which checks object validity) or guard with `IsValid(this)`.
140
+
141
+ ---
142
+
143
+ ## Pattern 5: Progress Tracking
144
+
145
+ ```cpp
146
+ void UMyLoadingScreen::StartLoadWithProgress(const TArray<FSoftObjectPath>& Assets)
147
+ {
148
+ FStreamableManager& SM = UAssetManager::GetStreamableManager();
149
+
150
+ LoadHandle = SM.RequestAsyncLoad(
151
+ Assets,
152
+ FStreamableDelegate::CreateUObject(this, &UMyLoadingScreen::OnLoadComplete));
153
+ }
154
+
155
+ // Poll from Tick or a timer.
156
+ void UMyLoadingScreen::Tick(float DeltaTime)
157
+ {
158
+ if (LoadHandle.IsValid() && LoadHandle->IsLoadingInProgress())
159
+ {
160
+ float Progress = LoadHandle->GetLoadProgress(); // 0.0 to 1.0
161
+ ProgressBar->SetPercent(Progress);
162
+ }
163
+ }
164
+
165
+ void UMyLoadingScreen::OnLoadComplete()
166
+ {
167
+ ProgressBar->SetPercent(1.f);
168
+ LoadHandle.Reset();
169
+ HideLoadingScreen();
170
+ }
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Pattern 6: Load Grouping with Asset Manager Bundles
176
+
177
+ Bundle state transitions are the recommended way to manage groups of related assets across gameplay states.
178
+
179
+ ```cpp
180
+ UAssetManager& AM = UAssetManager::Get();
181
+
182
+ // Get all weapon IDs.
183
+ TArray<FPrimaryAssetId> WeaponIds;
184
+ AM.GetPrimaryAssetIdList(FPrimaryAssetType(TEXT("WeaponDefinition")), WeaponIds);
185
+
186
+ // On entering the main menu: load UI bundle only.
187
+ AM.LoadPrimaryAssets(WeaponIds, { TEXT("UI") });
188
+
189
+ // On entering gameplay: add Game bundle, keep UI or remove it.
190
+ AM.ChangeBundleStateForPrimaryAssets(
191
+ WeaponIds,
192
+ { TEXT("Game") }, // AddBundles
193
+ { TEXT("UI") }, // RemoveBundles
194
+ false, // bRemoveAllBundles
195
+ FStreamableDelegate::CreateLambda([]() { /* gameplay ready */ }));
196
+
197
+ // On leaving gameplay: unload Game bundle, keep UI.
198
+ AM.ChangeBundleStateForMatchingPrimaryAssets(
199
+ { TEXT("UI") }, // NewBundles
200
+ { TEXT("Game") }); // OldBundles (removes these)
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Handle Lifecycle Rules
206
+
207
+ | State | Meaning |
208
+ |---|---|
209
+ | `IsLoadingInProgress()` | Async load still running |
210
+ | `HasLoadCompleted()` | All assets loaded (callback may not have fired yet if deferred) |
211
+ | `WasCanceled()` | `CancelHandle()` was called; completion callback not invoked |
212
+ | `HasError()` | One or more assets failed to load |
213
+
214
+ **Ownership rules:**
215
+ - `TSharedPtr<FStreamableHandle>` keeps assets pinned while alive.
216
+ - Letting the `TSharedPtr` go out of scope calls `ReleaseHandle()` implicitly.
217
+ - `ReleaseHandle()` is deferred if called before the completion callback fires; callback still runs.
218
+ - `CancelHandle()` cancels immediately — completion callback is NOT called; cancel callback is called if bound.
219
+
220
+ ```cpp
221
+ // Cancellation with a cancel callback.
222
+ Handle->BindCancelDelegate(FStreamableDelegate::CreateLambda([]()
223
+ {
224
+ UE_LOG(LogTemp, Warning, TEXT("Load was canceled."));
225
+ }));
226
+ Handle->CancelHandle();
227
+ ```
228
+
229
+ ---
230
+
231
+ ## Combined / Merged Handles
232
+
233
+ When multiple independent loads should be tracked as one unit:
234
+
235
+ ```cpp
236
+ TSharedPtr<FStreamableHandle> HandleA = SM.RequestAsyncLoad(PathA, {});
237
+ TSharedPtr<FStreamableHandle> HandleB = SM.RequestAsyncLoad(PathB, {});
238
+
239
+ // Combine into a single tracking handle. CreateCombinedHandle is on FStreamableManager.
240
+ TSharedPtr<FStreamableHandle> Merged = SM.CreateCombinedHandle({ HandleA, HandleB });
241
+
242
+ Merged->BindCompleteDelegate(FStreamableDelegate::CreateLambda([]()
243
+ {
244
+ // Both A and B are loaded.
245
+ }));
246
+ ```
247
+
248
+ ---
249
+
250
+ ## Priority Constants
251
+
252
+ From `Engine/StreamableManager.h` (namespace `UE::StreamableManager::Private`):
253
+
254
+ ```cpp
255
+ constexpr TAsyncLoadPriority DefaultAsyncLoadPriority = 0;
256
+ constexpr TAsyncLoadPriority AsyncLoadHighPriority = 100;
257
+ ```
258
+
259
+ Pass priority to `RequestAsyncLoad`:
260
+ ```cpp
261
+ SM.RequestAsyncLoad(
262
+ Paths, Delegate,
263
+ UE::StreamableManager::Private::AsyncLoadHighPriority);
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Anti-Patterns
269
+
270
+ ### Releasing Handle Before Using Loaded Assets
271
+
272
+ ```cpp
273
+ // BAD: assets may be GC'd immediately after Reset().
274
+ SM.RequestAsyncLoad(Path, [this]() {
275
+ LoadHandle.Reset(); // released inside callback
276
+ UStaticMesh* Mesh = MeshSoft.Get(); // nullptr — already GC'd
277
+ });
278
+
279
+ // GOOD: only reset after assigning to a hard reference.
280
+ SM.RequestAsyncLoad(Path, [this]() {
281
+ MeshComp->SetStaticMesh(MeshSoft.Get()); // component holds the ref
282
+ LoadHandle.Reset(); // safe to release now
283
+ });
284
+ ```
285
+
286
+ ### Calling LoadSynchronous in Tick
287
+
288
+ ```cpp
289
+ // BAD: stalls every frame if asset not cached.
290
+ void AMyActor::Tick(float DeltaTime)
291
+ {
292
+ UStaticMesh* Mesh = MeshSoft.LoadSynchronous(); // blocks rendering
293
+ }
294
+
295
+ // GOOD: start async load once, use result only after callback.
296
+ ```
297
+
298
+ ### Ignoring Load Errors
299
+
300
+ ```cpp
301
+ // Always check after callback:
302
+ if (Handle->HasError())
303
+ {
304
+ UE_LOG(LogTemp, Error, TEXT("One or more assets failed to load."));
305
+ }
306
+ ```
@@ -0,0 +1,378 @@
1
+ # Data-Driven Design in Unreal Engine
2
+
3
+ Patterns for structuring game data using `UDataAsset`, `UPrimaryDataAsset`, and `UDataTable` so designers control gameplay values without C++ changes.
4
+
5
+ ---
6
+
7
+ ## Core Principle
8
+
9
+ Data-driven design separates **what** (values, configurations, relationships) from **how** (C++ logic). C++ defines the schema; designers populate instances in the editor or spreadsheets.
10
+
11
+ | Layer | Owner | Tool |
12
+ |---|---|---|
13
+ | Data schema | Programmer | C++ `USTRUCT` / `UCLASS` |
14
+ | Data values | Designer | Data Asset editor, CSV/JSON |
15
+ | Data loading | Programmer | Asset Manager, soft refs |
16
+ | Data consumption | Programmer | `FindRow`, `GetPrimaryAssetObject` |
17
+
18
+ ---
19
+
20
+ ## Pattern A: Item / Ability Definitions (UPrimaryDataAsset)
21
+
22
+ Best when each item has unique properties, may need Blueprint extension, and should integrate with the Asset Manager for selective loading.
23
+
24
+ ### Schema
25
+
26
+ ```cpp
27
+ // ItemDefinition.h
28
+ #pragma once
29
+ #include "Engine/DataAsset.h"
30
+ #include "GameplayTagContainer.h"
31
+ #include "ItemDefinition.generated.h"
32
+
33
+ UENUM(BlueprintType)
34
+ enum class EItemRarity : uint8
35
+ {
36
+ Common,
37
+ Uncommon,
38
+ Rare,
39
+ Legendary
40
+ };
41
+
42
+ UCLASS(BlueprintType)
43
+ class MYGAME_API UItemDefinition : public UPrimaryDataAsset
44
+ {
45
+ GENERATED_BODY()
46
+ public:
47
+ // Identity
48
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item")
49
+ FText DisplayName;
50
+
51
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item")
52
+ FText Description;
53
+
54
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item")
55
+ EItemRarity Rarity = EItemRarity::Common;
56
+
57
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item",
58
+ AssetRegistrySearchable)
59
+ FGameplayTagContainer ItemTags;
60
+
61
+ // Stats
62
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Stats")
63
+ float BaseDamage = 0.f;
64
+
65
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Stats")
66
+ int32 MaxStack = 1;
67
+
68
+ // UI bundle: loaded in inventory screen.
69
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Art",
70
+ meta = (AssetBundles = "UI"))
71
+ TSoftObjectPtr<UTexture2D> Icon;
72
+
73
+ // Game bundle: loaded in the world.
74
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Art",
75
+ meta = (AssetBundles = "Game"))
76
+ TSoftObjectPtr<UStaticMesh> WorldMesh;
77
+
78
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Spawning",
79
+ meta = (AssetBundles = "Game"))
80
+ TSoftClassPtr<AActor> DroppedActorClass;
81
+ };
82
+ ```
83
+
84
+ ### DefaultGame.ini Registration
85
+
86
+ ```ini
87
+ [/Script/Engine.AssetManagerSettings]
88
+ +PrimaryAssetTypesToScan=(
89
+ PrimaryAssetType="ItemDefinition",
90
+ AssetBaseClass=/Script/MyGame.ItemDefinition,
91
+ bHasBlueprintClasses=False,
92
+ bIsEditorOnly=False,
93
+ Directories=((Path="/Game/Data/Items")),
94
+ Rules=(Priority=1,ChunkId=-1,bApplyRecursively=True,CookRule=AlwaysCook))
95
+ ```
96
+
97
+ ### Discovery and Loading
98
+
99
+ ```cpp
100
+ // Get all item IDs (no assets loaded yet).
101
+ TArray<FPrimaryAssetId> AllItemIds;
102
+ UAssetManager::Get().GetPrimaryAssetIdList(
103
+ FPrimaryAssetType(TEXT("ItemDefinition")), AllItemIds);
104
+
105
+ // Filter by tag using Asset Registry (no load).
106
+ IAssetRegistry& AR = IAssetRegistry::GetChecked();
107
+ TArray<FAssetData> MeleeAssets;
108
+ AR.GetAssetsByClass(
109
+ FTopLevelAssetPath(TEXT("/Script/MyGame"), TEXT("ItemDefinition")),
110
+ MeleeAssets, true);
111
+
112
+ // Load UI bundle for inventory.
113
+ UAssetManager::Get().LoadPrimaryAssetsWithType(
114
+ FPrimaryAssetType(TEXT("ItemDefinition")),
115
+ { TEXT("UI") });
116
+
117
+ // After callback: retrieve a specific item.
118
+ FPrimaryAssetId SwordId(TEXT("ItemDefinition"), TEXT("DA_Sword"));
119
+ UItemDefinition* Sword =
120
+ UAssetManager::Get().GetPrimaryAssetObject<UItemDefinition>(SwordId);
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Pattern B: Stat Tables (UDataTable)
126
+
127
+ Best for large, flat, designer-authored datasets where all rows share the same shape: XP curves, damage falloff, NPC dialogue, loot drop weights.
128
+
129
+ ### Row Struct
130
+
131
+ ```cpp
132
+ // XPTableRow.h
133
+ #pragma once
134
+ #include "Engine/DataTable.h"
135
+ #include "XPTableRow.generated.h"
136
+
137
+ USTRUCT(BlueprintType)
138
+ struct FXPTableRow : public FTableRowBase
139
+ {
140
+ GENERATED_BODY()
141
+
142
+ /** Player level. Also used as the row name in the DataTable. */
143
+ UPROPERTY(EditAnywhere, BlueprintReadWrite)
144
+ int32 Level = 1;
145
+
146
+ /** XP required to reach this level from the previous one. */
147
+ UPROPERTY(EditAnywhere, BlueprintReadWrite)
148
+ int32 XPRequired = 0;
149
+
150
+ /** Stat multiplier applied at this level. */
151
+ UPROPERTY(EditAnywhere, BlueprintReadWrite)
152
+ float StatMultiplier = 1.f;
153
+
154
+ // Validate row after import.
155
+ virtual void OnPostDataImport(const UDataTable* InDataTable,
156
+ const FName InRowName,
157
+ TArray<FString>& OutCollectedImportProblems) override
158
+ {
159
+ if (XPRequired < 0)
160
+ {
161
+ OutCollectedImportProblems.Add(
162
+ FString::Printf(TEXT("Row '%s': XPRequired cannot be negative."),
163
+ *InRowName.ToString()));
164
+ }
165
+ }
166
+ };
167
+ ```
168
+
169
+ ### CSV Format
170
+
171
+ ```csv
172
+ ---,Level,XPRequired,StatMultiplier
173
+ Level_1,1,0,1.0
174
+ Level_2,2,100,1.05
175
+ Level_3,3,250,1.10
176
+ Level_4,4,500,1.15
177
+ ```
178
+
179
+ The first column (`---` or `Name`) is the row name. Import via: DataTable asset > Import > select CSV.
180
+
181
+ ### Runtime Lookup
182
+
183
+ ```cpp
184
+ // Stored as a hard reference because it is small and always needed.
185
+ UPROPERTY(EditDefaultsOnly, Category = "Data")
186
+ TObjectPtr<UDataTable> XPTable;
187
+
188
+ FXPTableRow* ULevelingComponent::GetRowForLevel(int32 Level) const
189
+ {
190
+ if (!XPTable) { return nullptr; }
191
+ FName RowName = FName(*FString::Printf(TEXT("Level_%d"), Level));
192
+ return XPTable->FindRow<FXPTableRow>(RowName, TEXT("GetRowForLevel"));
193
+ }
194
+
195
+ int32 ULevelingComponent::GetXPToNextLevel(int32 CurrentLevel) const
196
+ {
197
+ const FXPTableRow* Row = GetRowForLevel(CurrentLevel + 1);
198
+ return Row ? Row->XPRequired : 0;
199
+ }
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Pattern C: Config Objects (UDataAsset — Not Primary)
205
+
206
+ Use plain `UDataAsset` (not Primary) for global configuration that is always loaded with the referencing class, is not addressable by ID, and does not need Asset Manager integration.
207
+
208
+ ```cpp
209
+ // GameBalanceConfig.h
210
+ #pragma once
211
+ #include "Engine/DataAsset.h"
212
+ #include "GameBalanceConfig.generated.h"
213
+
214
+ UCLASS(BlueprintType)
215
+ class MYGAME_API UGameBalanceConfig : public UDataAsset
216
+ {
217
+ GENERATED_BODY()
218
+ public:
219
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Economy")
220
+ int32 StartingGold = 100;
221
+
222
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Economy")
223
+ float SellPriceMultiplier = 0.5f;
224
+
225
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Combat")
226
+ float GlobalDamageScale = 1.f;
227
+
228
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Combat")
229
+ float CritMultiplier = 2.f;
230
+ };
231
+ ```
232
+
233
+ Reference from GameMode or GameInstance with a hard `TObjectPtr`:
234
+
235
+ ```cpp
236
+ UPROPERTY(EditDefaultsOnly, Category = "Config")
237
+ TObjectPtr<UGameBalanceConfig> BalanceConfig;
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Pattern D: Hybrid — DataAsset with DataTable Reference
243
+
244
+ An item definition holds a soft reference to a DataTable for per-tier stat scaling, while the DataAsset handles identity and art.
245
+
246
+ ```cpp
247
+ UCLASS(BlueprintType)
248
+ class MYGAME_API UWeaponDefinition : public UPrimaryDataAsset
249
+ {
250
+ GENERATED_BODY()
251
+ public:
252
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
253
+ FText WeaponName;
254
+
255
+ // Reference to a scaling table — loaded on demand.
256
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Stats",
257
+ meta = (AssetBundles = "Game"))
258
+ TSoftObjectPtr<UDataTable> ScalingTable;
259
+
260
+ float GetDamageAtTier(int32 Tier) const
261
+ {
262
+ // ScalingTable must already be loaded (via bundle).
263
+ UDataTable* Table = ScalingTable.Get();
264
+ if (!Table) { return 0.f; }
265
+
266
+ FName RowName = FName(*FString::Printf(TEXT("Tier_%d"), Tier));
267
+ const FWeaponScalingRow* Row =
268
+ Table->FindRow<FWeaponScalingRow>(RowName, TEXT("GetDamageAtTier"));
269
+ return Row ? Row->Damage : 0.f;
270
+ }
271
+ };
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Pattern E: Subsystem as Data Gateway
277
+
278
+ A `UGameInstanceSubsystem` caches loaded data assets after initial scan, providing a central access point that avoids repeated Asset Manager calls throughout the codebase.
279
+
280
+ ```cpp
281
+ // ItemSubsystem.h
282
+ #pragma once
283
+ #include "Subsystems/GameInstanceSubsystem.h"
284
+ #include "ItemSubsystem.generated.h"
285
+
286
+ UCLASS()
287
+ class MYGAME_API UItemSubsystem : public UGameInstanceSubsystem
288
+ {
289
+ GENERATED_BODY()
290
+ public:
291
+ virtual void Initialize(FSubsystemCollectionBase& Collection) override;
292
+
293
+ UItemDefinition* GetItemById(const FPrimaryAssetId& Id) const;
294
+ TArray<UItemDefinition*> GetItemsByTag(FGameplayTag Tag) const;
295
+
296
+ private:
297
+ void OnItemsLoaded();
298
+
299
+ TMap<FPrimaryAssetId, TObjectPtr<UItemDefinition>> ItemCache;
300
+ TSharedPtr<FStreamableHandle> LoadHandle;
301
+ };
302
+ ```
303
+
304
+ ```cpp
305
+ // ItemSubsystem.cpp
306
+ void UItemSubsystem::Initialize(FSubsystemCollectionBase& Collection)
307
+ {
308
+ Super::Initialize(Collection);
309
+
310
+ UAssetManager& AM = UAssetManager::Get();
311
+ TArray<FPrimaryAssetId> ItemIds;
312
+ AM.GetPrimaryAssetIdList(FPrimaryAssetType(TEXT("ItemDefinition")), ItemIds);
313
+
314
+ LoadHandle = AM.LoadPrimaryAssets(
315
+ ItemIds,
316
+ { TEXT("UI") },
317
+ FStreamableDelegate::CreateUObject(this, &UItemSubsystem::OnItemsLoaded));
318
+ }
319
+
320
+ void UItemSubsystem::OnItemsLoaded()
321
+ {
322
+ UAssetManager& AM = UAssetManager::Get();
323
+ TArray<UObject*> LoadedObjects;
324
+ AM.GetPrimaryAssetObjectList(FPrimaryAssetType(TEXT("ItemDefinition")), LoadedObjects);
325
+
326
+ for (UObject* Obj : LoadedObjects)
327
+ {
328
+ if (UItemDefinition* Item = Cast<UItemDefinition>(Obj))
329
+ {
330
+ ItemCache.Add(Item->GetPrimaryAssetId(), Item);
331
+ }
332
+ }
333
+ }
334
+
335
+ UItemDefinition* UItemSubsystem::GetItemById(const FPrimaryAssetId& Id) const
336
+ {
337
+ const TObjectPtr<UItemDefinition>* Found = ItemCache.Find(Id);
338
+ return Found ? Found->Get() : nullptr;
339
+ }
340
+
341
+ TArray<UItemDefinition*> UItemSubsystem::GetItemsByTag(FGameplayTag Tag) const
342
+ {
343
+ TArray<UItemDefinition*> Results;
344
+ for (const auto& Pair : ItemCache)
345
+ {
346
+ if (Pair.Value && Pair.Value->ItemTags.HasTag(Tag))
347
+ {
348
+ Results.Add(Pair.Value);
349
+ }
350
+ }
351
+ return Results;
352
+ }
353
+ ```
354
+
355
+ ---
356
+
357
+ ## When to Use Each Approach
358
+
359
+ | Scenario | Recommended Approach |
360
+ |---|---|
361
+ | Per-item configs, ~10–500 items, designers use editor | `UPrimaryDataAsset` + Asset Manager |
362
+ | Large flat tables, designers use Excel/Sheets | `UDataTable` + CSV import |
363
+ | Global game settings, always in memory | `UDataAsset` (plain), hard reference |
364
+ | Server-only data (drop rates, economy) | `UDataTable` with `bStripFromClientBuilds=true` |
365
+ | Items with stat scaling per tier | Hybrid: PrimaryDataAsset + soft ref to DataTable |
366
+ | Runtime-generated content | `UDataTable::AddRow`, or dynamic `FPrimaryAssetId` via `AddDynamicAsset` |
367
+ | Querying without loading | `IAssetRegistry::GetAssetsByClass` + `FAssetData` |
368
+
369
+ ---
370
+
371
+ ## Designer Workflow Checklist
372
+
373
+ 1. **C++ schema merged**: Row struct or DataAsset class is compiled and visible in the editor.
374
+ 2. **Asset registered**: `DefaultGame.ini` has the correct `PrimaryAssetTypesToScan` entry (for PrimaryDataAssets).
375
+ 3. **Editor asset created**: Data Asset instances or DataTable created in Content Browser under the scanned path.
376
+ 4. **Data populated**: Fields filled in the editor or CSV imported.
377
+ 5. **Cook verified**: Launch a development cook and confirm assets appear in the cooked output. Check for "Asset not in cook" warnings in the log.
378
+ 6. **Bundle states tested**: Verify UI bundle loads in menus and Game bundle loads during gameplay without performance spikes.