From c71836c02932827fa72bd137699baf4c6eefd29a Mon Sep 17 00:00:00 2001 From: Michal Adam Pikulski Date: Tue, 21 Oct 2025 15:08:48 +0200 Subject: [PATCH] Stash WIP of settings documentation --- docs/settings_readme.md | 50 ++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/docs/settings_readme.md b/docs/settings_readme.md index 0ec0fd0b..ce42a650 100644 --- a/docs/settings_readme.md +++ b/docs/settings_readme.md @@ -19,6 +19,7 @@ Centralized, designer-friendly configuration using `ScriptableObject` assets, wi - [Diving Minigame Tuning](#diving-minigame-tuning) - [Troubleshooting / FAQ](#troubleshooting--faq) - [Paths & Namespaces](#paths--namespaces) +- [Design Opinion: Interface-per-Settings vs Simpler Alternatives](#design-opinion-interface-per-settings-vs-simpler-alternatives) - [Change Log](#change-log) ## What This Solves @@ -29,10 +30,12 @@ Centralized, designer-friendly configuration using `ScriptableObject` assets, wi ## Architecture at a Glance - Base: `BaseSettings` (`ScriptableObject`) — common parent for all settings. Implements optional `OnValidate()`. -- Access (runtime): `SettingsProvider` (singleton `MonoBehaviour`) — loads assets synchronously via Addressables at keys `Settings/` and caches them. -- Access (editor/dev): `SettingsAccess` (static) — editor-friendly shim for reading selected values even when not in Play Mode, falling back to `GameManager` at runtime. -- Contracts: `SettingsInterfaces` (`IPlayerFollowerSettings`, `IInteractionSettings`, `IDivingMinigameSettings`) used by systems to remain decoupled from concrete assets. -- Editor tooling: `AppleHills/Settings Editor` — finds/creates assets under `Assets/Settings` and provides a tabbed UI. +- Access (runtime): `SettingsProvider` (singleton `MonoBehaviour`) — loads assets synchronously via Addressables at keys `Settings/` and caches them. Source: `Assets/Scripts/Core/Settings/SettingsProvider.cs`. +- Access (editor/dev): `SettingsAccess` (static) — editor-friendly shim for reading selected values even when not in Play Mode, falling back to `GameManager` at runtime. Source: `Assets/Scripts/Core/SettingsAccess.cs`. +- Editor glue (non-Play Mode): `EditorSettingsProvider` — initializes on script reload, loads assets from `Assets/Settings/*.asset`, wires delegates in `SettingsAccess` and repaints Scene views on changes. Source: `Assets/Editor/Settings/EditorSettingsProvider.cs`. +- Contracts: `SettingsInterfaces` (`IPlayerFollowerSettings`, `IInteractionSettings`, `IDivingMinigameSettings`) used by systems to remain decoupled from concrete assets. Source: `Assets/Scripts/Core/Settings/SettingsInterfaces.cs`. +- Concrete assets: `PlayerFollowerSettings`, `InteractionSettings`, `DivingMinigameSettings` — all derive from `BaseSettings` and implement their respective interfaces with validation in `OnValidate()`. +- Editor tooling: `AppleHills/Settings Editor` — finds/creates assets under `Assets/Settings` and provides a tabbed UI; `AppleHills/Developer Settings Editor` — similar pattern for developer‑only settings under `Assets/Settings/Developer`. Sources: `Assets/Editor/Settings/SettingsEditorWindow.cs`, `Assets/Editor/Settings/DeveloperSettingsEditorWindow.cs`. ## Quick Start (Code-First) @@ -103,12 +106,14 @@ float promptRange = inter.DefaultPuzzlePromptRange; - Click “Save All” to persist and refresh editor providers/gizmos. ### Addressables Keys & Loading -At runtime, `SettingsProvider` synchronously loads settings via Addressables with keys: +At runtime, `SettingsProvider` synchronously loads settings via Addressables with keys constructed as `Settings/` where `` is the C# class name: - `Settings/PlayerFollowerSettings` - `Settings/InteractionSettings` - `Settings/DivingMinigameSettings` -Ensure these assets are marked as Addressables with the exact keys above. The provider caches objects, so subsequent `GetSettings()` calls are fast. +Notes: +- Asset filenames can differ (e.g., `MinigameSettings.asset`), but Addressables keys should follow the type name to match `SettingsProvider`’s lookup. +- Mark each settings asset as Addressable and set its Addressables key as above. The provider caches objects, so subsequent `GetSettings()` calls are fast. ## Available Settings Types - `PlayerFollowerSettings` (`IPlayerFollowerSettings`) @@ -175,12 +180,41 @@ public class SpawnController - `PlayerFollowerSettings.cs` - `InteractionSettings.cs` - `DivingMinigameSettings.cs` -- Editor tooling: `Assets/Editor/SettingsEditorWindow.cs` +- Editor tooling: `Assets/Editor/Settings/` + - `SettingsEditorWindow.cs` + - `DeveloperSettingsEditorWindow.cs` + - `EditorSettingsProvider.cs` - Editor-time facade: `Assets/Scripts/Core/SettingsAccess.cs` - Namespaces: - Runtime: `AppleHills.Core.Settings` - - Editor window: `AppleHills.Core.Settings.Editor` + - Editor windows: `AppleHills.Core.Settings.Editor` + - Editor glue: `AppleHills.Editor` - Facade: `AppleHills` +## Design Opinion: Interface-per-Settings vs Simpler Alternatives + +Your current setup creates a dedicated interface and a concrete `ScriptableObject` class per settings domain (e.g., `IInteractionSettings` + `InteractionSettings`). Here’s an assessment based on the repository: + +Benefits +- Decoupling and testability: Call sites can depend on `IInteractionSettings`/`IDivingMinigameSettings`, making it trivial to mock or swap implementations in tests or temporary experiments. +- Contract discipline: Interfaces create a curated public surface for teams to converge on; designers can add fields to the asset without automatically expanding the contract. +- Runtime safety during refactors: Systems compile against the interface even if you split a single asset into multiple specialized assets later. + +Costs +- Boilerplate: Duplicated getters across the interface and class; more files to maintain. +- Drift risk: If a field is added to the `ScriptableObject` but not reflected in the interface (or vice versa), consumers may not see it or may rely on the wrong surface. +- Over-abstraction for small teams: When the same team owns both the consumer and the asset, interfaces can feel heavy until real polymorphism or mocking is needed. + +Pragmatic options +- Keep interfaces where they pay off now: `IInteractionSettings`, `IDivingMinigameSettings` already gate many systems and benefit from abstraction. +- Simplify where scope is narrow: For settings used in one place (or purely visual), rely on concrete classes until a second consumer appears. You can introduce an interface later without breaking Addressables keys (which use type names in lookup). +- Aggregator interface: Introduce an `IGameSettings` façade that exposes only the subset most systems need, backed by a composite provider that reads from the three assets. This reduces type spread at call sites while preserving modular assets. +- Codegen or source generators (optional): Generate interfaces from concrete classes (read-only properties only) to eliminate drift/boilerplate. Not required, just an idea if the pattern grows. + +Recommendation +- Short term: Keep the three interfaces you have (interaction, follower, diving). They are already wired and provide value. Avoid adding new interfaces unless a setting has multiple independent consumers or needs mocking. +- Medium term: Consider an `IGameSettings` façade (or a small static wrapper) for high-traffic reads like interaction distances to reduce repetitive `GetSettings()` calls around the codebase. + ## Change Log +- v1.2: Expanded architecture with `EditorSettingsProvider` and developer tooling; clarified Addressables key pattern vs asset filenames; added design opinion and recommendations. - v1.1: New page with TOC, code-first usage, authoring workflow, Addressables keys, case studies, troubleshooting, and paths.