Implement Persistent Level Streaming in Unreal Engine Using World Composition
Learn how to divide a large world into sublevels that load and unload based on player proximity, reducing memory usage and improving frame rates.
19 Feb 2026, 13:46 UTC

Desired outcome
\nEnable seamless streaming of large worlds by dividing them into sublevels that are loaded and unloaded at runtime based on player proximity. This reduces memory usage, improves frame rates, and keeps only the necessary geometry active.
\nPrerequisites
\n- \n
- Unreal Engine 4.27 or later (UE5 Early Access also supported). \n
- A project with World Composition enabled (
Edit → Project Settings → Engine → World Composition). \n - Sublevels created in the Levels panel (right‑click →
Add Level → Create New Level). \n - Basic familiarity with Blueprint visual scripting or C++. \n
- Player start placed in a persistent level that will remain always loaded. \n
Procedure
\n- \n
- \nEnable World Composition\n
- \n
- Open
Edit → Project Settings. \n - Navigate to
Engine → World Compositionand check Enable World Composition. \n - Restart the editor if prompted. \n
\n - Open
- \nCreate and configure sublevels\n
- \n
- In the Levels panel, right‑click the persistent level and choose
Add Level → Create New Levelfor each streaming region. \n - Select each new level, open its Details panel, and set Streaming Method to either
Level Streaming VolumeorBlueprint(choose one method consistently for simplicity). \n - If using
Level Streaming Volume, place aLevel Streaming Volumeactor in the level and adjust its bounds to cover the intended streaming area. \n
\n - In the Levels panel, right‑click the persistent level and choose
- \nSet up player‑proximity triggers\n
- \n
- Open the persistent level (or a dedicated Blueprint) and add a
Box Collisioncomponent where you want the trigger to exist (e.g., around the player start). \n - Set the collision to generate overlap events (
Collision → Generate Overlap Eventsenabled). \n - In the Blueprint graph, add: \n
On Actor BeginOverlap→Load Stream Levelnode. \nOn Actor EndOverlap→Unload Stream Levelnode. \n- For each node, set the Level Name to the exact name of the sublevel you created (case‑sensitive). \n
- Optionally enable Make Visible After Load and Should Be Loaded on the nodes to control visibility. \n
- \n
\n - Open the persistent level (or a dedicated Blueprint) and add a
- \nAlternative C++ approach\n
If you prefer C++, implement the same logic in your
\nAPlayerControlleror a customAActor:
\nvoid AMyStreamingActor::BeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,\n UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)\n{\n UGameplayStatics::LoadStreamLevel(this, FName(TEXT(\"Sublevel_01\")), true, true, FLatentActionInfo());\n}\n\nvoid AMyStreamingActor::EndOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,\n UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)\n{\n UGameplayStatics::UnloadStreamLevel(this, FName(TEXT(\"Sublevel_01\")), FLatentActionInfo());\n}\nReplace
\nSublevel_01with your level’s name. \n - \nSet persistent gameplay objects\n
Ensure that the player controller, game mode, HUD, and any objects that must survive streaming are placed in the persistent level, not inside any streamed sublevel.
\n \n
Expected checks
\n- \n
- While playing in the editor, open the World Outliner and watch the eye icon next to each sublevel: it should appear when the player enters its volume and disappear when exiting. \n
- Use the console command
stat streamingto see memory allocated for streaming levels; values should drop when levels are unloaded. \n - Run
show collisionto verify that only the currently loaded sublevels have collision rendered. \n - Check the Output Log for any warnings like
Warning: Failed to load stream level; if present, verify that the level’s package is cooked and referenced inDefaultEngine.iniunder[Streaming]. \n
Recovery options
\nIf a sublevel fails to load or unload correctly:
\n- \n
- Manually toggle its visibility in the World Outliner to force a reload. \n
- Restart the play session; the persistent level will reset all streaming states. \n
- Review the Blueprint or C++ overlap logic for missing references or incorrect level names. \n
Limitations and practical verification
\n- \n
- Streaming volumes only work with simple axis‑aligned boxes or shapes; complex geometry may require multiple volumes or Blueprint‑based distance checks. \n
- Actors with hard references (e.g., direct variable pointers) to objects in a streamed level will cause those objects to persist after unload, leading to memory leaks. Use weak pointers or event‑based communication to avoid this. \n
- To confirm that memory usage actually drops, run
memreport -fullbefore entering a volume and after exiting; compare theTotal allocated memoryvalues. A successful stream should show a noticeable reduction proportional to the size of the unloaded level(s). \n
\n
By following these steps you can implement persistent level streaming that adapts to player movement, keeping your world large yet performant. Adjust the size and placement of streaming volumes to match your level design, and always verify with the provided checks before shipping.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.