Level Streaming
How Fusion Interacts with Unreal Level Streaming
The plugin subscribes to FWorldDelegates::LevelAddedToWorld and LevelRemovedFromWorld and forwards both through UFusionOnlineSubsystem::OnLevelAdded and OnLevelRemoved to UFusionClient. The plugin does not decide which sub-levels to stream — it only observes the events and reacts to whatever level set the game has chosen.
On every sub-level add, UFusionClient::OnLevelAdded walks Level->Actors and registers every actor that is bNetStartup and whose IsFullNameStableForNetworking() returns true. These actors land in the client's MapActors map, keyed by UFusionHelpers::SafePathNameHash. The hash is what the room uses to identify the actor across peers — two clients computing the same hash for the same actor name agree on its networked identity.
C++
void AArenaCoordinator::BeginPlay()
{
Super::BeginPlay();
if (!OnlineSubsystem->IsMasterClient()) return;
FLatentActionInfo Latent;
UGameplayStatics::LoadStreamLevel(
this, FName("SubLevel_Boss"),
/*MakeVisible=*/true,
/*bShouldBlockOnLoad=*/false,
Latent);
}
The game is responsible for calling LoadStreamLevel and UnloadStreamLevel (or the world-partition equivalent). Fusion only observes the result and binds replicated identities accordingly. The plugin does not stream levels on its own and does not implicitly coordinate streaming across peers.
World Partition and Fusion
The plugin includes WorldPartition/WorldPartitionReplay.h and WorldPartition/DataLayer/WorldDataLayers.h for interoperability, but it does not drive partition cell loading itself. Partition streaming runs on Unreal's standard pipeline, the same way it would in a non-Fusion project.
Because Fusion is the transport — and there is no replication-server to ack the standard make-visible / make-invisible transactions — the plugin applies bSkipClientUseMakingVisibleTransactionRequest and bSkipClientUseMakingInvisibleTransactionRequest to every ULevelStreaming. Without those flags, UE would wait on an ack that never arrives. The relevant code lives in ApplyFusionStreamingSkipFlags in FusionClient.cpp.
Fusion's MasterClient-attaches-current-map model assumes a shared persistent level. Partition-driven workflows where different clients see different cells are project-specific and must be designed around that constraint. When consistency between clients matters — for example, ensuring all peers have loaded the same boss arena before a fight begins — coordinate any partition activation through replicated state on a shared actor (typically MasterClient-owned).
Persistent vs Streamed Replicated Actors
Map actors — those with bNetStartup and a stable networking name — are eligible regardless of whether they live in the persistent level or a streamed sub-level. The hash function mixes the sub-level's LevelTransform.GetLocation() into the key for sub-level actors, so duplicate instances of the same sub-level placed at different origins do not collide on identity.
| Persistent-level actor | Streamed sub-level actor | |
|---|---|---|
| When registered | AttachCurrentMap after joining the room |
OnLevelAdded when the sub-level streams in |
| When unregistered | Map change unloads the persistent world | OnLevelRemoved when the sub-level streams out |
| Hash inputs | Actor FName only |
Actor FName + quantized sub-level origin |
| Removal trigger | ChangeWorld |
UnloadStreamLevel |
Dynamically spawned AActors with a UFusionActorComponent follow the spawn/destroy path covered in Spawning, not the level-add/level-remove path. They are unaffected by OnLevelAdded and OnLevelRemoved — their lifetime is governed by their ownership mode, not by the sub-level they were spawned into.
All Clients Must Have the Same Persistent Level
The map-change handshake assumes a shared persistent level. The room-wide ChangeWorld / ChangeWorldByName flow brings every client onto the same CurrentActiveWorld — see Map Changes for the flow.
Differences between clients must live in sub-levels loaded on top of a shared persistent base, not in the persistent level itself. If clients have different persistent levels, the Master-Client-driven AttachCurrentMap walk will not match remote MapActors entries — and bNetStartup actors will be invisible across peers.
Coordinating Sub-Level Streaming Across Clients
The plugin does not replicate LoadStreamLevel and UnloadStreamLevel calls. The LevelAddedToWorld and LevelRemovedFromWorld events react locally per client and add/remove entries in UFusionClient::MapActors. Each client decides independently which sub-levels to stream — there is no automatic "every peer loads the same sub-levels" guarantee.
If every client must load the same sub-level, the game has to coordinate that itself. The two common patterns are: an RPC routed through RPC Basics telling every client to load the sub-level, or a replicated property on a MasterClient-owned actor that triggers a local LoadStreamLevel via its OnRep handler.
C++
UPROPERTY(ReplicatedUsing=OnRep_ArenaSubLevelActive)
bool bArenaSubLevelActive = false;
UFUNCTION()
void OnRep_ArenaSubLevelActive()
{
FLatentActionInfo Latent;
if (bArenaSubLevelActive)
UGameplayStatics::LoadStreamLevel(this, FName("Arena"), true, false, Latent);
else
UGameplayStatics::UnloadStreamLevel(this, FName("Arena"), Latent, false);
}
Partial overlap is a supported state. Networked actors that live inside a sub-level only have local registrations on clients that have streamed the sub-level in. A client without the sub-level loaded has no MapActors entry for those actors and does not receive updates for them — they exist in the room but not on that client. Streaming the level in retroactively creates the registrations and resumes updates.
The LoadMapBehaviourOverride CVar
Fusion.LoadMapBehaviourOverride is a console variable declared in FusionCVars.h and registered in FusionOnlineSubsystem.cpp. It overrides the project-settings UFusionOnlineSubsystemSettings::LoadMapAutomatically flag at runtime:
| Value | Behaviour |
|---|---|
| 0 | Use the project-settings LoadMapAutomatically value. |
| 1 | Force auto-load — Fusion loads the next map automatically. |
| 2 | Disable auto-load — the game must drive the travel itself via the OnMapLoadPerform delegate. |
C++
// In project module startup, before Fusion is initialised:
IConsoleManager::Get()
.FindConsoleVariable(TEXT("Fusion.LoadMapBehaviourOverride"))
->Set(2);
// In a custom GameInstance:
void UMyGameInstance::HandleMapLoadPerform(FString MapName)
{
LoadingScreen->AddToViewport();
UGameplayStatics::OpenLevel(this, FName(*MapName));
}
The CVar only affects the persistent-level switch performed by Map Changes. Sub-level streaming is unaffected — LoadStreamLevel and UnloadStreamLevel are driven directly by the game and do not consult this CVar.