Files
AppleHillsProduction/docs/settings_readme.md

221 lines
12 KiB
Markdown
Raw Permalink Normal View History

# Apple Hills Settings System
2025-10-21 12:37:53 +02:00
Centralized, designer-friendly configuration using `ScriptableObject` assets, with runtime access via `SettingsProvider` (Addressables-backed) and editor/live-preview access via `SettingsAccess`. This page follows the style of other updated docs (TOC, inline code, code-first usage, case studies).
## Table of Contents
- [What This Solves](#what-this-solves)
- [Architecture at a Glance](#architecture-at-a-glance)
- [Quick Start (Code-First)](#quick-start-code-first)
- [Get Settings at Runtime](#get-settings-at-runtime)
- [Use Editor-Time Values via `SettingsAccess`](#use-editor-time-values-via-settingsaccess)
- [Access Example Fields](#access-example-fields)
- [Authoring in the Editor](#authoring-in-the-editor)
- [Creating/Editing Settings Assets](#creatingediting-settings-assets)
- [Addressables Keys & Loading](#addressables-keys--loading)
- [Available Settings Types](#available-settings-types)
- [Case Studies](#case-studies)
- [Tune Interaction Distances](#tune-interaction-distances)
- [Follower Handling & Movement](#follower-handling--movement)
- [Diving Minigame Tuning](#diving-minigame-tuning)
- [Troubleshooting / FAQ](#troubleshooting--faq)
- [Paths & Namespaces](#paths--namespaces)
2025-10-21 15:08:48 +02:00
- [Design Opinion: Interface-per-Settings vs Simpler Alternatives](#design-opinion-interface-per-settings-vs-simpler-alternatives)
2025-10-21 12:37:53 +02:00
- [Change Log](#change-log)
## What This Solves
- Consistent, centralized configuration across gameplay systems.
- Safe, designer-editable `ScriptableObject` assets with validation (`OnValidate`).
- Simple, Addressables-based runtime loading and caching via `SettingsProvider`.
- Editor-time overrides and scene gizmo feedback via `SettingsAccess` helpers.
## Architecture at a Glance
- Base: `BaseSettings` (`ScriptableObject`) — common parent for all settings. Implements optional `OnValidate()`.
2025-10-21 15:08:48 +02:00
- Access (runtime): `SettingsProvider` (singleton `MonoBehaviour`) — loads assets synchronously via Addressables at keys `Settings/<TypeName>` 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 developeronly settings under `Assets/Settings/Developer`. Sources: `Assets/Editor/Settings/SettingsEditorWindow.cs`, `Assets/Editor/Settings/DeveloperSettingsEditorWindow.cs`.
2025-10-21 12:37:53 +02:00
## Quick Start (Code-First)
### Get Settings at Runtime
```csharp
2025-10-21 12:37:53 +02:00
using AppleHills.Core.Settings;
2025-10-21 12:37:53 +02:00
var playerFollower = SettingsProvider.Instance.GetSettings<PlayerFollowerSettings>();
float speed = playerFollower.MoveSpeed;
```
2025-10-21 12:37:53 +02:00
Or fetch interaction settings once and reuse:
```csharp
2025-10-21 12:37:53 +02:00
using AppleHills.Core.Settings;
2025-10-21 12:37:53 +02:00
private IInteractionSettings _interaction;
2025-10-21 12:37:53 +02:00
void Awake()
{
2025-10-21 12:37:53 +02:00
_interaction = SettingsProvider.Instance.GetSettings<InteractionSettings>();
}
2025-10-21 12:37:53 +02:00
void UseIt()
{
2025-10-21 12:37:53 +02:00
float stopDist = _interaction.PlayerStopDistance;
}
```
2025-10-21 12:37:53 +02:00
### Use Editor-Time Values via `SettingsAccess`
For scene tools, gizmos, or editors that should reflect current settings outside Play Mode:
```csharp
// Returns editor-sourced values in Edit Mode; GameManager-backed in Play Mode
float stopDist = AppleHills.SettingsAccess.GetPlayerStopDistance();
float directStop = AppleHills.SettingsAccess.GetPlayerStopDistanceDirectInteraction();
float puzzleRange = AppleHills.SettingsAccess.GetPuzzlePromptRange();
```
2025-10-21 12:37:53 +02:00
### Access Example Fields
```csharp
using AppleHills.Core.Settings;
2025-10-21 12:37:53 +02:00
var pf = SettingsProvider.Instance.GetSettings<PlayerFollowerSettings>();
// Player
float moveSpeed = pf.MoveSpeed;
float accel = pf.MaxAcceleration;
bool useRb = pf.UseRigidbody;
// Follower
float followDist = pf.FollowDistance;
float near = pf.ThresholdNear;
var inter = SettingsProvider.Instance.GetSettings<InteractionSettings>();
LayerMask interactMask = inter.InteractableLayerMask;
GameObject pickupPrefab = inter.BasePickupPrefab;
float promptRange = inter.DefaultPuzzlePromptRange;
```
2025-10-21 12:37:53 +02:00
## Authoring in the Editor
### Creating/Editing Settings Assets
- Open via menu: `AppleHills/Settings Editor`.
- The window discovers all assets of type `BaseSettings` and provides tabbed editing for:
- `PlayerFollowerSettings`
- `InteractionSettings`
- `DivingMinigameSettings`
- If an asset is missing, the tool auto-creates it under `Assets/Settings/`:
- `Assets/Settings/PlayerFollowerSettings.asset`
- `Assets/Settings/InteractionSettings.asset`
- `Assets/Settings/DivingMinigameSettings.asset`
- Click “Save All” to persist and refresh editor providers/gizmos.
### Addressables Keys & Loading
2025-10-21 15:08:48 +02:00
At runtime, `SettingsProvider` synchronously loads settings via Addressables with keys constructed as `Settings/<TypeName>` where `<TypeName>` is the C# class name:
2025-10-21 12:37:53 +02:00
- `Settings/PlayerFollowerSettings`
- `Settings/InteractionSettings`
- `Settings/DivingMinigameSettings`
2025-10-21 15:08:48 +02:00
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<T>()` calls are fast.
2025-10-21 12:37:53 +02:00
## Available Settings Types
- `PlayerFollowerSettings` (`IPlayerFollowerSettings`)
- Player: `MoveSpeed`, `MaxAcceleration`, `StopDistance`, `UseRigidbody`, `DefaultHoldMovementMode`.
- Follower: `FollowDistance`, `ManualMoveSmooth`, `ThresholdFar`, `ThresholdNear`, `StopThreshold`.
- Backend: `FollowUpdateInterval`, `FollowerSpeedMultiplier`, `HeldIconDisplayHeight`.
- `InteractionSettings` (`IInteractionSettings`)
- Interactions: `PlayerStopDistance`, `PlayerStopDistanceDirectInteraction`, `FollowerPickupDelay`.
- Input/Layering: `InteractableLayerMask`.
- Prefabs: `BasePickupPrefab`, `LevelSwitchMenuPrefab`, `DefaultPuzzleIndicatorPrefab`.
- Puzzle/UI: `DefaultPuzzlePromptRange`.
- Items: `CombinationRules`, `SlotItemConfigs` plus helpers `GetCombinationRule(...)`, `GetSlotItemConfig(...)`.
- `DivingMinigameSettings` (`IDivingMinigameSettings`)
- Movement, spawning, scoring, surfacing, normalized movement, tile generation, obstacles, camera viewfinder settings, photo input mode (`PhotoInputModes`).
## Case Studies
### Tune Interaction Distances
```csharp
2025-10-21 12:37:53 +02:00
using AppleHills.Core.Settings;
2025-10-21 12:37:53 +02:00
public class InteractDistanceExample
{
2025-10-21 12:37:53 +02:00
private readonly IInteractionSettings _s = SettingsProvider.Instance.GetSettings<InteractionSettings>();
public bool IsInRange(float dist) => dist <= _s.PlayerStopDistance;
}
```
2025-10-21 12:37:53 +02:00
### Follower Handling & Movement
```csharp
2025-10-21 12:37:53 +02:00
using AppleHills.Core.Settings;
2025-10-21 12:37:53 +02:00
public class FollowerMover
{
2025-10-21 12:37:53 +02:00
private readonly IPlayerFollowerSettings _pf = SettingsProvider.Instance.GetSettings<PlayerFollowerSettings>();
public float TargetSpeed(float error) => Mathf.Clamp(error * _pf.FollowerSpeedMultiplier, 0f, _pf.MoveSpeed);
}
```
2025-10-21 12:37:53 +02:00
### Diving Minigame Tuning
```csharp
2025-10-21 12:37:53 +02:00
using AppleHills.Core.Settings;
2025-10-21 12:37:53 +02:00
public class SpawnController
{
2025-10-21 12:37:53 +02:00
private readonly IDivingMinigameSettings _m = SettingsProvider.Instance.GetSettings<DivingMinigameSettings>();
public float NextCooldown(float baseCooldown) => Mathf.Clamp(baseCooldown + Random.Range(-_m.ObstacleSpawnIntervalVariation, _m.ObstacleSpawnIntervalVariation), 0.1f, 99);
}
```
2025-10-21 12:37:53 +02:00
## Troubleshooting / FAQ
- Settings return null at runtime:
- Ensure assets are Addressable with keys `Settings/<TypeName>` and Addressables are initialized before first access.
- Editor changes dont reflect in scene gizmos:
- Click “Save All” in `AppleHills/Settings Editor`; the editor provider refresh call updates views.
- Which API to use: `SettingsProvider` vs `SettingsAccess`?
- Use `SettingsProvider` in runtime code. Use `SettingsAccess` in editor tools/gizmos or shared code that runs both in Edit and Play Modes.
## Paths & Namespaces
- Scripts: `Assets/Scripts/Core/Settings/`
- `BaseSettings.cs`
- `SettingsInterfaces.cs`
- `SettingsProvider.cs`
- `PlayerFollowerSettings.cs`
- `InteractionSettings.cs`
- `DivingMinigameSettings.cs`
2025-10-21 15:08:48 +02:00
- Editor tooling: `Assets/Editor/Settings/`
- `SettingsEditorWindow.cs`
- `DeveloperSettingsEditorWindow.cs`
- `EditorSettingsProvider.cs`
2025-10-21 12:37:53 +02:00
- Editor-time facade: `Assets/Scripts/Core/SettingsAccess.cs`
- Namespaces:
- Runtime: `AppleHills.Core.Settings`
2025-10-21 15:08:48 +02:00
- Editor windows: `AppleHills.Core.Settings.Editor`
- Editor glue: `AppleHills.Editor`
2025-10-21 12:37:53 +02:00
- Facade: `AppleHills`
2025-10-21 15:08:48 +02:00
## 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`). Heres 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<T>()` calls around the codebase.
2025-10-21 12:37:53 +02:00
## Change Log
2025-10-21 15:08:48 +02:00
- v1.2: Expanded architecture with `EditorSettingsProvider` and developer tooling; clarified Addressables key pattern vs asset filenames; added design opinion and recommendations.
2025-10-21 12:37:53 +02:00
- v1.1: New page with TOC, code-first usage, authoring workflow, Addressables keys, case studies, troubleshooting, and paths.