Add a Setting

In short

Options live in GameSettings (inside SaveData.cs) and are drawn by one method, SettingsView.DrawOptions, which both the SETTINGS popup (menu icon in the top bar) and the PAUSED menu use. Add a field with a sensible default, a row or slider in DrawOptions, and read it where it matters. Old saves just get the default.

Steps

  1. Field. Add it to class GameSettings in Assets/_Project/Scripts/Progression/SaveData.cs, with its default and a comment naming who reads it:
    public bool showFps;               // the FPS counter in the corner (UI/FpsCounter)
    Use JsonUtility-friendly types (bool, int, float, string; no Dictionary).
  2. Draw it in SettingsView.DrawOptions (Assets/_Project/Scripts/UI/SettingsView.cs):
    • On/off: add a button to a Row(...) and flip the field on click, then save:
    int toggled = Row(("TIMING RING", settings.showTimingRing, true), ("FPS COUNTER", settings.showFps, true),
        ("CUTSCENES", !settings.skipCutscenes, true), ("REPLAYS", !settings.noReplays, true));
    if (toggled == 0) settings.showTimingRing = !settings.showTimingRing;
    else if (toggled == 1) settings.showFps = !settings.showFps;
    else if (toggled == 2) settings.skipCutscenes = !settings.skipCutscenes;
    else if (toggled == 3) settings.noReplays = !settings.noReplays;
    if (toggled >= 0) SaveSystem.Save(game.Save);
    • Slider: UIDraw.LayoutSlider("LABEL", value, min, max, format). Sliders apply live and save once, when the mouse is released (the _dirty flag); add your slider to that comparison. Labels are in caps, like the others.
  3. Read it where it applies, through GameRoot.Instance.Save.settings or game.Save.settings (for example GameAudio.MusicVolume, AtBatController reading aimSensitivity for aim speed, UI/FpsCounter).
  4. Display options (window, resolution, frame rate) go through UI/DisplaySettings.Apply, which runs at startup (GameRoot) and after each change (SettingsView.ApplyAndSave).

Today’s settings

MUSIC, SOUND, AIM SPEED (0.5x to 2.5x in 0.05 steps), TIMING RING, FPS COUNTER, CUTSCENES, REPLAYS, FULLSCREEN / WINDOWED, V-SYNC, FPS CAP (MAX, 30, 60, 120, 144, 240; only with V-Sync off), and the resolution arrows.

CUTSCENES and REPLAYS (#40, #42) are the newest and a good model for an on/off option. They are stored inverted, so “on” is the default for old saves and new players alike (false is JsonUtility’s default):

public bool skipCutscenes;  // the match intro and the end-of-game cutscenes; on by default (false = play them)
public bool noReplays;      // the instant replay after a big home run (Batting/AtBatController.HitShow); on by default

and the row shows !settings.skipCutscenes. RunManager reads skipCutscenes before the intro and the end scenes (Cutscenes); the hit show reads noReplays (Hit Show).

Check it works

Press Play, change it in SETTINGS (title screen) and in the PAUSED menu (Esc in an at-bat), quit, press Play again and check it stuck. Tools → Home Run Derby → Delete Save File resets to the defaults.

Gotchas

  • The panels have fixed heights. The settings popup is 460 x 480 (LobbyNav) and the pause panel 400 x 616, or 668 in the batting cage (UIRoot.DrawPause). A new row may need those heights raised, or the bottom buttons spill out.
  • No save version bump or migration is needed: JsonUtility fills a missing field with the class default. For a bool that should default to on, name it the negative way (skipCutscenes, noReplays) or give it an initializer.
  • The panel sizes did not change when CUTSCENES and REPLAYS joined the toggle row (still 460 x 480 and 400 x 616 / 668), so check the row still fits at small window sizes.
  • Fullscreen and resolution do nothing in the Editor’s Game view; they apply in a build.
  • Settings are per machine and never sent in co-op.