Game Classes

GameMode in Fusion

Every client constructs its own AGameMode locally. Fusion is distributed-authority, not server-only, so there is no single peer that runs the GameMode "for real" while everyone else proxies it. Each peer's AGameMode is a full Unreal instance from the engine's point of view, and all the standard hooks (HandleMatchHasStarted, HandleStartingNewPlayer, RestartGame) fire locally on every peer.

This differs from stock Unreal where only the dedicated server runs the GameMode. The shared-authority model and what "authority" means under Fusion are covered in Ownership.

C++

void AMyGameMode::RestartGame()
{
    if (!OnlineSubsystem->IsMasterClient()) return;
    Super::RestartGame();
}

Gate match-flow code (RestartGame, HandleMatchHasStarted, login hooks) on UFusionOnlineSubsystem::IsMasterClient() so it does not fire on every peer. Without the gate, the same logic runs once per client and effects multiply by player count — exactly the behaviour that the stock UE "server-only" rule prevents in dedicated-server projects.

GameState and Master Client Authority

The GameState is networked automatically. UFusionClient auto-attaches a UFusionActorComponent to any AGameStateBase with Ownership = EFusionObjectOwnerFlags::MasterClient, so only the Master Client's writes propagate to other peers — and the ownership auto-transfers to the new Master Client on master migration without application code.

UFusionClient::UpdateGameState() runs each tick and patches ReplicatedWorldTimeSecondsDouble and ReplicatedWorldTimeSeconds on the GameState from UFusionOnlineSubsystem::NetworkTime() via reflection. The patch path means projects can use stock AGameStateBase without a Fusion-specific subclass — the world-time fields stay synchronised automatically.

For projects that need finer control over which GameState properties Fusion syncs, register a custom UFusionGameStateDescriptorBuilder. The builder runs at descriptor-build time and decides which FProperty entries on the GameState participate in the networked descriptor.

C++

UCLASS()
class UMyGameStateDescriptorBuilder : public UFusionGameStateDescriptorBuilder
{
    GENERATED_BODY()
    // Override CreateDescriptor to customise which properties are synced.
};

// In project module startup:
UFusionTypeLookup::RegisterTypeBuilder(
    AMyGameState::StaticClass(),
    UMyGameStateDescriptorBuilder::StaticClass());

The DisableGameStateNetworking CVar

Fusion.DisableGameStateNetworking is a console variable declared in FusionCVars.h (as FusionCVars::DisableGameStateNetworking) that opts the GameState out of the auto-attach path entirely.

When set to true, UFusionClient skips the MasterClient UFusionActorComponent install on AGameStateBase, skips UpdateGameState(), and routes the GameState through AttachGlobalInstanceActor instead. The GameState still exists, but Fusion does not manage its replication.

C++

// In project module startup, before UFusionOnlineSubsystem::Initialize runs:
IConsoleManager::Get()
    .FindConsoleVariable(TEXT("Fusion.DisableGameStateNetworking"))
    ->Set(1);

Flip this on when the project ships its own hand-rolled GameState replication and wants Fusion to stay out of the way. The common case where projects need it: a custom GameState that participates in replication through bespoke channels and would conflict with Fusion's auto-attach.

MatchState Replication and the Local-Bogus-State Race

Each client's local AGameMode sets its own initial MatchState during map load — before Fusion has replicated anything. The value is briefly wrong on non-master clients: the local GameMode thinks the match has just started fresh, but the room is already mid-match.

Once UFusionClient::UpdateGameState() ticks, non-master clients mirror AGameState::GetMatchState() onto the local AGameMode's MatchState field directly. The mirror bypasses AGameMode::SetMatchState deliberately, to avoid re-firing server-only handlers like HandleMatchHasStarted on every client (which would otherwise multiply gameplay side-effects by player count).

C++

void AMyGameState::OnRep_MatchState(FName OldMatchState)
{
    if (OldMatchState == MatchState) return; // Bogus initial state — skip.
    HandleMatchStateChanged(OldMatchState, MatchState);
}

PlayerState and the InactivePlayerArray

UFusionClient auto-attaches a UFusionActorComponent with Ownership = EFusionObjectOwnerFlags::PlayerAttached to every APlayerState. The PlayerState's lifetime follows the owning Fusion player: it is created when the player joins and destroyed when the player leaves.

Disconnected players land in the standard AGameMode::InactivePlayerArray as in stock Unreal networking. The carryover state — score, team assignment, any other replicated property on the PlayerState — survives across a rejoin within the PlayerTTL window configured on the room (see Connection).

The exact correlation mechanism between a returning player and their preserved PlayerState is governed by Fusion's player-object lifecycle, not by the stock-UE address lookup. Verify project-specific behaviour for the rejoin path against the PlayerTTL setting and the Ownership rules for PlayerAttached objects.

GameInstance is Process-Local

UGameInstance is not networked under Fusion. It is process-local and has no Fusion identity — properties on it do not replicate, and RPCs cannot be defined on it. This is the same as stock Unreal: UGameInstance lives one-per-process and is never replicated.

Specific UGameInstanceSubsystem instances — notably UFusionOnlineSubsystem — participate in Fusion, but they do so by managing a UFusionClient directly, not by replicating any field on the UGameInstance itself.

Do not put gameplay state you expect to share on UGameInstance. Put it on a networked actor with a UFusionActorComponent or on the GameState, and read Replicated Properties for the actual sync path.

The Fusion Online Subsystem on the GameInstance

UFusionOnlineSubsystem is a UGameInstanceSubsystem reached via GameInstance->GetSubsystem<UFusionOnlineSubsystem>(). It owns the UFusionClient and UFusionRealtimeClient for the whole process and is the entry point for every connect, join, and room-management call.

The subsystem exposes the room/session flow (ConnectToPhoton, JoinOrCreateRoom, ChangeWorld, StopFusionSession), the status queries (IsConnected, IsInRoom, IsMasterClient, Status, GetLocalPlayerId), and the lifecycle delegates (OnConnectedToPhoton, OnMapLoadRequested, OnMapLoadPerform, OnMapLoadDone).

C++

UFusionOnlineSubsystem* Subsystem =
    GetGameInstance()->GetSubsystem<UFusionOnlineSubsystem>();

The full connect-and-join sequence is in Connection, and the map-change flow — including how ChangeWorld interacts with the Fusion.LoadMapBehaviourOverride CVar — is in Map Changes.

PlayerController and Pawn

APlayerController and APawn use standard Unreal behaviour. Each client runs the normal HandleStartingNewPlayer / RestartPlayer chain locally and spawns its own APlayerController and pawn — no MasterClient gating is needed in this path. Possession follows the usual APlayerController::Possess flow.

C++

void AMyPlayerController::LeaveToMainMenu()
{
    UFusionHelpers::FusionClientTravel(
        this, FName("MainMenu"), /*bAbsolute=*/true, FString());
}

Use UFusionHelpers::FusionClientTravel for non-master client travel and UFusionOnlineSubsystem::ChangeWorld from the Master Client when reseating controllers across a level change. The reasoning and the room-wide vs. local-only distinction are in Map Changes.

Back to top