UI & Online · Updated Jun 16, 2026

Front-End, Settings, Party & Sessions — Design Doc

Front-End, Settings, Party & Sessions — Design Doc

How a player goes from launching the game to shooting a friend they invited, and every menu in between. This doc owns the screen flow, the menu inventory, the player-settings model, and the EOS-based party/invite/session architecture.

It sits on top of Docs/UISystem.md, which owns the CommonUI plumbing (layer model, base widget classes, controller-input pipeline, footguns). Read that for "how a button focuses on gamepad"; read this for "what screens exist and how multiplayer is wired."


#0. Decisions Locked (2026-06-07)

DecisionChoiceNotes
Social / identity / sessionsEpic Online Services (EOS)Free, store-agnostic friends + invites + lobbies + P2P relay. The only thing that gives real internet "invite a friend."
ReachInternet-primary, LAN alsoEOS P2P relay for internet (no port-forwarding); LAN beacon / direct-IP path for same-network.
Match hosting (now)Player-hosted listen serverParty leader hosts. Zero infra/cost. Over EOS P2P it works across the internet with no port forwarding.
Match hosting (later)AWS GameLift, optionalDedicated servers swap in behind the ISLSessionService abstraction (§7). Party/menu code never changes.
Settings storageHybridVideo → UGameUserSettings; rebinds → UEnhancedInputUserSettings; name + gameplay prefs → small USaveGame profile.

#The two-layer mental model (the thing not to forget)


SOCIAL / PARTY layer   →  "Who agreed to play together?"     →  EOS (Lobbies, Friends, Invites)

MATCH-HOSTING layer    →  "Where does the match actually run?" →  Listen server now / GameLift later

EOS is required no matter what. GameLift only ever replaces the hosting layer. We build EOS first and keep hosting behind ISLSessionService so the upgrade is a contained change.


#1. Screen Flow (boot → match)


[Engine/Studio splash]

        │

        ▼

[EOS Login]  ── silent if already authed; first run may need account-portal / device-id ──┐

        │  (shows "Signing in…"; on fail → offline/LAN-only mode with a retry)            │

        ▼                                                                                 │

[First-run: Set Display Name]  ── only if no profile yet; else skipped ───────────────────┘

        │

        ▼

┌──────────────────────────────────────────────────────────┐

│  MAIN MENU   (Play · Settings · Profile · Credits · Quit) │

└──────────────────────────────────────────────────────────┘

        │ Play

        ▼

┌──────────────────────────────────────────────────────────┐

│  PARTY / LOBBY  (you are leader of a party-of-one)        │

│   • Member list + ready states                            │

│   • Invite Friends ▸ Friends list ▸ send invite           │

│   • Leader: pick Map + Mode                               │

│   • Leader: START MATCH                                    │

└──────────────────────────────────────────────────────────┘

        │ Start (leader)                         ▲ incoming invite (anywhere)

        ▼                                        │  toast → Accept → join party

[ISLSessionService.HostMatch]  ──────────────────┘

   • Internet → create EOS Session, listen server on leader (SocketSubsystemEOS)

   • LAN      → create LAN session / direct host

        │

        ▼

[ServerTravel to gameplay map]   ── party members ClientTravel to host ──

        │

        ▼

┌──────────────────────────────────────────────────────────┐

│  IN MATCH  (existing HUD; Pause menu on Start/Esc)        │

│   Pause: Resume · Settings · Leave Match                  │

└──────────────────────────────────────────────────────────┘

        │ Leave Match

        ▼

   back to PARTY / LOBBY

Invite-only is enforced structurally: the only way into a party is an invite the recipient accepts. The EOS Lobby is created with JoinViaPresence/invite-only permissions, no public listing, no quick-join. "Only players who agree to play together can play together" falls out of the lobby permission model — there is no code path that puts an un-invited stranger in your match.


#2. Menu Inventory

Each screen maps onto a base class from Docs/UISystem.md §4.3. Stacks (MenuStack, ModalStack) already exist on USLPrimaryGameLayout.

ScreenBase classNotes
WBP_SL_LoginScreenUSLScreenWidget"Signing in…" + retry / offline fallback. Shown only during boot.
WBP_SL_FirstRunNameUSLScreenWidgetFirst-launch display-name capture. Text entry + confirm.
WBP_SL_MainMenuUSLScreenWidgetRoot. Buttons → push other screens onto MenuStack.
WBP_SL_PartyLobbyUSLScreenWidgetThe heart of invite-only. Member list, ready, map/mode, invite, start.
WBP_SL_FriendsListUSLScreenWidgetEOS friends + presence; "Invite" per row. Uses UCommonListView.
WBP_SL_SettingsUSLTabbedScreenWidgetTabs: Gameplay · Controls · Video · Audio · Account.
WBP_SL_PauseMenuUSLScreenWidgetIn-match. Resume · Settings · Leave Match. (Already on the build-order list.)
WBP_SL_Modal_ConfirmUSLModalWidgetQuit / Leave / Disband / Discard-settings confirms.
WBP_SL_KeybindCaptureUSLModalWidget"Press any key…" overlay during rebind.
WBP_SL_InviteToastUSLGameOverlayWidgetIncoming-invite notification (Accept/Decline). HUD-tier, non-back-handler.

Settings tab content widgets (children of the tabbed screen's switcher): WBP_SL_Settings_Gameplay, _Controls, _Video, _Audio, _Account.

Settings rows (from UISystem.md §4.3): USLSettingsRow_Toggle, _Slider, _Rotator (UCommonRotator — best for gamepad enum cycling), _KeyBind.


#3. Player Settings (the part the user wants early)

Priority: Gameplay/Controls ships first — it carries the Y-invert + sensitivity the user called out. It's also low-risk and independent of EOS, so it delivers value before the hard party work.

#3.1 Settings catalog

Gameplay / Controls

  • Look Sensitivity X / Look Sensitivity Y (sliders)
  • ADS / Zoom Sensitivity Multiplier (slider)
  • Invert Look Y (toggle) — the headline request
  • Invert Look X (toggle, less common but cheap)
  • Controller: Aim Assist (toggle), Vibration (toggle), Stick Deadzones (sliders), Trigger as ADS hold-vs-toggle
  • ADS / Crouch: Hold vs Toggle (rotator)
  • Field of View (slider)

Controls (rebinding)

  • Per-action KBM + gamepad rebind rows via UEnhancedInputUserSettings (UE5.3+)
  • Reset-to-default (per-row + all)

Video

  • Resolution, Window Mode (rotator), VSync, Frame Rate Limit
  • Quality preset + scalability groups (View Distance, Shadows, AA, Post, Textures, Effects, Foliage, Shading)
  • Brightness / Gamma

Audio

  • Master / Music / SFX / Voice / UI (sliders, drive Sound Class volumes)

Account / Profile

  • Display Name (editable), EOS account status, Sign Out

#3.2 How Y-invert + sensitivity actually apply

Store on a small profile object the look handler reads every frame:


USLPlayerProfileSaveGame  (USaveGame)

  float  LookSensitivityX = 1.0

  float  LookSensitivityY = 1.0

  float  ADSSensitivityMul = 0.6

  bool   bInvertLookY = false

  bool   bInvertLookX = false

  FString DisplayName

  // (audio levels can live here too, or in GameUserSettings — see §3.3)

In ASLPlayerController (or wherever Look is bound), the look input becomes:


Yaw   = Raw.X * LookSensitivityX * (bInvertLookX ? -1 : 1) * ADSMul(ifAiming)

Pitch = Raw.Y * LookSensitivityY * (bInvertLookY ? -1 : 1) * ADSMul(ifAiming)

Profile loads on login, applies immediately, and re-applies on every change from the settings screen (broadcast a OnPlayerProfileChanged delegate the controller listens to). No restart, live preview.

Alternative considered: Enhanced Input scalar/negate modifiers driven by the profile. Cleaner in
theory but more moving parts; the direct-multiply path is the "shooting faster" choice. Revisit if
we want per-mapping-context sensitivity curves later.

#3.3 Persistence split

  • Video / scalabilityUGameUserSettings (or a USLGameUserSettings subclass). Engine
  • auto-persists to GameUserSettings.ini. Use the built-in ApplySettings / RunHardwareBenchmark.

  • RebindsUEnhancedInputUserSettings. Engine persists; gives us mappable configs + save/load
  • for free.

  • Name + gameplay prefs (+ optionally audio)USLPlayerProfileSaveGame in one slot.

#4. EOS Architecture (the new dependency)

#4.1 Plugin & subsystem choice

Two routes exist in UE 5.7:

  • OSSv1 — OnlineSubsystemEOS (+ OnlineSubsystemEOSPlus): mature, the most tutorials, plays
  • well with community Advanced Sessions. Recommended for fastest path.

  • OSSv2 — "Online Services" (OnlineServicesEOS): Epic's forward-looking API. Cleaner, but
  • thinner docs and more churn.

Either way we wrap it in our own USLOnlineSubsystem (game-instance subsystem) so the rest of the game never calls EOS types directly — keeps the OSSv1↔OSSv2 choice swappable and isolates SDK churn. Sub-decision still open — see §9.

#4.2 Setup (one-time)

  1. Epic Dev Portal: create a Product, Sandbox, Deployment; get Client ID / Client Secret / Encryption
  2. key.

  1. Enable OnlineSubsystemEOS + OnlineSubsystemEOSPlus plugins; enable SocketSubsystemEOS.
  1. Config/DefaultEngine.ini: [/Script/OnlineSubsystemEOS.EOSSettings] artifact block + set
  2. DefaultPlatformService=EOS, and [/Script/Engine.Engine] !NetDriverDefinitions / NetDriver to the EOS NetDriver + SocketSubsystemEOS so listen-server traffic rides EOS P2P/relay.

  1. EOS Dev Auth Tool for logging test accounts into PIE (so two editor instances = two players).

#4.3 Login

  • On boot, USLOnlineSubsystem::Login() → EOS Auth (Account Portal / Persistent / Device ID for a
  • no-account guest). Resolve a stable ProductUserId (PUID) + display name.

  • Success → continue to Main Menu. Failure → offline mode: LAN/direct only, social features greyed
  • out, retry button. (Don't hard-block the whole game on EOS being up.)

#4.4 Party = EOS Lobby; Match = EOS Session

  • Lobby is the social/party primitive: persistent membership, invites, presence, member
  • attributes (ready state, chosen character later). Created invite-only (no public search).

    • Create on "Play" (party-of-one) or on accepting an invite.
    • Invite: SendInvite(FriendPUID) → friend gets OnLobbyInviteReceivedWBP_SL_InviteToast
    • Accept → JoinLobby.

    • Leader-only controls (map/mode/start) gated on IsLobbyOwner.
  • Session is the match registration the host creates at Start. Carries the connection info party
  • members travel to.

    • Pattern: keep the Lobby alive as the party "home"; create a Session for the match; on Leave
    • Match everyone falls back to the Lobby. (Simpler MVP alternative: use the Lobby itself as the match container and skip a separate Session — fine for first playable.)

#4.5 Getting everyone into the match

Leader presses Start → ISLSessionService::HostMatch(MapName, Mode):

  1. Host creates the Session (or marks the Lobby in-game) and ServerTravels to the gameplay map as a
  2. listen server bound to SocketSubsystemEOS.

  1. Host broadcasts the connect string (the host's PUID-based EOS URL, not an IP) to lobby members
  2. via a lobby attribute / RPC.

  1. Members ClientTravel to that EOS URL → join the listen server over P2P/relay. **No port
  2. forwarding** because EOS relays when direct NAT punch fails.

#4.6 LAN path

Same ISLSessionService interface, different implementation: LAN session beacon (or direct "Join by IP / code"). The Party screen offers a "LAN" toggle for the leader; everything downstream (travel, HUD, pause) is identical.


#5. Front-End Map & Layout Ownership (architectural change needed)

Today USLPrimaryGameLayout is created in ASLPlayerHUD::BeginPlay, which is tied to a possessed gameplay pawn. The main menu has no pawn, so this must move up.

Plan: a dedicated front-end map (/Game/SystemLink/Maps/L_FrontEnd) with a minimal front-end GameMode + PlayerController, and the USLPrimaryGameLayout promoted to live at a level that exists with or without a gameplay pawn:

  • Preferred: a UGameInstanceSubsystem (e.g. USLUIManagerSubsystem) owns the root layout and the
  • push/pop API; both the front-end controller and the in-game ASLPlayerHUD route through it. (See UISystem.md footgun 6.23 — bind OnInputMethodChangedNative in OnLocalPlayerAdded, not Initialize.)

  • Lighter interim: a front-end-only HUD/controller that creates the layout, mirroring ASLPlayerHUD.

This is a prerequisite for any main-menu work and should be sequenced before §6 Phase 1's screens.


#6. Build Order (phased)

Dependency reality: the menu plumbing in Docs/UISystem.md §7 (UI input actions, CommonUI data assets, USLCommonActivatableWidget enhancements, USLButtonBase, pop/clear API) must land before any screen here. Then:

Phase A — Menu foundation (UISystem.md §7 steps 1–5) — prerequisite, already specced there.

Phase B — Front-end shell

  • B1. USLUIManagerSubsystem + layout-ownership move (§5).
  • B2. Front-end map + GameMode/PC.
  • B3. WBP_SL_MainMenu (Play/Settings/Quit wired; Play stubs for now).
  • B4. Pause menu (UISystem.md §7.6) — reuses the same screen base in-match.

Phase C — Settings (deliver the Y-invert/sensitivity ask)

  • C1. USLPlayerProfileSaveGame + load/apply on boot + OnPlayerProfileChanged.
  • C2. Sensitivity / Invert-Y wired into the look handler (§3.2). Testable in isolation, no EOS.
  • C3. WBP_SL_Settings tabbed screen + Gameplay/Controls tab + rows.
  • C4. Video tab via UGameUserSettings; Audio tab via Sound Classes.
  • C5. Controls/rebind tab via UEnhancedInputUserSettings.

Phase D — EOS foundation

  • D1. Plugins + Dev Portal config + Dev Auth Tool (§4.2).
  • D2. USLOnlineSubsystem wrapper + Login() + login screen + offline fallback (§4.3).

Phase E — Party & invites (invite-only core)

  • E1. WBP_SL_FriendsList (EOS friends + presence).
  • E2. EOS Lobby create/join + invite send/receive + WBP_SL_InviteToast (§4.4).
  • E3. WBP_SL_PartyLobby (members, ready, leader map/mode, Start disabled until ready).

Phase F — Sessions / getting into a match

  • F1. ISLSessionService interface + USLSessionService_ListenEOS (internet, §4.5).
  • F2. Start → host travel → members ClientTravel → everyone in the same match.
  • F3. Leave Match → back to lobby.

Phase G — LAN (§4.6)USLSessionService_LAN behind the same interface.

Phase H — GameLift (optional, later)USLSessionService_GameLift swaps in for hosting. Needs a dedicated server build target + GameLift Server SDK + a thin backend (Lambda/API Gateway) so the client never holds AWS creds. Party/menu UI unchanged. Use GameLift Anywhere for dev/testing.

Fastest-to-shooting-with-a-friend MVP = A → B(min) → D → E2(min) → F2. Settings (C) can run in
parallel and is what the user wants first, so in practice: A → C → B → D → E → F.

#7. The Hosting Abstraction


// Pseudocode — the seam that lets GameLift replace listen-server later.

class ISLSessionService

{

    virtual void HostMatch(FName Map, FName Mode, FOnMatchReady) = 0;   // leader calls

    virtual void JoinMatch(const FSLConnectInfo&, FOnJoined) = 0;       // members call

    virtual void LeaveMatch() = 0;

};



// Implementations, chosen at runtime by transport (Internet/LAN) + future config:

USLSessionService_ListenEOS   // now: leader hosts over EOS P2P/relay

USLSessionService_LAN         // now: LAN beacon / direct IP

USLSessionService_GameLift    // later: request a GameLift placement, return its IP:port

Menus and the party system only ever talk to ISLSessionService. Swapping hosting = registering a different implementation; no screen or party code changes.


#8. Open Sub-Decisions

  1. OSSv1 (OnlineSubsystemEOS) vs OSSv2 (OnlineServicesEOS) — recommend OSSv1 for docs/speed;
  2. wrapped either way. Confirm before Phase D.

  1. Separate Session vs Lobby-as-match — MVP can use the Lobby as the match container and add a
  2. real Session later. Decide at Phase F.

  1. Display name source — EOS account display name vs our own editable profile name. Plan:
  2. default to EOS name, allow override in profile.

  1. Pause in multiplayer — listen server: pausing stops time for everyone, so the in-match pause
  2. should be a UI overlay that does not call SetGamePaused once we're networked (single-player testing can pause). Matches UISystem.md footgun 6.12.

  1. Character/loadout select — does it live in the Party screen (pre-match) or in-match? Out of
  2. scope here; flag for a later pass.


#9. References

  • Docs/UISystem.md — CommonUI plumbing, base classes, controller-input pipeline, ~28 footguns.
  • EOS: Epic Dev Portal + EOS SDK docs; UE OnlineSubsystemEOS plugin.
  • GameLift (later): AWS GameLift docs; GameLift Anywhere for dev; GameLift Server SDK for UE.
  • Reference UI project: C:\3D-DEV\HaloProject\SystemLink — portable activatable/button/modal/tab
  • base classes (port, don't copy — UE version gap).