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 inUIThemeand 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
UIRoot.OnGUIreturns early ifGameRoot.IsReadyis false.UITheme.BeginGUI()setsGUI.matrixto a uniform scale ofScreen.height / 720. Views then lay out inUITheme.Widthx 720 units.UIRootcalls the current view’sDraw(game)(Game States and Screens), thenIntroOverlayView.DrawTransitionandOutroOverlayView.DrawTransition(the cutscene wipes that carry over the next screen), then the pause panel if paused.- Other components draw their own
OnGUIon top byGUI.depth:DevMenu(-10),FpsCounter(-20),ScreenFade(-1000).
Theme
| Class | Role |
|---|---|
UITheme | Colours (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. |
UIDraw | The 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. |
UIGlyphs | Vector 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. |
UITextures | Generates 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 whendim), icon on the left. It replaced the oldIconPill.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;PopupOpentells the screen to disable its own controls,HandleEscapecloses them.DrawBarreturns 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 everyStateChanged, 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 inLateUpdateand 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, soWarm(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.MapSelectViewcallsWarmon opening and shows a loading bar (Progress) whileIsWarming. Pictures areRenderTextures kept for the session and released inOnDestroy. See Map Select and Unlocks.BroadcastDraw(#40): the TV-graphics kit shared byIntroOverlayViewandOutroOverlayView: 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 throughGUI.color, so it never adds textures to theUITexturescache. 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
| What | Value | Where to tune |
|---|---|---|
| Reference height | 720 | UITheme.RefHeight |
| Top bar height | 56 | LobbyNav.BarHeight |
| Fade length | 0.35 s | ScreenFade.Seconds |
| Thumbnail size | 160 px | ThumbnailRenderer.Size |
| Thumbnail layer | 3 | ThumbnailRenderer.Layer |
| Park picture | 768 x 368, FOV 52, 2x MSAA | ParkSnapshots.Width, Height, ViewFov |
| Letterbox bar | 56 | BroadcastDraw.Bar |
| Card enter / row stagger / bar fill | 0.28 s / 0.05 s / 0.4 s | BroadcastDraw.EnterSeconds, Stagger, FillSeconds |
| Gem texture | 64 px | UITextures.Gem |
| Display font path | Resources/Fonts/Display (.ttf / .otf with Include Font Data) | UITheme.Display |
In the code
| File | What it does |
|---|---|
Scripts/UI/UIRoot.cs | Picks the view, draws the cutscene transitions, pause, cursor lock. |
Scripts/UI/Theme/UITheme.cs | Colours, Currency, scale, font. |
Scripts/UI/Theme/UIDraw.cs | Drawing toolkit, currency pills and price buttons. |
Scripts/UI/Theme/UIGlyphs.cs | Icons, including PointGem and Coin. |
Scripts/UI/Theme/UITextures.cs | Generated textures, including Gem. |
Scripts/UI/BroadcastDraw.cs | The cutscenes’ broadcast graphics kit. |
Scripts/UI/LobbyNav.cs | Top bar and its popups. |
Scripts/UI/BuildPanel.cs | The build summary. |
Scripts/UI/ScreenFade.cs | Fade between screens. |
Scripts/UI/ThumbnailRenderer.cs | Item pictures. |
Scripts/UI/ParkSnapshots.cs | Ballpark pictures for map select. |
Gotchas
Resources/Fonts/Displayis 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.OnGUIruns several times a frame (layout, repaint, input). Do no game logic there;NetRuntimepolls the network inUpdatefor this reason.- Never start a camera render from
OnGUI: URP can be mid-frame and throws (“UniversalCameraData has already been created”). That is whyThumbnailRendererrenders inLateUpdate(#45) andParkSnapshotsin a coroutine. A tile shows nothing for the first frame it asks. ParkSnapshots.Warmrebuilds the field once per park and leaves the last one built.- Views must disable their controls while a
LobbyNavpopup is open, or clicks reach both. - Visible buttons go through
UIDraw.Button/Slab/Hotspot/PriceButton(invisibleGUI.Buttons under hand-drawn art), so the theme and the click sound stay consistent.CustomizeViewalso uses bare invisibleGUI.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.
Related
- Game States and Screens: which view draws when
- Add a Screen: adding a new view
- Add a Setting: adding an option to
SettingsView - Customization: the screen that uses thumbnails
- Map Select and Unlocks: the carousel that uses park pictures
- Cutscenes: the screens built with
BroadcastDraw - Shop and Economy and Meta Progression: coins and points
- Dev Menu and Shortcuts: the dev overlay