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 get hashed and stored in UFusionClient. 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

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 Level Actors

Level 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. These actors will be registered with Fusion usually when they have their BeginPlay functions called. Alternatively when AttachCurrentMap is called.

Level actors rely on deterministic hashing to work. The hash function uses the level's name and the index of the actor in the level to create a unique hash. For sub-levels it is important to define a OptionalLevelNameOverride, this behaviour is the same as in normal Unreal networking where a name override is required.

Sometimes certain level actors have been destroyed remotely when joining a game world, fusion will attempt to destroy these actors either inside UFusionClient::OnMapActorDestroyedRemote or store them so that when they processed in AttachMapActor they will be destroyed. This way the clients local state matches that of the rest of the players.

Runtime Actors

Dynamically spawned AActors with a UFusionActorComponent follow the spawn/destroy path covered in Spawning.

All Clients Must Have the Same Persistent Level (World)

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.

Each client can have its own set of sub-levels or level instances loaded. Fusion does not enforce a client's level state, however for replication to work the client needs to be in the same persistent-level (world) as the MasterClient.

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 the local actor cache. 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);


void UMyGameInstance::HandleMapLoadPerform(FString MapName)
{
    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.

Back to top