claude-code-arcane 2.3.0 → 2.5.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.
- package/CHANGELOG.md +23 -0
- package/README.md +131 -41
- package/agents/game/ue-blueprint-specialist.md +125 -0
- package/agents/game/ue-gas-specialist.md +133 -0
- package/agents/game/ue-umg-specialist.md +131 -0
- package/dist/cli.js +642 -478
- package/docs/SKILLS-CATALOG.md +42 -3
- package/docs/USER-GUIDE.md +28 -54
- package/hooks/detect-unreal.sh +90 -0
- package/package.json +2 -1
- package/profiles/agile.yaml +1 -1
- package/profiles/ai.yaml +1 -1
- package/profiles/android-native.yaml +1 -1
- package/profiles/audio.yaml +1 -1
- package/profiles/backend-dotnet.yaml +1 -1
- package/profiles/backend-go.yaml +1 -1
- package/profiles/backend-nestjs.yaml +1 -1
- package/profiles/backend-nextjs.yaml +1 -1
- package/profiles/backend-ts.yaml +1 -1
- package/profiles/business.yaml +1 -1
- package/profiles/clevel.yaml +1 -1
- package/profiles/clickup.yaml +1 -1
- package/profiles/core.yaml +1 -1
- package/profiles/database.yaml +1 -1
- package/profiles/design.yaml +1 -1
- package/profiles/docs.yaml +1 -1
- package/profiles/ecommerce.yaml +1 -1
- package/profiles/finance.yaml +1 -1
- package/profiles/flutter.yaml +1 -1
- package/profiles/freelance.yaml +1 -1
- package/profiles/frontend.yaml +1 -1
- package/profiles/infra.yaml +1 -1
- package/profiles/integrations.yaml +1 -1
- package/profiles/ios-native.yaml +1 -1
- package/profiles/jira.yaml +1 -1
- package/profiles/job-hunt.yaml +1 -1
- package/profiles/marketing.yaml +1 -1
- package/profiles/mobile.yaml +1 -1
- package/profiles/regulatory.yaml +1 -1
- package/profiles/second-brain.yaml +1 -1
- package/profiles/security.yaml +1 -1
- package/profiles/self-improving.yaml +1 -1
- package/profiles/statusline.yaml +1 -1
- package/profiles/testing.yaml +1 -1
- package/profiles/unity-design.yaml +1 -1
- package/profiles/unity-dev.yaml +1 -1
- package/profiles/unreal-dev.yaml +97 -0
- package/profiles/visual-novel.yaml +1 -1
- package/rules/gamedev/unreal-code.md +125 -0
- package/skills/arcane-list/SKILL.md +12 -7
- package/skills/install-mcp/SKILL.md +135 -46
- package/skills/install-mcp/references/unreal-operations.md +55 -0
- package/skills/ue-actor-component-architecture/SKILL.md +509 -0
- package/skills/ue-actor-component-architecture/references/actor-lifecycle.md +301 -0
- package/skills/ue-actor-component-architecture/references/component-types.md +592 -0
- package/skills/ue-ai-navigation/SKILL.md +526 -0
- package/skills/ue-ai-navigation/references/behavior-tree-patterns.md +392 -0
- package/skills/ue-ai-navigation/references/behavior-tree-workflow.md +107 -0
- package/skills/ue-ai-navigation/references/custom-bttask.md +104 -0
- package/skills/ue-ai-navigation/references/eqs-reference.md +423 -0
- package/skills/ue-animation-system/SKILL.md +521 -0
- package/skills/ue-animation-system/references/anim-notify-reference.md +486 -0
- package/skills/ue-animation-system/references/locomotion-setup.md +550 -0
- package/skills/ue-async-threading/SKILL.md +433 -0
- package/skills/ue-async-threading/references/thread-safety-guide.md +213 -0
- package/skills/ue-async-threading/references/threading-patterns.md +345 -0
- package/skills/ue-audio-system/SKILL.md +527 -0
- package/skills/ue-audio-system/references/audio-setup-patterns.md +700 -0
- package/skills/ue-blueprints/SKILL.md +134 -0
- package/skills/ue-blueprints/references/communication.md +82 -0
- package/skills/ue-character-movement/SKILL.md +475 -0
- package/skills/ue-character-movement/references/cmc-extension-patterns.md +335 -0
- package/skills/ue-character-movement/references/movement-pipeline.md +189 -0
- package/skills/ue-cpp-foundations/SKILL.md +533 -0
- package/skills/ue-cpp-foundations/references/components-and-gc.md +107 -0
- package/skills/ue-cpp-foundations/references/container-patterns.md +544 -0
- package/skills/ue-cpp-foundations/references/delegate-patterns.md +497 -0
- package/skills/ue-cpp-foundations/references/gameplay-classes-cpp.md +136 -0
- package/skills/ue-cpp-foundations/references/property-specifiers.md +306 -0
- package/skills/ue-data-assets-tables/SKILL.md +527 -0
- package/skills/ue-data-assets-tables/references/asset-loading-patterns.md +306 -0
- package/skills/ue-data-assets-tables/references/data-driven-design.md +378 -0
- package/skills/ue-editor-tools/SKILL.md +523 -0
- package/skills/ue-editor-tools/references/detail-customization-patterns.md +395 -0
- package/skills/ue-editor-tools/references/editor-module-setup.md +480 -0
- package/skills/ue-game-features/SKILL.md +477 -0
- package/skills/ue-game-features/references/experience-system.md +222 -0
- package/skills/ue-game-features/references/game-feature-patterns.md +308 -0
- package/skills/ue-gameplay-abilities/SKILL.md +518 -0
- package/skills/ue-gameplay-abilities/references/ability-task-reference.md +391 -0
- package/skills/ue-gameplay-abilities/references/gameplay-effect-reference.md +397 -0
- package/skills/ue-gameplay-abilities/references/gas-setup-patterns.md +369 -0
- package/skills/ue-gameplay-framework/SKILL.md +466 -0
- package/skills/ue-gameplay-framework/references/framework-class-map.md +243 -0
- package/skills/ue-input-system/SKILL.md +394 -0
- package/skills/ue-input-system/references/cpp-setup.md +131 -0
- package/skills/ue-input-system/references/enhanced-input-workflow.md +117 -0
- package/skills/ue-input-system/references/input-action-reference.md +731 -0
- package/skills/ue-mass-entity/SKILL.md +476 -0
- package/skills/ue-mass-entity/references/mass-entity-patterns.md +319 -0
- package/skills/ue-mass-entity/references/mass-fragment-reference.md +119 -0
- package/skills/ue-materials-rendering/SKILL.md +526 -0
- package/skills/ue-materials-rendering/references/material-parameter-reference.md +216 -0
- package/skills/ue-materials-rendering/references/post-process-settings.md +383 -0
- package/skills/ue-module-build-system/SKILL.md +487 -0
- package/skills/ue-module-build-system/references/build-cs-reference.md +418 -0
- package/skills/ue-module-build-system/references/common-build-errors.md +381 -0
- package/skills/ue-naming-conventions/SKILL.md +142 -0
- package/skills/ue-naming-conventions/references/asset-name-modifiers.md +233 -0
- package/skills/ue-naming-conventions/references/content-structure.md +83 -0
- package/skills/ue-networking-replication/SKILL.md +452 -0
- package/skills/ue-networking-replication/references/replication-patterns.md +526 -0
- package/skills/ue-networking-replication/references/rpc-decision-guide.md +393 -0
- package/skills/ue-niagara-effects/SKILL.md +531 -0
- package/skills/ue-niagara-effects/references/niagara-data-interfaces.md +491 -0
- package/skills/ue-niagara-effects/references/niagara-parameter-types.md +153 -0
- package/skills/ue-niagara-effects/references/niagara-workflow.md +115 -0
- package/skills/ue-packaging/SKILL.md +116 -0
- package/skills/ue-physics-collision/SKILL.md +423 -0
- package/skills/ue-physics-collision/references/collision-channel-setup.md +218 -0
- package/skills/ue-physics-collision/references/trace-patterns.md +499 -0
- package/skills/ue-procedural-generation/SKILL.md +505 -0
- package/skills/ue-procedural-generation/references/pcg-node-reference.md +381 -0
- package/skills/ue-procedural-generation/references/procedural-mesh-patterns.md +940 -0
- package/skills/ue-project-context/SKILL.md +305 -0
- package/skills/ue-sequencer-cinematics/SKILL.md +506 -0
- package/skills/ue-sequencer-cinematics/references/sequencer-patterns.md +629 -0
- package/skills/ue-serialization-savegames/SKILL.md +524 -0
- package/skills/ue-serialization-savegames/references/save-system-architecture.md +705 -0
- package/skills/ue-state-trees/SKILL.md +489 -0
- package/skills/ue-state-trees/references/state-tree-mass-integration.md +184 -0
- package/skills/ue-state-trees/references/state-tree-patterns.md +321 -0
- package/skills/ue-testing-debugging/SKILL.md +520 -0
- package/skills/ue-testing-debugging/references/automation-test-patterns.md +387 -0
- package/skills/ue-testing-debugging/references/profiling-commands.md +357 -0
- package/skills/ue-ui-umg-slate/SKILL.md +431 -0
- package/skills/ue-ui-umg-slate/references/common-ui-setup.md +484 -0
- package/skills/ue-ui-umg-slate/references/widget-types.md +487 -0
- package/skills/ue-world-level-streaming/SKILL.md +518 -0
- package/skills/ue-world-level-streaming/references/streaming-patterns.md +414 -0
- package/templates/gamedev/unreal-project-context.md +107 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# Level Streaming Configuration Patterns
|
|
2
|
+
|
|
3
|
+
Reference patterns for common game types. Choose the pattern that matches your project's world structure and adapt the specifics to your scale and multiplayer requirements.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Pattern 1: Open World with World Partition (UE5)
|
|
8
|
+
|
|
9
|
+
**Best for:** Large continuous worlds — survival games, open-world RPGs, exploration games.
|
|
10
|
+
|
|
11
|
+
### Configuration
|
|
12
|
+
|
|
13
|
+
Enable World Partition on the persistent level. Every actor is managed by the WP grid. Sub-levels are not used.
|
|
14
|
+
|
|
15
|
+
Streaming radius is configured per-partition in the World Partition editor UI (`LoadingRange` on `URuntimePartition`). World Partition streaming is enabled per-world in the editor, not via ini.
|
|
16
|
+
|
|
17
|
+
### Data Layers for Dynamic Content
|
|
18
|
+
|
|
19
|
+
```cpp
|
|
20
|
+
// Assign actors to data layers in editor (details panel: Data Layers).
|
|
21
|
+
// At runtime, activate/deactivate via UDataLayerManager (UE 5.3+).
|
|
22
|
+
// NOTE: UDataLayerSubsystem is deprecated since UE 5.3 — use UDataLayerManager instead.
|
|
23
|
+
|
|
24
|
+
// Example: interior content (caves, buildings) only loads when player is near
|
|
25
|
+
#include "WorldPartition/DataLayer/DataLayerManager.h"
|
|
26
|
+
UDataLayerManager* DLMgr = UDataLayerManager::GetDataLayerManager(GetWorld());
|
|
27
|
+
|
|
28
|
+
// SetDataLayerRuntimeState takes UDataLayerAsset* (asset reference set up in editor)
|
|
29
|
+
UDataLayerAsset* InteriorLayerAsset = /* UPROPERTY asset reference */;
|
|
30
|
+
|
|
31
|
+
// Activate — loads and makes visible
|
|
32
|
+
DLMgr->SetDataLayerRuntimeState(InteriorLayerAsset, EDataLayerRuntimeState::Activated);
|
|
33
|
+
|
|
34
|
+
// Pre-load without making visible (background warm)
|
|
35
|
+
DLMgr->SetDataLayerRuntimeState(InteriorLayerAsset, EDataLayerRuntimeState::Loaded);
|
|
36
|
+
|
|
37
|
+
// Unload completely
|
|
38
|
+
DLMgr->SetDataLayerRuntimeState(InteriorLayerAsset, EDataLayerRuntimeState::Unloaded);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Streaming Radius per Player (Multiplayer)
|
|
42
|
+
|
|
43
|
+
World Partition automatically uses each player controller as a streaming source. Each controller can have a different runtime cell size. Override `IWorldPartitionStreamingSourceProvider` for non-player streaming sources (e.g., AI directors, cinematic cameras).
|
|
44
|
+
|
|
45
|
+
### HLOD Setup
|
|
46
|
+
|
|
47
|
+
1. Open World Partition editor panel.
|
|
48
|
+
2. Add HLOD layers: one per LOD tier (e.g., far, medium).
|
|
49
|
+
3. Set cell size to match grid (e.g., 128m cells = 12800 units).
|
|
50
|
+
4. Build HLODs before shipping: **Build -> Build World Partition HLODs**.
|
|
51
|
+
|
|
52
|
+
### Anti-Patterns for This Setup
|
|
53
|
+
|
|
54
|
+
- Do not use streaming volumes — they have no effect in World Partition.
|
|
55
|
+
- Do not add actors to persistent level outside of World Partition (they will always be loaded).
|
|
56
|
+
- Do not use `UGameplayStatics::LoadStreamLevel` — there are no named sub-levels to target.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Pattern 2: Hub-and-Spoke with Manual Sub-Level Streaming
|
|
61
|
+
|
|
62
|
+
**Best for:** Games with discrete zones (MMO zones, dungeon crawlers, hub worlds with portal travel).
|
|
63
|
+
|
|
64
|
+
### Persistent Level
|
|
65
|
+
|
|
66
|
+
The persistent level contains: GameMode, GameState, player spawn points, UI actors, and global managers. It never unloads during a session.
|
|
67
|
+
|
|
68
|
+
### Sub-Levels Setup
|
|
69
|
+
|
|
70
|
+
Each zone is a separate `.umap` added to the persistent level's streaming list in the editor. Zones are loaded on demand.
|
|
71
|
+
|
|
72
|
+
```cpp
|
|
73
|
+
// MyZoneManager.h — UWorldSubsystem for zone state tracking
|
|
74
|
+
UCLASS()
|
|
75
|
+
class UMyZoneManager : public UWorldSubsystem
|
|
76
|
+
{
|
|
77
|
+
GENERATED_BODY()
|
|
78
|
+
|
|
79
|
+
public:
|
|
80
|
+
void LoadZone(FName ZoneLevelName);
|
|
81
|
+
void UnloadZone(FName ZoneLevelName);
|
|
82
|
+
bool IsZoneLoaded(FName ZoneLevelName) const;
|
|
83
|
+
|
|
84
|
+
private:
|
|
85
|
+
TSet<FName> LoadedZones;
|
|
86
|
+
|
|
87
|
+
UFUNCTION()
|
|
88
|
+
void OnZoneLoaded() { /* update LoadedZones */ }
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```cpp
|
|
93
|
+
// MyZoneManager.cpp
|
|
94
|
+
void UMyZoneManager::LoadZone(FName ZoneLevelName)
|
|
95
|
+
{
|
|
96
|
+
FLatentActionInfo LatentInfo;
|
|
97
|
+
LatentInfo.CallbackTarget = this;
|
|
98
|
+
LatentInfo.ExecutionFunction = FName("OnZoneLoaded");
|
|
99
|
+
LatentInfo.Linkage = 0;
|
|
100
|
+
LatentInfo.UUID = GetTypeHash(ZoneLevelName);
|
|
101
|
+
|
|
102
|
+
UGameplayStatics::LoadStreamLevel(this, ZoneLevelName, true, false, LatentInfo);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
void UMyZoneManager::UnloadZone(FName ZoneLevelName)
|
|
106
|
+
{
|
|
107
|
+
FLatentActionInfo LatentInfo;
|
|
108
|
+
LatentInfo.CallbackTarget = this;
|
|
109
|
+
LatentInfo.ExecutionFunction = FName("OnZoneUnloaded");
|
|
110
|
+
LatentInfo.Linkage = 0;
|
|
111
|
+
LatentInfo.UUID = GetTypeHash(ZoneLevelName) + 1;
|
|
112
|
+
|
|
113
|
+
UGameplayStatics::UnloadStreamLevel(this, ZoneLevelName, LatentInfo, false);
|
|
114
|
+
LoadedZones.Remove(ZoneLevelName);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Pre-Loading Adjacent Zones
|
|
119
|
+
|
|
120
|
+
Load neighbor zones into `LoadedNotVisible` state so they are in memory before the player arrives:
|
|
121
|
+
|
|
122
|
+
```cpp
|
|
123
|
+
void UMyZoneManager::PreloadAdjacentZone(FName ZoneLevelName)
|
|
124
|
+
{
|
|
125
|
+
const TArray<ULevelStreaming*>& Levels = GetWorld()->GetStreamingLevels();
|
|
126
|
+
for (ULevelStreaming* Level : Levels)
|
|
127
|
+
{
|
|
128
|
+
if (Level->GetWorldAssetPackageFName() == ZoneLevelName)
|
|
129
|
+
{
|
|
130
|
+
Level->SetShouldBeLoaded(true);
|
|
131
|
+
Level->SetShouldBeVisible(false); // Load but keep invisible
|
|
132
|
+
// Changes are picked up by the streaming system each frame automatically
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Loading Screen Handoff
|
|
140
|
+
|
|
141
|
+
Before loading screen dismiss, wait for the target zone to reach `LoadedVisible`:
|
|
142
|
+
|
|
143
|
+
```cpp
|
|
144
|
+
void UMyHUD::WaitForZoneVisible(FName ZoneLevelName)
|
|
145
|
+
{
|
|
146
|
+
GetWorld()->GetTimerManager().SetTimer(
|
|
147
|
+
PollTimerHandle,
|
|
148
|
+
[this, ZoneLevelName]()
|
|
149
|
+
{
|
|
150
|
+
for (ULevelStreaming* Level : GetWorld()->GetStreamingLevels())
|
|
151
|
+
{
|
|
152
|
+
if (Level->GetWorldAssetPackageFName() == ZoneLevelName &&
|
|
153
|
+
Level->GetLevelStreamingState() == ELevelStreamingState::LoadedVisible)
|
|
154
|
+
{
|
|
155
|
+
DismissLoadingScreen();
|
|
156
|
+
GetWorld()->GetTimerManager().ClearTimer(PollTimerHandle);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
0.1f, // poll every 100ms
|
|
162
|
+
true // looping
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Pattern 3: Procedural / Instanced Level Streaming
|
|
170
|
+
|
|
171
|
+
**Best for:** Roguelikes, procedural dungeons, instanced arenas, modular buildings loaded at runtime.
|
|
172
|
+
|
|
173
|
+
### Core Pattern: ULevelStreamingDynamic
|
|
174
|
+
|
|
175
|
+
```cpp
|
|
176
|
+
// ProcDungeonGenerator.h
|
|
177
|
+
UCLASS()
|
|
178
|
+
class AProcDungeonGenerator : public AActor
|
|
179
|
+
{
|
|
180
|
+
GENERATED_BODY()
|
|
181
|
+
|
|
182
|
+
public:
|
|
183
|
+
// Room template level to instance (set in editor)
|
|
184
|
+
UPROPERTY(EditDefaultsOnly)
|
|
185
|
+
TSoftObjectPtr<UWorld> RoomTemplate;
|
|
186
|
+
|
|
187
|
+
void SpawnRoom(FTransform RoomTransform, FString InstanceName);
|
|
188
|
+
void DespawnRoom(FString InstanceName);
|
|
189
|
+
|
|
190
|
+
private:
|
|
191
|
+
UPROPERTY()
|
|
192
|
+
TMap<FString, TObjectPtr<ULevelStreamingDynamic>> SpawnedRooms;
|
|
193
|
+
|
|
194
|
+
UFUNCTION()
|
|
195
|
+
void OnRoomVisible();
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```cpp
|
|
200
|
+
// ProcDungeonGenerator.cpp
|
|
201
|
+
void AProcDungeonGenerator::SpawnRoom(FTransform RoomTransform, FString InstanceName)
|
|
202
|
+
{
|
|
203
|
+
ULevelStreamingDynamic::FLoadLevelInstanceParams Params(
|
|
204
|
+
GetWorld(),
|
|
205
|
+
RoomTemplate.GetLongPackageName(),
|
|
206
|
+
RoomTransform
|
|
207
|
+
);
|
|
208
|
+
Params.OptionalLevelNameOverride = &InstanceName; // Deterministic name for net sync
|
|
209
|
+
Params.bInitiallyVisible = true;
|
|
210
|
+
|
|
211
|
+
bool bSuccess = false;
|
|
212
|
+
ULevelStreamingDynamic* Level = ULevelStreamingDynamic::LoadLevelInstance(Params, bSuccess);
|
|
213
|
+
|
|
214
|
+
if (bSuccess && Level)
|
|
215
|
+
{
|
|
216
|
+
Level->OnLevelShown.AddDynamic(this, &AProcDungeonGenerator::OnRoomVisible);
|
|
217
|
+
SpawnedRooms.Add(InstanceName, Level);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
void AProcDungeonGenerator::DespawnRoom(FString InstanceName)
|
|
222
|
+
{
|
|
223
|
+
if (ULevelStreamingDynamic** Level = SpawnedRooms.Find(InstanceName))
|
|
224
|
+
{
|
|
225
|
+
(*Level)->SetShouldBeLoaded(false);
|
|
226
|
+
(*Level)->SetShouldBeVisible(false);
|
|
227
|
+
(*Level)->SetIsRequestingUnloadAndRemoval(true);
|
|
228
|
+
SpawnedRooms.Remove(InstanceName);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Multiplayer: Replicating Instance Names
|
|
234
|
+
|
|
235
|
+
The server spawns room instances with deterministic names (e.g., "Room_0001", "Room_0002"). It replicates these names to clients via a replicated array on GameState. Clients call `LoadLevelInstance` with the same `OptionalLevelNameOverride`. The names must match exactly — without this, clients and server reference different package names and streaming breaks.
|
|
236
|
+
|
|
237
|
+
```cpp
|
|
238
|
+
// MyGameState.h
|
|
239
|
+
UPROPERTY(ReplicatedUsing=OnRep_SpawnedRooms)
|
|
240
|
+
TArray<FString> SpawnedRoomNames;
|
|
241
|
+
|
|
242
|
+
UFUNCTION()
|
|
243
|
+
void OnRep_SpawnedRooms();
|
|
244
|
+
|
|
245
|
+
// MyGameState.cpp — client side
|
|
246
|
+
void AMyGameState::OnRep_SpawnedRooms()
|
|
247
|
+
{
|
|
248
|
+
for (const FString& RoomName : SpawnedRoomNames)
|
|
249
|
+
{
|
|
250
|
+
// Load each room with the replicated name as the override
|
|
251
|
+
// so the package name matches what the server has
|
|
252
|
+
DungeonGenerator->SpawnRoom(GetRoomTransform(RoomName), RoomName);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Pattern 4: Linear Level Sequence (Chapter / Act Structure)
|
|
260
|
+
|
|
261
|
+
**Best for:** Narrative games, linear action games with distinct chapters or missions.
|
|
262
|
+
|
|
263
|
+
### Approach
|
|
264
|
+
|
|
265
|
+
Each chapter is a separate `.umap`. A small persistent level holds global actors. Travel between chapters uses seamless travel (multiplayer) or `OpenLevel` (single-player).
|
|
266
|
+
|
|
267
|
+
### Single-Player: OpenLevel with GameInstance State
|
|
268
|
+
|
|
269
|
+
```cpp
|
|
270
|
+
// Save chapter progress to GameInstance before traveling
|
|
271
|
+
void AMyGameMode::TravelToChapter(FName ChapterMapName)
|
|
272
|
+
{
|
|
273
|
+
UMyGameInstance* GI = GetGameInstance<UMyGameInstance>();
|
|
274
|
+
if (GI)
|
|
275
|
+
{
|
|
276
|
+
GI->LastCompletedChapter = CurrentChapter;
|
|
277
|
+
GI->PlayerInventory = CollectPlayerInventory();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
UGameplayStatics::OpenLevel(this, ChapterMapName, true);
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Multiplayer: Seamless Travel
|
|
285
|
+
|
|
286
|
+
```ini
|
|
287
|
+
; DefaultEngine.ini
|
|
288
|
+
[/Script/Engine.GameMapsSettings]
|
|
289
|
+
TransitionMap=/Game/Maps/Transition_Loading
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
```cpp
|
|
293
|
+
// MyGameMode.h
|
|
294
|
+
uint32 bUseSeamlessTravel : 1; // set to 1 in constructor
|
|
295
|
+
|
|
296
|
+
// MyGameMode.cpp
|
|
297
|
+
void AMyGameMode::TravelToChapter(const FString& ChapterURL)
|
|
298
|
+
{
|
|
299
|
+
// Server initiates; clients follow automatically
|
|
300
|
+
GetWorld()->ServerTravel(ChapterURL);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
void AMyGameMode::GetSeamlessTravelActorList(bool bToTransition, TArray<AActor*>& ActorList)
|
|
304
|
+
{
|
|
305
|
+
Super::GetSeamlessTravelActorList(bToTransition, ActorList);
|
|
306
|
+
|
|
307
|
+
if (!bToTransition)
|
|
308
|
+
{
|
|
309
|
+
// Carry GameState into the destination
|
|
310
|
+
ActorList.Add(GameState);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
void AMyGameMode::HandleSeamlessTravelPlayer(AController*& C)
|
|
315
|
+
{
|
|
316
|
+
Super::HandleSeamlessTravelPlayer(C);
|
|
317
|
+
// Restore character state from GameInstance or GameState
|
|
318
|
+
RestorePlayerState(C);
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Pattern 5: Streaming Volume–Driven Interior Loading
|
|
325
|
+
|
|
326
|
+
**Best for:** Open world games with buildings or interiors that stream in as the player approaches.
|
|
327
|
+
|
|
328
|
+
### Setup
|
|
329
|
+
|
|
330
|
+
1. Create a sub-level per interior (e.g., `L_Building_Interior_01`).
|
|
331
|
+
2. Place an `ALevelStreamingVolume` in the persistent level surrounding the building exterior.
|
|
332
|
+
3. Set `StreamingUsage = SVB_LoadingAndVisibility` on the volume.
|
|
333
|
+
4. Assign the sub-level to the volume via the streaming level's `EditorStreamingVolumes` array (set in editor Details panel or via Levels panel).
|
|
334
|
+
5. Set `MinTimeBetweenVolumeUnloadRequests = 5.0` seconds on the sub-level to prevent unload flicker when the player stands near the volume boundary.
|
|
335
|
+
|
|
336
|
+
### Disabling Volume Control at Runtime (Cutscene or Boss Arena)
|
|
337
|
+
|
|
338
|
+
```cpp
|
|
339
|
+
void AMyBossRoomTrigger::BeginPlay()
|
|
340
|
+
{
|
|
341
|
+
Super::BeginPlay();
|
|
342
|
+
// Find the streaming level and disable volume-based streaming
|
|
343
|
+
for (ULevelStreaming* Level : GetWorld()->GetStreamingLevels())
|
|
344
|
+
{
|
|
345
|
+
if (Level->GetWorldAssetPackageFName() == FName("/Game/Levels/L_BossArena"))
|
|
346
|
+
{
|
|
347
|
+
// Disable volume control — we'll manage load state from code
|
|
348
|
+
Level->bDisableDistanceStreaming = true;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
void AMyBossRoomTrigger::NotifyActorBeginOverlap(AActor* OtherActor)
|
|
355
|
+
{
|
|
356
|
+
if (OtherActor->IsA<APlayerCharacter>())
|
|
357
|
+
{
|
|
358
|
+
// Manually load and show
|
|
359
|
+
for (ULevelStreaming* Level : GetWorld()->GetStreamingLevels())
|
|
360
|
+
{
|
|
361
|
+
if (Level->GetWorldAssetPackageFName() == FName("/Game/Levels/L_BossArena"))
|
|
362
|
+
{
|
|
363
|
+
Level->SetShouldBeLoaded(true);
|
|
364
|
+
Level->SetShouldBeVisible(true);
|
|
365
|
+
// Changes are picked up by the streaming system each frame automatically
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Dedicated Server Streaming Considerations
|
|
375
|
+
|
|
376
|
+
On a dedicated server, there is no rendering pipeline. Streaming must be entirely logic-driven.
|
|
377
|
+
|
|
378
|
+
- **World Partition**: streaming sources on the server must be explicit. Player controller positions are used by default if `APlayerController` is registered as a source.
|
|
379
|
+
- **Sub-levels**: call `SetShouldBeLoaded` and `SetShouldBeVisible` explicitly. Volume-based streaming is disabled (no camera player controller overlap in server-only mode without clients).
|
|
380
|
+
- **Avoid `bShouldBlockOnLoad`** on the server unless behind a map-change sequence — blocking the server stalls all clients.
|
|
381
|
+
|
|
382
|
+
```cpp
|
|
383
|
+
// Server-side: manually drive zone loading based on player positions
|
|
384
|
+
// Inherits UTickableWorldSubsystem (UWorldSubsystem has no Tick)
|
|
385
|
+
void UServerZoneSubsystem::Tick(float DeltaTime)
|
|
386
|
+
{
|
|
387
|
+
if (GetWorld()->GetNetMode() != NM_DedicatedServer) return;
|
|
388
|
+
|
|
389
|
+
for (APlayerController* PC : TActorRange<APlayerController>(GetWorld()))
|
|
390
|
+
{
|
|
391
|
+
FVector PlayerPos = PC->GetPawn() ? PC->GetPawn()->GetActorLocation() : FVector::ZeroVector;
|
|
392
|
+
FName TargetZone = DetermineZoneForPosition(PlayerPos);
|
|
393
|
+
if (!LoadedZones.Contains(TargetZone))
|
|
394
|
+
{
|
|
395
|
+
LoadZone(TargetZone);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Performance Benchmarks and Budget Guidelines
|
|
404
|
+
|
|
405
|
+
| Scenario | Recommended Cell/Zone Size | Max Simultaneous Loaded Levels |
|
|
406
|
+
|---|---|---|
|
|
407
|
+
| Open world (WP) | 128m–256m cells | N/A (grid-based) |
|
|
408
|
+
| Hub-and-spoke zones | Per zone (no fixed size) | 2–3 (current + neighbors) |
|
|
409
|
+
| Procedural rooms | Per room (10m–50m) | 10–30 depending on actor counts |
|
|
410
|
+
| Interior streaming | Per building | 3–5 around player |
|
|
411
|
+
|
|
412
|
+
**Actor counts per cell (World Partition):** Keep under 1000 dynamic actors per cell. Static meshes are merged by HLOD and do not count against this limit in distant cells.
|
|
413
|
+
|
|
414
|
+
**Loading budget per frame:** Level streaming operations are spread across frames. `bShouldBlockOnLoad` forces all operations into a single frame — only acceptable during loading screens. Async loading typically completes in 0.5–5 seconds depending on level size and disk speed.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Unreal Project Context: [Project Name]
|
|
2
|
+
|
|
3
|
+
> Lives at `docs/unreal/project-context.md`. Every `ue-*` skill reads this before acting, so
|
|
4
|
+
> keeping it accurate is what stops the advice from being generic. Generate or refresh it with
|
|
5
|
+
> `/ue-project-context`.
|
|
6
|
+
|
|
7
|
+
## Document Status
|
|
8
|
+
- **Version**: 1.0
|
|
9
|
+
- **Last Updated**: [Date]
|
|
10
|
+
- **Maintainer**: [Agent/Person]
|
|
11
|
+
- **Drafted from**: [codebase scan / interview / both]
|
|
12
|
+
|
|
13
|
+
## 1. Engine & Project Overview
|
|
14
|
+
|
|
15
|
+
| Field | Value |
|
|
16
|
+
|-------|-------|
|
|
17
|
+
| **Project name** | [Name] |
|
|
18
|
+
| **One-line description** | [What this is] |
|
|
19
|
+
| **Engine version** | [e.g. 5.4.4] |
|
|
20
|
+
| **Build kind** | [launcher build / source build] — decides where `.mcp.json` is written |
|
|
21
|
+
| **Project type** | [game / simulation / visualization / tool / plugin] |
|
|
22
|
+
| **Genre or domain** | [e.g. first-person shooter, architectural viz, training sim] |
|
|
23
|
+
| **Target platforms** | [Windows, Mac, Linux, PS5, Xbox, iOS, Android, VR] |
|
|
24
|
+
|
|
25
|
+
## 2. Module Structure
|
|
26
|
+
|
|
27
|
+
| Module | Type | Primary? | Public deps | Private deps |
|
|
28
|
+
|--------|------|----------|-------------|--------------|
|
|
29
|
+
| [Name] | [Runtime / Editor / Developer / ThirdParty] | [yes/no] | [...] | [...] |
|
|
30
|
+
|
|
31
|
+
- **Modules under active development**: [...]
|
|
32
|
+
- **Modules stable or locked**: [...]
|
|
33
|
+
- **Standalone plugins or shared libraries**: [...]
|
|
34
|
+
|
|
35
|
+
## 3. Plugin Dependencies
|
|
36
|
+
|
|
37
|
+
| Plugin | Source | Critical to gameplay? | Notes |
|
|
38
|
+
|--------|--------|-----------------------|-------|
|
|
39
|
+
| [e.g. GameplayAbilities] | [engine / Fab / in-house] | [yes/no] | [licence restrictions, version pins] |
|
|
40
|
+
|
|
41
|
+
Engine plugins to record if enabled: GameplayAbilities, EnhancedInput, CommonUI, Niagara, PCG,
|
|
42
|
+
MetaSounds, Chaos, OnlineSubsystem, MassEntity, StateTree, ModelContextProtocol + AllToolsets.
|
|
43
|
+
|
|
44
|
+
## 4. Coding & Asset Conventions
|
|
45
|
+
|
|
46
|
+
### Asset naming
|
|
47
|
+
|
|
48
|
+
- **Pattern**: [`Prefix_BaseAssetName_Variant_Suffix`, or the project's own]
|
|
49
|
+
- **Static Mesh prefix**: [`SM_` or `S_` — the two common conventions disagree; pick one]
|
|
50
|
+
- **Structs / enums authored in Blueprint**: [`F`/`E` like C++, or `F_`/`E_` to distinguish]
|
|
51
|
+
- **Texture packing order**: [e.g. `_RAM` = Roughness/AO/Metallic in RGB]
|
|
52
|
+
- **Prefix collisions resolved**: [e.g. `Submix_` vs `SM_`, `GEOC_` vs `GC_`]
|
|
53
|
+
- **Top-level content folder**: [`Content/MyProject/`]
|
|
54
|
+
- **`Developers/` in use?**: [yes/no]
|
|
55
|
+
|
|
56
|
+
> Full prefix tables live in `/ue-naming-conventions`. Record here only what this project
|
|
57
|
+
> decided or does differently.
|
|
58
|
+
|
|
59
|
+
### Code conventions
|
|
60
|
+
|
|
61
|
+
- **Naming prefixes**: [Epic standard `F`/`U`/`A`/`E`/`I`, plus any exceptions]
|
|
62
|
+
- **Header guards**: [`#pragma once` / traditional]
|
|
63
|
+
- **Log categories**: [`DEFINE_LOG_CATEGORY` names used most]
|
|
64
|
+
- **C++ vs Blueprint boundary**: [where the line sits, and who decides]
|
|
65
|
+
- **Formatting / linting**: [clang-format config, .editorconfig, enforcement]
|
|
66
|
+
- **Anything that deviates from Epic's conventions**: [and why]
|
|
67
|
+
|
|
68
|
+
> Record what the codebase **actually does**, not what it aspires to. These lines are what the
|
|
69
|
+
> `ue-*` skills follow.
|
|
70
|
+
|
|
71
|
+
## 5. Subsystems in Use
|
|
72
|
+
|
|
73
|
+
| Subsystem | In use? | How it is used here |
|
|
74
|
+
|-----------|---------|---------------------|
|
|
75
|
+
| Gameplay Ability System | [yes/no] | [scope: all combat? just abilities?] |
|
|
76
|
+
| Enhanced Input | [yes/no] | [mapping context strategy] |
|
|
77
|
+
| CommonUI | [yes/no] | [activatable stacks, input routing] |
|
|
78
|
+
| Replication / multiplayer | [yes/no] | [server-authoritative? listen server? dedicated?] |
|
|
79
|
+
| World Partition / level streaming | [yes/no] | [data layers, streaming sources] |
|
|
80
|
+
| Niagara | [yes/no] | [budget expectations] |
|
|
81
|
+
| Mass Entity / StateTree | [yes/no] | [what runs at scale] |
|
|
82
|
+
| Save system | [yes/no] | [versioning approach] |
|
|
83
|
+
|
|
84
|
+
## 6. Build Configuration
|
|
85
|
+
|
|
86
|
+
- **Target types built**: [Game, Editor, Server, Client]
|
|
87
|
+
- **Configurations used**: [Development, Shipping, Test, DebugGame]
|
|
88
|
+
- **Game Default Map**: [map path — the usual cause of a black packaged build]
|
|
89
|
+
- **Packaging**: [maps/directories cooked, compression, full rebuild policy]
|
|
90
|
+
- **CI**: [does `RunUAT BuildCookRun` run automatically? where?]
|
|
91
|
+
- **Platform SDKs installed**: [...]
|
|
92
|
+
- **Performance targets**: [target FPS, frame budget, platform floor]
|
|
93
|
+
|
|
94
|
+
## 7. Team Context (Optional)
|
|
95
|
+
|
|
96
|
+
- **Team size and roles**: [...]
|
|
97
|
+
- **Source control**: [Perforce / Git + LFS / other]
|
|
98
|
+
- **Review expectations**: [what needs a second pair of eyes]
|
|
99
|
+
- **Definition of done**: [tests? functional test? packaged build verified?]
|
|
100
|
+
- **Known pain points**: [what keeps breaking]
|
|
101
|
+
|
|
102
|
+
## Open Questions
|
|
103
|
+
|
|
104
|
+
- [Anything the scan could not determine and nobody has confirmed yet]
|
|
105
|
+
|
|
106
|
+
> **Rule**: an unanswered item here is an item the `ue-*` skills will have to ask about. Closing
|
|
107
|
+
> them is what makes the rest of the workflow fast.
|