UI System

In short

All UI is immediate-mode IMGUI (OnGUI), drawn in a virtual 720-pixel-tall space that scales to the window. Every panel, button and icon is generated in code by the theme classes; the only optional asset is a display font. Change a colour in UITheme and every screen follows. Every amount of money is drawn with its currency icon: a purple gem for points, a gold coin for coins.

How it works

Drawing a frame

  1. UIRoot.OnGUI returns early if GameRoot.IsReady is false.
  2. UITheme.BeginGUI() sets GUI.matrix to a uniform scale of Screen.height / 720. Views then lay out in UITheme.Width x 720 units.
  3. UIRoot calls the current view’s Draw(game) (Game States and Screens), then IntroOverlayView.DrawTransition and OutroOverlayView.DrawTransition (the cutscene wipes that carry over the next screen), then the pause panel if paused.
  4. Other components draw their own OnGUI on top by GUI.depth: DevMenu (-10), FpsCounter (-20), ScreenFade (-1000).

Theme

ClassRole
UIThemeColours (Yellow, Orange, Red, Green, SkyBlue, DeepBlue, Purple, Muted, Navy, NavyLight, NavyLine, Midnight, Cream, Ink, panel, card and lobby colours), ButtonColor gradients (Orange, Blue, Green, Red, Purple, Yellow, Gray), the Currency enum (Points, Coins), Scale, Width, Height, Centered(w, h), Lighten, and the Display font.
UIDrawThe toolkit. Absolute versions take a Rect; Layout* versions work inside GUILayout areas. Text: Text (outlined, shadowed), Body (wrapped, rich text), TextWidth, BodyHeight (wrapped height), FitText (one line that shrinks down to a minimum size, then ends in ”…”). Panels: Panel, Card, LobbyPanel, LobbyRow, Fill, BrushPanel, Dim, TitlePlate. Controls: Button, Slab, Tab, Hotspot, Slider, LayoutSlider, Pill, Bar, Circle, AlertTag, KeyHints. Currencies: CurrencyIcon, CurrencyPill, PriceButton. Layout: Layout, LayoutButton, LayoutTitle, LayoutBody, BeginPanel / EndPanel, BeginScrollArea / EndScrollArea, PushTransform.
UIGlyphsVector icons built from rounded rects and circles: Hat, Glasses, Shoe, Bat, Shirt, Glove, Smile, Lock, Check, None, Crown, Chevron, Person, People, Menu, PointGem, Coin, Baseball.
UITexturesGenerates and caches anti-aliased rounded-rect (9-sliced) and circle textures, with optional gloss, and Gem (a cut diamond drawn straight into a 64 px texture, so it works inside scroll views).

Buttons and hotspots play the click sound (GameAudio.Click).

Currencies (#38)

Points are the account’s money, kept between runs (batters, cosmetics, perk unlocks). Coins belong to one run and its shop (Shop and Economy, Meta Progression). The UI never shows one without its icon:

  • UIGlyphs.PointGem: a purple cut gem (UITextures.Gem). UIGlyphs.Coin: a gold coin with a stamped $.
  • UIDraw.CurrencyPill(rect, currency, text, size, dim): an amount on a pill, purple for points, orange for coins (gray when dim), icon on the left. It replaced the old IconPill.
  • UIDraw.PriceButton(rect, label, amount, currency, color, enabled, size): a button that reads “BUY [coin] 12”; the label rides down with the pressed face.
  • Where they appear: the top bar’s points pill (purple rim), PERKS unlock buttons, the title screen’s UNLOCK button for a batter, Customize tiles and the detail card, the shop’s coins pill, BUY and REROLL, the build panel’s coins, and the results screen’s POINTS EARNED row.

Shared pieces

  • LobbyNav: the top bar on the title screen, Customize and the INDEX screen. Tabs PLAY, CUSTOMIZE, PERKS (with a ”!” when a perk is affordable), BATTING CAGE, INDEX; then the account LV badge with its XP bar, the points pill and the settings menu icon. It owns the PERKS, settings and cage-park popups; PopupOpen tells the screen to disable its own controls, HandleEscape closes them. DrawBar returns the free x left of the badge, where the Editor-only NET and FIT buttons go.
  • BuildPanel: coins, SCORE, batter name, EQUIPMENT by slot, SYNERGIES and STATS. Shared by the map and the shop.
  • SettingsView: the options, drawn in the settings popup and the pause menu. The first row of toggles is TIMING RING, FPS COUNTER, CUTSCENES, REPLAYS (Save System).
  • ScreenFade: on every StateChanged, a black fade-in over 0.35 s on unscaled time (works while paused). Between screens that share the top bar (MainMenu, Customize, Index) it fades only below the bar. No fade from the match intro into play (one continuous shot, unless the intro was skipped) or from play into an outro (its own wipe covers it).
  • ThumbnailRenderer: renders each cosmetic (and bat) into a 160 px transparent texture for the Customize tiles. Items are built on a hidden stage at y = -500 on layer 3 (“Thumbnails”), shot by a disabled camera that sees only that layer, with its own fill light. The main camera never renders layer 3. Since #45, For(def) only queues a first request and returns null; the render happens in LateUpdate and is cached for the session (an item with no model caches null and is not retried).
  • ParkSnapshots (#39): one 768 x 368 picture of each ballpark for the map-select carousel. Only one park exists at a time, so Warm(parks) builds each park in turn, waits two frames, and renders it with the main camera from up behind the plate (from (-30, 42, -48) looking at (6, 0, 62), FOV 52), so the picture has the real sky, lights and post effects. MapSelectView calls Warm on opening and shows a loading bar (Progress) while IsWarming. Pictures are RenderTextures kept for the session and released in OnDestroy. See Map Select and Unlocks.
  • BroadcastDraw (#40): the TV-graphics kit shared by IntroOverlayView and OutroOverlayView: letterbox bars, the LIVE bug, glass panels (Glass3), the slash wipe, slam titles, the “CLICK TO CONTINUE” prompt with page dots, hints, stat rows with up / down arrows, chips, name sizing, and the motion helpers (SlideX, SlideY, Row, Fade). Its panel colours are fixed and fades go through GUI.color, so it never adds textures to the UITextures cache. See Cutscenes.

Other IMGUI components

HudView (in-round HUD; it also draws HitCounterView, the live distance counter, see Hit Show), IntroOverlayView, OutroOverlayView (Cutscenes), IndexView (Index Screen), TimingIndicator (the timing ring, see Swing and Timing), FpsCounter, GreenFeedback (camera shake only) and DisplaySettings (applies display options).

Numbers

WhatValueWhere to tune
Reference height720UITheme.RefHeight
Top bar height56LobbyNav.BarHeight
Fade length0.35 sScreenFade.Seconds
Thumbnail size160 pxThumbnailRenderer.Size
Thumbnail layer3ThumbnailRenderer.Layer
Park picture768 x 368, FOV 52, 2x MSAAParkSnapshots.Width, Height, ViewFov
Letterbox bar56BroadcastDraw.Bar
Card enter / row stagger / bar fill0.28 s / 0.05 s / 0.4 sBroadcastDraw.EnterSeconds, Stagger, FillSeconds
Gem texture64 pxUITextures.Gem
Display font pathResources/Fonts/Display (.ttf / .otf with Include Font Data)UITheme.Display

In the code

FileWhat it does
Scripts/UI/UIRoot.csPicks the view, draws the cutscene transitions, pause, cursor lock.
Scripts/UI/Theme/UITheme.csColours, Currency, scale, font.
Scripts/UI/Theme/UIDraw.csDrawing toolkit, currency pills and price buttons.
Scripts/UI/Theme/UIGlyphs.csIcons, including PointGem and Coin.
Scripts/UI/Theme/UITextures.csGenerated textures, including Gem.
Scripts/UI/BroadcastDraw.csThe cutscenes’ broadcast graphics kit.
Scripts/UI/LobbyNav.csTop bar and its popups.
Scripts/UI/BuildPanel.csThe build summary.
Scripts/UI/ScreenFade.csFade between screens.
Scripts/UI/ThumbnailRenderer.csItem pictures.
Scripts/UI/ParkSnapshots.csBallpark pictures for map select.

Gotchas

  • Resources/Fonts/Display is not in the repo, so the game uses Unity’s built-in font in bold. Fonts made at runtime from OS fonts don’t work with Unity 6’s IMGUI text renderer.
  • OnGUI runs several times a frame (layout, repaint, input). Do no game logic there; NetRuntime polls the network in Update for this reason.
  • Never start a camera render from OnGUI: URP can be mid-frame and throws (“UniversalCameraData has already been created”). That is why ThumbnailRenderer renders in LateUpdate (#45) and ParkSnapshots in a coroutine. A tile shows nothing for the first frame it asks.
  • ParkSnapshots.Warm rebuilds the field once per park and leaves the last one built.
  • Views must disable their controls while a LobbyNav popup is open, or clicks reach both.
  • Visible buttons go through UIDraw.Button / Slab / Hotspot / PriceButton (invisible GUI.Buttons under hand-drawn art), so the theme and the click sound stay consistent. CustomizeView also uses bare invisible GUI.Buttons for its sidebar rows and tiles.
  • Customize’s detail card still shows the word as well (“[gem] 120 PTS”); the tiles and buttons show just the icon and number.