tools/purrlobby/how-it-works.md
PurrLobby is built from three pieces that stay out of each other's way: an orchestrator asset that names your backend, a menu scene that runs the UI, and a game scene that receives the connection. Nothing is hardcoded to a specific backend, and the game scene needs almost no PurrLobby-specific setup.
The orchestrator
GameOrchestrator is a ScriptableObject (Create → PurrLobby → Menu Orchestrator) holding four provider slots:
| Slot | Responsibility |
|---|---|
sessionProvider |
Logging the player in and giving them an identity |
lobbyProvider |
Creating, joining, listing and leaving lobbies |
matchmakingProvider |
Ticket-based matchmaking |
gameAllocator |
Producing connection info and loading the game scene |
Mix and match freely. A Steam lobby with the generic matchmaker and Steam sockets is just as valid as PurrNet Services lobbies with PurrTransport.
This matters more than it first looks. The slots are independent, so you can take the best part of each backend: Nakama for session, lobby and matchmaking, paired with PurrTransportGameAllocator so the match itself does not run over Nakama's WebSocket relay. Swapping the allocator changes only how the game scene connects, and nothing in the menu flow notices.
The asset is shared between the menu and game scenes, so it doubles as the handoff channel. At runtime it also carries:
activeLobby, the lobby the player is currently in, so the game scene can leave it on the way out.lastExitReason, why the player last left the game, which the menu reads when it comes back.menuScene, captured automatically when the game scene loads so the return trip knows where to go.
GameOrchestrator.active is a static pointing at whichever orchestrator booted the menu. That is how the game scene finds its way back without any wiring.
Boot
LobbyManager drives startup. It holds the orchestrator and a PurrUI ViewStack, and on Start it runs:
- Set
GameOrchestrator.active. sessionProvider.Login(stack). The provider may push its own views here, which is how the device login screen appears when a backend needs credentials.Initialize()on the lobby, matchmaking and allocator providers.- Subscribe to
onExternalJoinRequestedso platform invites work. - Push
MainMenuView.
Everything after that is view-driven.
External joins (an accepted Steam overlay invite, for example) are handled for you. LobbyManager leaves the current lobby, shows a loading view, joins the requested one, and opens the lobby screen. It is skipped silently if the provider does not advertise JoinLobbyById.
Starting a game
When every player is ready, the lobby owner runs the launch flow:
gameAllocator.AllocateGame(lobby)returns aConnectionInfo(server address, host id).- The connection info is written into lobby metadata so every member receives it.
gameAllocator.LoadGame(lobby)loads the game scene.gameAllocator.Connect(connection, shouldBeHost: true)starts the host.
Clients see the metadata change, run steps 3 and 4 themselves, and connect with shouldBeHost: false. Both paths run behind a LoadingView, and a failure at any step toasts the error and resets the ready state instead of stranding the lobby.
The scene load
LoadGameScene does more than call LoadSceneAsync:
- Records the current scene as
menuScenefor the return trip. - Calls
NetworkManager.DisableFlags(), globally suppressing auto-start until two frames after the load. This is what lets your game scene keep its auto-start flags and double as a testing environment: press play on it directly and it boots into a networked session, launch it through the lobby and the flags stand aside. - Creates a
GameSessionin the loaded scene viaGameSession.EnsureInScene.
That last point is why the game scene needs no PurrLobby component: the session is added for you.
Connecting
GameAllocatorProvider.Connect is shared by every allocator and does the guard work in one place:
- Aborts if the scene's
GameSessionis already exiting. - Requires a
NetworkManagerin the scene, and refuses to run if auto-start is still active. During a lobby-driven load it never is, sinceLoadGameScenehas suppressed it. - Downgrades a host request to a client when the allocator sets
supportsHostingto false, which is what dedicated-server backends do. - Calls the allocator's
ConfigureTransport, which adds and configures the right transport component on theNetworkManager. - Calls
StartHost()orStartClient().
Because ConfigureTransport adds the transport itself, you do not pre-assign one in the game scene. PurrTransportGameAllocator adds a PurrTransport and points it at the lobby id; SteamGameAllocator adds a SteamTransport aimed at the host's SteamID.
Coming back
GameSession owns the return trip and covers three cases:
- The player chooses to leave.
- The server ends the game through
GameOverBroadcaster.EndGame(). - The connection drops unexpectedly, in which case clients retry for a configurable window before giving up.
In all three it leaves the lobby, loads menuScene, and records lastExitReason on the orchestrator.
Next
- Scene setup for what actually goes in each scene.
- Providers for backend-specific settings.
- In the game scene for the pause menu, game over and reconnect.
- Customizing the UI for the view system.