Game States and Screens

In short

The game is always in exactly one GameState. UIRoot.OnGUI draws the matching view; the 3D scene behind it is set up by whoever changed the state. Overlays (dev menu, NET TEST, Fit Mode, settings popups, pause, fade, FPS counter) are drawn by their owners on top.

How it works

States and views

GameStateView classWhat it showsEntered by
MainMenuMainMenuViewTitle screen: top bar, leaderboards, daily challenge, batter, mode card, PLAY! / CONTINUE / PLAY CO-OP.GameRoot.GoToMainMenu
MapSelectMapSelectViewPick a map and difficulty; PLAY BALL! starts a solo run.GameRoot.OpenMapSelect
RunMapRunMapView (+ BuildPanel)Between innings: the inning, its pitcher, goal, PLAY BALL!, SAVE & QUIT / ABANDON RUN (co-op: LEAVE GAME).RunManager.ShowMap, CoopRun.ShowMap
AtBatHudViewIn-round HUD. Also the batting cage.RunManager.PlayCurrentRound, CoopRun (your turn), BattingCage.StartSession
ShopShopView (+ BuildPanel)Offers, REROLL, NEXT INNING (co-op: READY).RunManager.OpenShop, CoopRun.OpenShop
RunOverRunOverViewResults, XP bars, RUN IT BACK / PICK A MAP / MAIN MENU (co-op: MAIN MENU only).RunManager.EndRun, CoopRun.EndRun
CustomizeCustomizeViewDress-up screen and the Fit Mode host.GameRoot.EnterCustomize
LobbyLobbyViewCo-op host / join, players, START RUN.GameRoot.EnterLobby
CoopWatchCoopWatchView (+ HudView while AtBat.IsSpectating)Someone else is batting.CoopRun
IntroIntroOverlayViewThe match intro before a run’s first inning: cold open, park tour, map card, pitcher and batter cards, face-off, PLAY BALL! (Cutscenes).RunManager.PlayCurrentRound (inning 1, once per run), CoopRun.PlayMatchIntro, MatchIntroDirector.Preview
OutroOutroOverlayViewBOSS DOWN, MAP BEATEN or GAME OVER (Cutscenes).OutroDirector.Begin (at the midpoint of its entry wipe), OutroDirector.Preview
IndexIndexViewThe INDEX screen: tabs BATTERS, PITCHERS, BALLPARKS, EQUIPMENT, FOOD, SPECIAL BALLS under the lobby top bar (Index Screen).GameRoot.EnterIndex (INDEX tab in LobbyNav)

Transitions

stateDiagram-v2
    [*] --> MainMenu: GameRoot.Awake
    MainMenu --> MapSelect: PLAY! (no saved run)
    MainMenu --> RunMap: CONTINUE (resume run.json) / DAILY CHALLENGE
    MainMenu --> Shop: CONTINUE (checkpoint was in the shop)
    MainMenu --> Customize: CUSTOMIZE tab
    Customize --> MainMenu: PLAY tab / Esc
    MainMenu --> Lobby: PLAY CO-OP
    MainMenu --> AtBat: BATTING CAGE popup (pick a park)
    MapSelect --> RunMap: PLAY BALL!
    MapSelect --> MainMenu: BACK
    RunMap --> AtBat: PLAY BALL!
    RunMap --> MainMenu: SAVE & QUIT
    AtBat --> Shop: inning cleared
    AtBat --> RunOver: out of outs / last inning cleared
    Shop --> RunMap: NEXT INNING
    AtBat --> MainMenu: pause SAVE & QUIT TO MENU / LEAVE CAGE
    RunOver --> RunMap: RUN IT BACK
    RunOver --> MapSelect: PICK A MAP
    RunOver --> MainMenu: MAIN MENU
    Lobby --> RunMap: host START RUN (CoopRun.Start)
    Lobby --> MainMenu: BACK / Esc
    RunMap --> CoopWatch: co-op, another player bats
    CoopWatch --> AtBat: co-op, your turn
    AtBat --> CoopWatch: co-op, turn over
    RunMap --> Intro: PLAY BALL! on inning 1 (cutscenes on)
    Intro --> AtBat: walk-up (your turn)
    Intro --> CoopWatch: co-op, the opener is someone else
    AtBat --> Outro: boss inning cleared / map beaten / out of outs
    CoopWatch --> Outro: co-op, same endings
    Outro --> Shop: after BOSS DOWN
    Outro --> RunOver: after MAP BEATEN / GAME OVER
    MainMenu --> Index: INDEX tab
    Customize --> Index: INDEX tab
    Index --> MainMenu: PLAY tab / Esc
    Index --> Customize: CUSTOMIZE tab

With CUTSCENES off in Settings (skipCutscenes), the intro and outro states are skipped: PLAY BALL! goes straight to AtBat, and the inning end goes straight to Shop or RunOver as before. ABANDON RUN and the dev menu’s BEAT MAP / LOSE RUN never play an outro (EndRun(..., null)).

ABANDON RUN (map or pause) ends the run through EndRun(false), so it lands on RunOver. In co-op, LEAVE GAME and a lost session go back to MainMenu (CoopRun.OnSessionEnded shows the reason with MainMenuView.ShowNotice).

Overlays

OverlayOwnerOpens withNotes
Pause (PAUSED)UIRoot.DrawPauseEsc during AtBat, or the window losing focusRESUME, the settings, then SAVE & QUIT TO MENU + ABANDON RUN (co-op: LEAVE GAME), or in the cage PITCH / PARK / LEAVE CAGE. Sets Time.timeScale = 0 and AudioListener.pause.
Dev menuDevMenu (own component, GUI.depth = -10)F1Editor and development builds only. Pauses an at-bat while open. See Dev Menu and Shortcuts.
NET TESTNetTestView, drawn by MainMenuViewCtrl+Shift+N, or the NET button (Editor)Transport test. See Transports.
Fit ModeFitModeView, drawn by CustomizeViewCtrl+Shift+F, or the FIT button (Editor)See Fit Mode.
SettingsSettingsView.DrawOptionsMenu icon in the top bar (LobbyNav), and inside pauseSee Add a Setting.
PERKS, BATTING CAGE park pickerLobbyNav.DrawPopupsTop bar tabsShared by MainMenu and Customize. Esc closes them.
Screen fadeScreenFade (GUI.depth = -1000)Every StateChanged0.35 s fade from black.
FPS counterFpsCounter (GUI.depth = -20)Settings: FPS COUNTEROver everything, pause included.
Walk-up transitionIntroOverlayView.DrawTransitionAfter the match introThe second half of the wipe, the NOW BATTING lower-third and the letterbox retracting, drawn over the next screen (AtBat or CoopWatch) while the HUD fades in (MatchIntroDirector.HudAlpha).
Outro entry wipeOutroOverlayView.DrawTransitionThe last play of a boss inning or the runA slash wipe over AtBat / CoopWatch; the state flips to Outro under its midpoint.
Hit counterHitCounterView, drawn by HudViewEvery batted ballPart of the HUD, not its own state (Hit Show).

Numbers

WhatValueWhere to tune
Fade-in length0.35 sScreenFade.Seconds
Screens that share the top bar (fade only below it)MainMenu, Customize, IndexScreenFade.HasTopBar
Pause panel height616 (cage: 668)UIRoot.DrawPause
GUI depth: UIRoot / DevMenu / FpsCounter / ScreenFade0 / -10 / -20 / -1000each component’s OnGUI

In the code

FileWhat it does
Scripts/Core/GameState.csThe 12 states: MainMenu, RunMap, AtBat, Shop, RunOver, Customize, Lobby, CoopWatch, MapSelect, Intro, Outro, Index.
Scripts/UI/UIRoot.csOne view instance per state; Update handles pause, dev pause and cursor confinement; OnGUI switches on State.
Scripts/UI/LobbyNav.csTop bar for MainMenu, Customize and Index (PLAY, CUSTOMIZE, PERKS, BATTING CAGE, INDEX, points, menu).
Scripts/Intro/MatchIntroDirector.cs, OutroDirector.csRun the cutscenes and set the Intro / Outro states.
Scripts/UI/IntroOverlayView.cs, OutroOverlayView.cs, IndexView.csThe views for the three new states.
Scripts/Modes/BattingCage.csEnter and StartSession set AtBat with the cage active.

Gotchas

  • MainMenuView, CustomizeView and the other views are plain classes, not MonoBehaviours. They keep their fields between visits; CustomizeView resets itself through OnStateChanged.
  • The cursor is Confined only while batting and not paused. Unity enforces it in Windows/Linux builds, not the Editor’s Game view.
  • Alt-tab during an at-bat pauses (OnApplicationFocus) so you never lose a strike to a focus change.
  • BattingCage.IsActive is cleared on any state change away from AtBat.
  • The cage counts as AtBat, so HudView and the pause menu serve it too.
  • ScreenFade skips its fade from Intro to AtBat / CoopWatch (the intro ends on the batting view, one continuous shot) unless the intro was skipped with Esc, and from AtBat / CoopWatch into Outro (the entry wipe covers it).
  • The pause menu only opens in AtBat; in Intro and Outro Esc skips the cutscene instead.
  • GameAudio treats Intro and Outro like AtBat: the menu organ fades out (Audio).
  • The dev previews (dev menu CUTSCENES, the Editor-only I / B / V / G keys on the run map) go MainMenu or RunMap to Intro / Outro and back to where they started (Dev Menu and Shortcuts).