Breaking down the architecture and loading handshake of an open-source Godot 4 multiplayer lobby template designed to fix scene transition desyncs.
Raul G
February 19, 2026
If you've spent any time working with Godot 4's high-level multiplayer API, you know that getting basic movement synced across a network is relatively painless. But the moment you try to hot-swap scenes—like moving players from a staging lobby into the actual game map—things get messy.
Out of the box, Godot's MultiplayerSpawner and MultiplayerSynchronizer nodes are incredibly powerful, but they have a fatal flaw when dealing with scene transitions: they expect everyone to be on the same page at the exact same time.
If the server loads the new scene instantly and starts broadcasting spawn instructions, any client still stuck on a loading screen will completely miss those instructions. Worse, they might throw hard errors and crash because the target spawn paths don't exist in their local scene tree yet.
To solve this boilerplate nightmare, I built a server-authoritative, open-source multiplayer lobby template. The goal was simple: provide a 2D template that developers can fork, wipe the placeholder world, and immediately start building on top of a reliable networking foundation.
To keep responsibilities strictly separated, the system relies on three core autoloads (singletons):
The PeerManager handles the actual connection logic. It exposes a simple API for hosting or joining a game. Under the hood, it delegates the actual networking to an abstract NetworkProvider class. Currently, it implements standard UDP/ENet connections, but this abstraction makes it trivial to swap in Steam or WebSockets later without touching the core game logic. You simply pass a configuration object to the provider on initialization, and it handles the rest.
This node acts as the server-authoritative state machine. It holds the lobby configuration, tracks max players, and manages LobbyPlayer nodes containing metadata for each connected user (e.g., their name and status: scene_loading, synced, or in_game).
Crucially, it also holds an active_map string property. The LobbyManager broadcasts all of this state to clients using a MultiplayerSynchronizer. Clients cannot change this state directly; they must send RPC requests to the server.
Instead of relying on a hard freeze during get_tree().change_scene_to_file(), the SceneManager handles transitions gracefully. It fades in a loading overlay, background-loads the heavy scene resources on a separate thread, swaps the scene tree once ready, and manages the fade-out.
The real fix to the desync issue happens between the SceneManager and the server. We don't just dump players into the game the millisecond their local scene finishes loading. We use a strict handshake protocol.
Here is the flow of how the server and clients negotiate a map change without crashing:
sequenceDiagram
participant Server (LobbyManager)
participant Client (LobbyManager)
participant Client (SceneManager)
Note over Server (LobbyManager), Client (SceneManager): The server decides to start the game
Server (LobbyManager)->>Server (LobbyManager): Update `active_map` string
Server (LobbyManager)->>Client (LobbyManager): Broadcast new `active_map` via Synchronizer
Client (LobbyManager)->>Client (SceneManager): Detect map change, request load
Client (SceneManager)->>Client (SceneManager): Fade UI, background load resource
Client (SceneManager)->>Client (SceneManager): Swap Scene Tree
Note over Client (SceneManager): Map is fully loaded locally
Client (SceneManager)->>Client (LobbyManager): Emit `done_loading` signal
Client (LobbyManager)->>Server (LobbyManager): RPC: "Change my status to synced!"
Server (LobbyManager)->>Server (LobbyManager): Update client status to `synced`
Note over Server (LobbyManager): Server now knows it is safe to spawn the player
Server (LobbyManager)->>Client (SceneManager): MultiplayerSpawner instantiates player character
By forcing the server to wait for individual clients to report their local status via RPC, we completely eliminate the race conditions that break out-of-the-box multiplayer spawners. Fast loaders get in quickly, slow loaders don't crash, and the server maintains total authority over the game state.
Building multiplayer games as a solo dev is tough enough without wrestling with synchronization crashes. This template abstracts away the painful boilerplate, leaving you free to focus on what actually matters: making a fun game.
Check out the repo on Github here: Friendslop Template
Hey yall, I built an #opensource (MIT) #godot 4 starter kit for multiplayer. I designed it as a genre-agnostic framework that I can fork for each co-op idea I want to explore.
Wanted to add more, but I should probably work on my ideas now 😅 #FOSS
github.com/RGonzalezTec...
— Raul Gonzalez | Computer Nerd (@rgonzaleztech.bsky.social) February 19, 2026 at 6:25 PM
[image or embed]