Architecture
In short
There are no scenes to set up:
GameBootstrapspawns oneGameRoot, which builds every system in code. Content is data (DefaultContent), every gameplay effect is a stat modifier, systems talk throughGameEvents, and one seed drives a whole run. Each screen is an IMGUI view picked by the currentGameState.
How it works
The diagram below is the README’s diagram, updated to what main does now. Solid arrows mean “creates or calls”. Dotted arrows mean “reads from”.
flowchart LR Boot["GameBootstrap<br/>press Play: spawns [Game]"] --> Root["GameRoot<br/>composition root,<br/>holds the GameState"] Content[("ContentDatabase<br/>Resources asset or DefaultContent")] -.-> Root Fit[("FitOverrides<br/>FitOverrides.json + fit-overrides.json")] -.->|applied at boot| Content Save[("SaveSystem<br/>save.json, run.json")] <--> Meta["MetaProgression<br/>unlocks, maps, XP, levels"] Root --> Meta Root --> Runs["RunManager (solo)<br/>map select, 9 innings, shop, run over"] Root --> Net["NetRuntime<br/>transport + NetSession"] Net -->|RunStarting| Coop["CoopRun (co-op)<br/>CoopRules on the host"] Root --> Cage["BattingCage"] Runs & Coop -->|IRunDriver = GameRoot.Run| Screens Runs & Coop -->|first inning| Intro["MatchIntroDirector<br/>match intro + walk-up"] Runs & Coop -->|boss down, run over| Outro["OutroDirector<br/>end-of-game cutscenes, Fireworks"] Intro --> AtBat Runs & Coop & Cage --> AtBat["AtBatController<br/>pitch, swing, flight, count"] AtBat --> Show["Hit show<br/>HitCounter, BallTracer,<br/>FlightCamera, replays"] Runs & Coop --> Field["BallparkBuilder<br/>field, crowd, scoreboard, sky"] Content -.-> Runs Content -.-> Catalog["IndexCatalog<br/>INDEX screen entries"] Runs --> Stats(["PlayerStats<br/>batter + level perks + upgrades<br/>+ synergies + park + pitcher"]) Stats -.-> AtBat Runs -->|run over| Meta Runs -->|checkpoint| Save AtBat & Runs & Coop & Root -->|raise| Events{{"GameEvents<br/>event bus"}} Events -->|subscribe| Screens["UIRoot<br/>one IMGUI view per GameState"] Events -->|subscribe| Audio["GameAudio"] Events -->|subscribe| Fx["ScreenFade, GreenFeedback,<br/>TimingIndicator, Scoreboard, crowd"] Events -->|PlayEnded| Log["SwingLog<br/>swings.csv"] Catalog -.-> Screens Show -.-> Screens Intro & Outro -.-> Screens
Boot order inside GameRoot.Awake (see Boot and GameRoot): content, fit overrides, save, display settings, meta progression, leaderboard, camera and light, ballpark, at-bat, the add-on components (including SwingLog, the two cutscene directors and ParkSnapshots), networking, batting cage, RunManager, UIRoot, then the main menu.
The big ideas
- No scenes needed.
GameBootstrapruns after any scene loads and adds aGameRootif none exists. Everything else is built from code. See Boot and GameRoot. - Everything is a stat modifier. Batters, level perks, upgrades, synergies, ballparks and boss pitchers all give
List<StatModifier>.PlayerStatssums them:(base + adds) x (1 + percents). See Stats System. - Content is data. Pitchers, pitch types, ballparks, upgrades, synergies, batters and cosmetics are ScriptableObjects (call signs are plain serializable
CallSignDefinitionentries inside theContentDatabase), all created in code byDefaultContent.Build()so the game runs with zero assets. See Ballparks, Upgrades, Batters. - Systems talk through
GameEvents. Gameplay raises events; UI, audio and effects subscribe. Gameplay code never knows audio exists. See Game Events. - Deterministic, seeded runs. One run seed builds the innings; pitches, special balls and each shop visit get their own
SeededRandomstream. See Seeds and Determinism. - IMGUI views per
GameState.UIRoot.OnGUIdraws one view for the current state. Add a screen = a state + a view + a case. See Game States and Screens and UI System.
Solo and co-op share the screens
GameRoot.Run returns an IRunDriver: the active CoopRun while a co-op game runs, else RunManager. The map, shop, build panel, pause menu and results screen only talk to IRunDriver, so they work for both. Starting a solo run still goes through GameRoot.Runs. See Co-op Run Flow.
Presentation layers added in #40 to #46
These sit on top of the gameplay and never change a score:
- Cutscenes (
Scripts/Intro/):MatchIntroDirectorplays the match intro before a run’s first inning, then the batter’s walk-up;OutroDirectorplays BOSS DOWN, MAP BEATEN and GAME OVER. Both are components on[Game](GameRoot.Intro,GameRoot.Outro), have their ownGameState(Intro,Outro) and draw throughIntroOverlayView/OutroOverlayViewwith the sharedBroadcastDrawkit. See Cutscenes. - Hit show (
AtBatController.HitShow.cs,HitCounter,HitTally,BallTracer,FlightCamera,HitCounterView): the live distance counter, the tracer, the TV-style flight camera and the big-moment replay. See Hit Show. - INDEX screen (
IndexCatalog+IndexView,GameState.Index): a list of everything in the game, built from the content. See Index Screen. - Swing log (
SwingLogon[Game]): every pitch you see appended toswings.csv. See Swing Log.
Assembly definitions
| Assembly | Folder | References | Notes |
|---|---|---|---|
HomeRunDerby | Assets/_Project/Scripts/ | Unity.InputSystem, Unity.RenderPipelines.Universal.Runtime, Unity.RenderPipelines.Core.Runtime | Root namespace HomeRunDerby. Auto-referenced. |
HomeRunDerby.Editor | Assets/_Project/Editor/ | HomeRunDerby, URP Runtime, URP Editor | Root namespace HomeRunDerby.EditorTools. Editor platform only. Not auto-referenced. |
The Facepunch Steamworks DLLs in Assets/Plugins/Steamworks/ are precompiled plugins, so they need no asmdef reference. The Steam code compiles only with the HOMERUNDERBY_STEAM define (see Build and Release).
Numbers
| What | Value | Where to tune |
|---|---|---|
| Unity version | 6000.6.6f1 | ProjectSettings/ProjectVersion.txt |
| URP package | 17.6.0 | Packages/manifest.json |
| Input System package | 1.19.0 | Packages/manifest.json |
| Active input handling | Both (old Input and Input System) | Project Settings > Player |
| UI reference height | 720 px (everything scales from it) | UITheme.RefHeight |
In the code
| File | What it does |
|---|---|
Scripts/Core/GameBootstrap.cs | ResetStatics clears GameEvents; Boot spawns [Game] with a GameRoot. |
Scripts/Core/GameRoot.cs | Composition root. Owns Content, Save, Meta, Leaderboard, Mode, Runs, ActiveCoop, Field, AtBat, Cage, Net, Intro, Outro, CameraRig, Sun. |
Scripts/Intro/ | The match intro and end-of-game cutscenes (Cutscenes). |
Scripts/Core/GameEvents.cs | The event bus. |
Scripts/Run/IRunDriver.cs | What in-run screens talk to (solo or co-op). |
Scripts/UI/UIRoot.cs | Draws the view for the current GameState, owns pause. |
Scripts/HomeRunDerby.asmdef, Editor/HomeRunDerby.Editor.asmdef | The two assemblies. |
Gotchas
README drift
The README’s diagram predates co-op, the batting cage, Fit Mode, audio and XP. Its folder table leaves out
Audio/,FitMode/andIntro/(the cutscenes folder from #40). The code is the source of truth; the diagram above is current for 45a2167.
GameRoot.IsReadyis false after a script recompile during Play mode wipes the services.UIRootandDevMenuskip drawing then.- Services are reached through the
GameRoot.Instancesingleton, not injected. Resources/ContentDatabase.assetis not in the repo on main, so the game always runs onDefaultContent.
Related
- Boot and GameRoot: the boot order in detail
- Code Map: every file and the note that covers it
- Game States and Screens: the screen state machine
- Game Events: the event bus
- Seeds and Determinism: why runs are reproducible
- Stats System: the one stacking system
- Cutscenes, Hit Show, Index Screen: the presentation layers added since e7dba51