Reference · Updated 2552.10.12.21.10
Lobby and map select — spec
Replaces the "host travels straight into the match" shortcut that MainMenuScreen.md §7 deferred. Decided 2026-10-06: the host picks the map inside the lobby, Halo custom-games styl
Replaces the "host travels straight into the match" shortcut that MainMenuScreen.md §7 deferred. Decided 2026-10-06: the host picks the map inside the lobby, Halo custom-games style, not before hosting. This is the LAN shape of the party lobby that MenusAndOnline.md §4.4 sketches for EOS, so Phase 6 mostly swaps the transport rather than the screens.
Main Menu ─ Host Game ─▶ Lobby map (listen server) ◀─ Join Game (SL-10) lands here
│ host: Change Map ─▶ Map Select ─▶ back to Lobby
└─ host: Start ─▶ ServerTravel to the chosen map, clients follow
#1. Lobby level
Lobbymap under/Game/SystemLink/Levels/Lobby/— a plain room; nobody plays in it.
ASLLobbyGameMode— no pawns to fight with, no respawn loop. OwnsStartMatch(): host-only,
ServerTravel to the selected map's level.
ASLLobbyGameState— replicates the selected map. The player list is the basePlayerArray,
which already replicates; do not build a parallel one.
USLMainMenuWidget::HostMappoints at the Lobby map instead of Level1.
⚠ Hard travel, still. MultiplayerPlan.md §4.4 keeps hard travel for Phase 1. Connected clients follow a hard ServerTravel by reconnecting, which is fine on a LAN. Seamless travel (and its transition map) is a later decision, and the loading screen (§4) is what hides either kind.
⚠ Menu input across travel — the main menu → lobby → match chain crosses two hard travels. The fresh- controller restore in ASLPlayerController::BeginPlay (Footguns → Input) is what keeps each arrival live.
#2. Lobby screen — WBP_SL_Lobby (USLLobbyWidget)
- Player list (one row per
PlayerState), the selected map's name and thumbnail.
- Host only: Change Map, Start. Clients see the same screen without them.
- Back / B on the host: leave =
LeaveMatch, which ends the lobby for everyone — confirm with a modal.
- Back / B on a client: leave =
LeaveMatch, back to the main menu.
- Testable before Join exists: PIE, Net Mode = Play As Listen Server, 2 players, starting on the Lobby map.
#3. Map select — WBP_SL_MapSelect
USLMapDefinitionprimary data asset per map: display name, level (TSoftObjectPtr<UWorld>),
description, thumbnail, max players. The screen lists every one the Asset Manager finds, so adding a map never edits a screen.
- Choosing a map sets it on the lobby game state (host's server — the host is the server, so no RPC).
- Level1 is the first entry.
As built (SL-34):
USLMapDefinition(Public/Game/) addsSortOrder. Lowest lists first, and a new lobby opens on the
first one, so there is no separate default-map setting. Primary asset type SLMap, registered in DefaultGame.ini to scan /Game/SystemLink/Levels — a definition saved elsewhere is not found.
ASLLobbyGameStatereplicates the definition'sFPrimaryAssetId, not the asset. An id always
arrives; an asset reference arrives null on a client that has not loaded it.
- A map whose
MaxPlayersis below the lobby's current player count is disabled in the list.
USLMapSelectWidgetfills a plain panel withUSLMapSelectEntrybuttons, built once. A list view is the
rule for variable-length lists, but this is a handful of entries and buttons take pad focus without the list view's workarounds.
- Picking a map sets it and pops the screen; Back pops it unchanged. The lobby reactivates and refreshes.
#4. Loading screen (after Join)
Hard travel blocks the game thread, so a UMG widget cannot animate during it — the load shows a frozen frame. Spec and decision (Epic's CommonLoadingScreen plugin): LoadingScreen.md (SL-35).
#5. Editor setup for SL-33 — in dependency order
The C++ (ASLLobbyGameMode, ASLLobbyGameState, USLLobbyWidget, USLLobbyPlayerRow, and ASLPlayerHUD::StartupScreenClass) is built. Each step below only references assets made in an earlier one.
WBP_SL_LobbyPlayerRow— parent SLLobbyPlayerRow. One CommonTextBlock named exactly
PlayerNameText.
WBP_SL_Lobby— parent SLLobbyWidget:- VerticalBox
PlayerListBox - CommonTextBlock
MapNameText StartButton— aWBP_SL_ButtonMainMenu, labelled StartWBP_SL_CommandBar- Get Desired Focus Target override returning
StartButton(the screen base will not compile - Class Defaults: Player Row Class =
WBP_SL_LobbyPlayerRow, Leave Confirm Modal Class =
without it)
WBP_SL_Modal
BP_SL_LobbyHUD— right-clickBP_SL_MenuHUD→ Create Child Blueprint Class. Set **Startup
Screen Class** = WBP_SL_Lobby. No graph.
BP_SL_LobbyGameModein/Game/SystemLink/Levels/Lobby/— parent SLLobbyGameMode:- Player Controller Class =
BP_SL_PlayerController(it carries the input contexts) - HUD Class =
BP_SL_LobbyHUD
- Lobby map — File → New Level → Basic, saved as
/Game/SystemLink/Levels/Lobby/Lobby(the path
USLMainMenuWidget::HostMap expects). Add one Camera Actor aimed at something; every player views the lobby through it. ⚠ Set its Auto Activate for Player to Player 0. Without it a client drops the camera the server gave it and views from its own pawnless controller (APlayerController::AutoManageActiveCameraTarget).
- Lobby map World Settings — GameMode Override =
BP_SL_LobbyGameMode.
#6. Testing SL-33
- Solo: MainMenu map → Host → the Lobby lists you and shows Level1 → Start → Level1, and Jump works.
From the lobby, B → confirm → back on the main menu.
- Two players, no Join screen needed: open the Lobby map, PIE with Net Mode Play As Listen Server
and 2 Players. Both windows list both players; the client has no Start; the host's Start takes both to Level1.
#7. Editor setup for SL-34 — in dependency order
The C++ (USLMapDefinition, USLMapSelectEntry, USLMapSelectWidget, and the lobby's map image and Change Map button) is built. Each step only references assets made in an earlier one.
- Thumbnails — import one 16:9 image per map (e.g. 1280×720) as
T_SL_Map_<Name>. In each texture:
Texture Group = UI, Mip Gen Settings = NoMipmaps.
- Second level — the temporary map, saved anywhere under
/Game/SystemLink/Levels/.
- Map definitions — Content Browser → right-click → Miscellaneous → Data Asset →
SLMapDefinition. DA_SL_Map_Level1— Display Name, Description (a line or two), Level =Level1, Thumbnail, Max Players 8, Sort Order 0DA_SL_Map_<Second>— the same for the temporary level, Sort Order 10
One per map, saved under /Game/SystemLink/Levels/ (that folder is what the Asset Manager scans):
WBP_SL_MapSelectEntry— User Widget, parent SLMapSelectEntry:- Style = the same style
WBP_SL_ButtonMainMenuuses - VerticalBox → SizeBox (320×180) → Image named exactly
MapImage; under it a CommonTextBlock named
exactly ButtonText
WBP_SL_MapSelect— User Widget, parent SLMapSelectWidget:- A WrapBox or HorizontalBox named exactly
MapListBox WBP_SL_CommandBar, for the Back prompt- Get Desired Focus Target override returning Get Current Map Entry
- Class Defaults: Entry Class =
WBP_SL_MapSelectEntry
WBP_SL_Lobby— add:- an Image named exactly
MapImage(in a 16:9 SizeBox, e.g. 480×270) - a
WBP_SL_ButtonMainMenunamed exactlyChangeMapButton, labelled Change Map - a CommonTextBlock named exactly
MapDescriptionText, under the map name, with Auto Wrap Text on - Class Defaults: Map Select Screen Class =
WBP_SL_MapSelect
⚠ Steps 4–6 will not compile until their named widgets exist — that is the BindWidget check doing its job.
#8. Testing SL-34
- Solo: Host → the lobby shows Level1's name, description and thumbnail → Change Map → the screen opens with Level1
focused → pick the second map → back in the lobby showing it → Start → the second map loads.
- Back on the map select returns to the lobby with the map unchanged.
- Pad only: the whole path above, with no mouse.
- Two players (PIE, Play As Listen Server, 2 players, starting on the Lobby map): the client has neither
Change Map nor Start; when the host changes the map, the client's name and thumbnail follow.
- Max players: set the second map's Max Players to 1, host with two players → its entry is disabled.
Set it back afterwards.