quodsoler/unreal-engine-skills2 files

Ue World Level Streaming

Use this skill when working with World Partition, level streaming, level travel, OpenLevel, ServerTravel, data layer, world subsystem, level instance, sub-level, seamless travel, open world, or HLOD. See references/streaming-patterns.md for configuration patterns by game type.

Specification
Skill ID
quodsoler/unreal-engine-skills/ue-world-level-streaming
Publisher
quodsoler
Repository
unreal-engine-skills
Installs
0
Files
2
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

quodsoler/unreal-engine-skills/ue-world-level-streamingInstalls these files
  • SKILL.md
  • references/streaming-patterns.md

What this skill tells the agent

UE World & Level Streaming

You are an expert in Unreal Engine's world management and level streaming systems.


Context

Read .agents/ue-project-context.md before advising. Pay attention to:

  • Engine version — World Partition is UE5 only; sub-level streaming works in both UE4 and UE5.
  • Build targets — Dedicated server has no rendering-driven streaming; streaming must be server-safe.
  • World size — Determines whether World Partition or manual sub-level streaming is appropriate.
  • Multiplayer — Seamless travel requirements and per-player streaming radius.

Information to Gather

Before recommending a streaming approach, confirm:

  1. World size and type: Is this an open world (World Partition), a set of discrete levels, or a hub-and-spoke map?
  2. Multiplayer: Are you running a dedicated server? Are per-player streaming radii needed?
  3. Streaming control: Does gameplay code need to control load/unload explicitly, or should proximity drive it?
  4. Level travel: Non-seamless (lobby flows), seamless (multiplayer round transitions), or no travel?
  5. Persistent data: What must survive a level transition — player state, inventory, session state?

World Partition (UE5)

Enabling World Partition

Enable via the Level menu: World -> World Partition -> Convert Level. Once enabled, all actors in the level are managed by World Partition's grid. The level can no longer have traditional sub-levels. Use One File Per Actor (OFPA) for collaborative editing: each actor is saved as its own .uasset under __ExternalActors__.

Runtime Data Layers

Data layers replace the old sub-level toggle pattern. A runtime data layer can be loaded/unloaded at runtime without traveling to a new map.

// MyGameMode.cpp
#include "WorldPartition/DataLayer/DataLayerManager.h"

void AMyGameMode::ActivateDungeonDataLayer()
{
    UDataLayerManager* DLMgr = UDataLayerManager::GetDataLayerManager(GetWorld());
    if (!DLMgr) return;

    // Get by asset reference (set up in editor as a UDataLayerAsset)
    UDataLayerAsset* DungeonLayer = DungeonDataLayerAsset.LoadSynchronous();
    DLMgr->SetDataLayerRuntimeState(DungeonLayer, EDataLayerRuntimeState::Activated);
}

void AMyGameMode::DeactivateDungeonDataLayer()
{
    UDataLayerManager* DLMgr = UDataLayerManager::GetDataLayerManager(GetWorld());
    if (!DLMgr) return;

    UDataLayerAsset* DungeonLayer = DungeonDataLayerAsset.LoadSynchronous();
    DLMgr->SetDataLayerRuntimeState(DungeonLayer, EDataLayerRuntimeState::Unloaded);
}

Data layer states:

  • Unloaded — not loaded, not visible.
  • Loaded — loaded into memory, not visible (pre-warming).
  • Activated — loaded and visible (fully active).

Streaming Sources

Each player controller is a streaming source by default. For custom sources (cinematic cameras, AI directors), implement IWorldPartitionStreamingSourceProvider.

HLOD

HLOD provides distant merged-mesh representations of World Partition cells. Configure HLOD layers in the World Partition editor; build before shipping via Build -> Build World Partition HLODs. Without HLOD, content beyond the streaming radius is simply absent.

Converting Sub-Levels to World Partition

Use Tools -> World Partition -> Convert Level. Actors migrate into the persistent level under WP management. Audit cross-level references beforehand — hard references to converted actors become invalid.

World Partition and Multiplayer

In a multiplayer session, each player controller acts as a streaming source with a configurable radius. The server streams based on server-side sources; clients receive visibility updates via AServerStreamingLevelsVisibility. On dedicated servers, rendering-based streaming does not apply — streaming is driven by server-side sources only.

Streaming radius is configured per-partition in the World Partition editor UI (LoadingRange on URuntimePartition), not via ini.


Level Streaming (Manual Sub-Levels)

ULevelStreaming State Machine

From LevelStreaming.h, the full state sequence is:

Removed -> Unloaded -> Loading -> LoadedNotVisible -> MakingVisible -> LoadedVisible -> MakingInvisible -> LoadedNotVisible
                                      |
                                 FailedToLoad   (check logs; level asset missing or corrupt)

Query state with:

ULevelStreaming* StreamingLevel = /* ... */;
ELevelStreamingState State = StreamingLevel->GetLevelStreamingState();

switch (State)
{
    case ELevelStreamingState::Unloaded:         /* not in memory */ break;
    case ELevelStreamingState::Loading:          /* async load in progress */ break;
    case ELevelStreamingState::LoadedNotVisible: /* in memory, not rendered */ break;
    case ELevelStreamingState::MakingVisible:    /* adding to world */ break;
    case ELevelStreamingState::LoadedVisible:    /* fully active */ break;
    case ELevelStreamingState::MakingInvisible:  /* removing from rendering */ break;
    case ELevelStreamingState::FailedToLoad:     /* check logs */ break;
}

UGameplayStatics: LoadStreamLevel / UnloadStreamLevel

For Blueprint-friendly async streaming with latent actions (from GameplayStatics.h):

// MyActor.cpp — async load using FLatentActionInfo
#include "Kismet/GameplayStatics.h"

void AMyActor::StreamInRoom(FName LevelName)
{
    FLatentActionInfo LatentInfo;
    LatentInfo.CallbackTarget = this;
    LatentInfo.ExecutionFunction = FName("OnRoomLoaded");
    LatentInfo.Linkage = 0;
    LatentInfo.UUID = GetUniqueID();

    UGameplayStatics::LoadStreamLevel(
        this,           // WorldContextObject
        LevelName,      // e.g., FName("Room_01")
        true,           // bMakeVisibleAfterLoad
        false,          // bShouldBlockOnLoad — keep false for async
        LatentInfo
    );
}

UFUNCTION()
void AMyActor::OnRoomLoaded()
{
    // Room is now loaded and visible
}

void AMyActor::StreamOutRoom(FName LevelName)
{
    FLatentActionInfo LatentInfo;
    LatentInfo.CallbackTarget = this;
    LatentInfo.ExecutionFunction = FName("OnRoomUnloaded");
    LatentInfo.Linkage = 0;
    LatentInfo.UUID = GetUniqueID() + 1;

    UGameplayStatics::UnloadStreamLevel(
        this,
        LevelName,
        LatentInfo,
        false // bShouldBlockOnUnload
    );
}

For soft object pointers (preferred for packaging safety), use LoadStreamLevelBySoftObjectPtr with the same arguments.

ULevelStreamingDynamic: Runtime Level Instances

Use ULevelStreamingDynamic::LoadLevelInstance to load the same level package multiple times at different transforms — for procedural dungeons, modular buildings, or instanced rooms (from LevelStreamingDynamic.h):

#include "Engine/LevelStreamingDynamic.h"

void AMyDungeonGenerator::SpawnRoom(FVector Location, FRotator Rotation)
{
    bool bSuccess = false;
    ULevelStreamingDynamic* StreamingLevel = ULevelStreamingDynamic::LoadLevelInstance(
        this,                              // WorldContextObject
        TEXT("/Game/Levels/Room_Corridor"), // LongPackageName — full path
        Location,
        Rotation,
        bSuccess
    );

    if (bSuccess && StreamingLevel)
    {
        // Bind to delegate to know when visible
        StreamingLevel->OnLevelShown.AddDynamic(this, &AMyDungeonGenerator::OnRoomShown);
        StreamingLevel->OnLevelHidden.AddDynamic(this, &AMyDungeonGenerator::OnRoomHidden);

        LoadedRooms.Add(StreamingLevel);
    }
}

void AMyDungeonGenerator::UnloadRoom(ULevelStreamingDynamic* StreamingLevel)
{
    if (StreamingLevel)
    {
        StreamingLevel->SetShouldBeLoaded(false);
        StreamingLevel->SetShouldBeVisible(false);
        StreamingLevel->SetIsRequestingUnloadAndRemoval(true);
    }
}

Use this skill in a project

Open any RiverX project, search the Skills panel for quodsoler/unreal-engine-skills/ue-world-level-streaming, and install it in one click.

Use this skill