Update readme docs for our systems
This commit is contained in:
@@ -1,78 +1,196 @@
|
||||
# Camera Screen Adaptation System
|
||||
|
||||
This system helps adapt your 2D game's orthographic camera to different screen sizes, ensuring content is displayed consistently across various devices. It also provides tools for anchoring game objects to screen edges.
|
||||
Adapts your 2D orthographic camera to different aspect ratios while keeping gameplay/UI elements aligned to screen edges. Includes a reference marker for target content width and utilities for edge anchoring.
|
||||
|
||||
## 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)
|
||||
- [Create and Wire Components in Code](#create-and-wire-components-in-code)
|
||||
- [Manual Recalculate / Listen for Changes](#manual-recalculate--listen-for-changes)
|
||||
- [Components](#components)
|
||||
- [`ScreenReferenceMarker`](#screenreferencemarker)
|
||||
- [`CameraScreenAdapter`](#camerascreenadapter)
|
||||
- [`EdgeAnchor`](#edgeanchor)
|
||||
- [Common Case Studies](#common-case-studies)
|
||||
- [HUD Top Bar (safe top margin)](#hud-top-bar-safe-top-margin)
|
||||
- [Side Buttons (left/right margins)](#side-buttons-leftright-margins)
|
||||
- [Cinemachine Virtual Camera](#cinemachine-virtual-camera)
|
||||
- [Tips & Best Practices](#tips--best-practices)
|
||||
- [Troubleshooting / FAQ](#troubleshooting--faq)
|
||||
- [Paths & Namespaces](#paths--namespaces)
|
||||
- [Change Log](#change-log)
|
||||
|
||||
## What This Solves
|
||||
- Consistent horizontal framing across devices: a fixed target world-space width always fits the screen.
|
||||
- Stable world-space margins from the physical screen edges for placing HUD/world elements.
|
||||
- Works with both a regular `Camera` and `Cinemachine`'s `CinemachineCamera`.
|
||||
|
||||
## Architecture at a Glance
|
||||
- Reference: `ScreenReferenceMarker` defines `targetWidth` and world-space margins (`topMargin`, `bottomMargin`, `leftMargin`, `rightMargin`).
|
||||
- Adapter: `CameraScreenAdapter` computes orthographic size from `targetWidth` and aspect and applies it to the controlled camera. Emits `OnCameraAdjusted`.
|
||||
- Anchoring: `EdgeAnchor` positions objects relative to the computed screen edges using the same marker margins.
|
||||
|
||||
## Quick Start (Code-First)
|
||||
|
||||
### Create and Wire Components in Code
|
||||
```csharp
|
||||
using AppleHillsCamera;
|
||||
using UnityEngine;
|
||||
|
||||
public class CameraAdaptationBootstrap : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Camera mainCamera; // assign in inspector or find at runtime
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 1) Create/locate the marker
|
||||
var markerGO = new GameObject("ScreenReferenceMarker");
|
||||
var marker = markerGO.AddComponent<ScreenReferenceMarker>();
|
||||
marker.targetWidth = 16f; // your desired world-space width
|
||||
marker.topMargin = 1f; // tune margins for your HUD
|
||||
|
||||
// 2) Set up the adapter on your camera
|
||||
var adapter = mainCamera.gameObject.AddComponent<CameraScreenAdapter>();
|
||||
adapter.referenceMarker = marker;
|
||||
adapter.adjustOnStart = true;
|
||||
adapter.adjustOnScreenResize = true;
|
||||
|
||||
// 3) Anchor a sample HUD object to the top edge
|
||||
var hud = GameObject.CreatePrimitive(PrimitiveType.Quad);
|
||||
var anchor = hud.AddComponent<EdgeAnchor>();
|
||||
anchor.referenceMarker = marker;
|
||||
anchor.anchor = EdgeAnchor.AnchorEdge.Top;
|
||||
anchor.additionalOffset = 0.25f; // extra spacing from the top margin
|
||||
anchor.continuousUpdate = true; // keep tracking when aspect changes
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Manual Recalculate / Listen for Changes
|
||||
```csharp
|
||||
using AppleHillsCamera;
|
||||
using UnityEngine;
|
||||
|
||||
public class CameraAdaptationHooks : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private CameraScreenAdapter adapter;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// Listen when the camera size is applied
|
||||
adapter.OnCameraAdjusted += OnAdjusted;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
adapter.OnCameraAdjusted -= OnAdjusted;
|
||||
}
|
||||
|
||||
public void ForceRecalculateNow()
|
||||
{
|
||||
adapter.AdjustCamera();
|
||||
}
|
||||
|
||||
private void OnAdjusted()
|
||||
{
|
||||
Debug.Log("Camera adjusted to match target width.");
|
||||
// Reposition custom elements if you don’t use EdgeAnchor
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### 1. ScreenReferenceMarker
|
||||
### `ScreenReferenceMarker`
|
||||
Defines the reference sizes and margins used by both the adapter and anchors.
|
||||
|
||||
This component defines the target width and edge margins for your level content.
|
||||
Key fields:
|
||||
- `targetWidth` (float): world-space width that should fully fit the viewport.
|
||||
- `topMargin`, `bottomMargin`, `leftMargin`, `rightMargin` (float): world-space distances from the corresponding screen edges.
|
||||
- Scene gizmos: visualizes width line, screen rectangle, and margin guides in the editor.
|
||||
|
||||
- **Target Width**: The desired width to be displayed on screen
|
||||
- **Edge Margins**: Distances from screen edges for anchoring objects
|
||||
- **Visualization**: Editor-only visualization of the reference sizes
|
||||
Typical setup:
|
||||
- Place at the world-space center of your playable area or camera focus.
|
||||
- Tune margins to match your HUD/world layout needs.
|
||||
|
||||
### 2. CameraScreenAdapter
|
||||
### `CameraScreenAdapter`
|
||||
Computes and applies orthographic size to a `Camera` or `CinemachineCamera` so that the screen horizontally fits `targetWidth`.
|
||||
|
||||
Automatically adjusts the camera's orthographic size to ensure the target width (defined by the ScreenReferenceMarker) matches the screen width.
|
||||
Highlights:
|
||||
- Fields: `referenceMarker`, `adjustOnStart`, `adjustOnScreenResize`.
|
||||
- Event: `OnCameraAdjusted` fires after a size update.
|
||||
- Works with built-in `Camera` (must be orthographic) or with `CinemachineCamera`.
|
||||
|
||||
- **Reference Marker**: Link to your ScreenReferenceMarker
|
||||
- **Adapt on Resolution Change**: Automatically update when screen resolution changes
|
||||
Formula:
|
||||
- `orthoSize = (targetWidth / 2f) * (Screen.height / Screen.width)`.
|
||||
|
||||
### 3. EdgeAnchor
|
||||
Public helpers:
|
||||
- `AdjustCamera()` — recalculate and apply now.
|
||||
- `GetControlledCamera()` — returns the underlying `Camera` instance.
|
||||
|
||||
Anchors game objects to screen edges, maintaining consistent distance when the camera's orthographic size changes.
|
||||
### `EdgeAnchor`
|
||||
Anchors a `Transform` to a chosen screen edge using the same marker margins.
|
||||
|
||||
- **Anchor Edge**: Which edge to anchor to (Top, Bottom, Left, Right)
|
||||
- **Reference Marker**: Uses the same marker as the CameraScreenAdapter
|
||||
- **Additional Offset**: Extra distance from the edge margin
|
||||
- **Continuous Update**: Whether to update position every frame
|
||||
Common fields (names based on code):
|
||||
- `referenceMarker` (`ScreenReferenceMarker`)
|
||||
- `anchor` (`EdgeAnchor.AnchorEdge`): `Top`, `Bottom`, `Left`, `Right`
|
||||
- `additionalOffset` (float): extra spacing beyond the margin
|
||||
- `continuousUpdate` (bool): update every frame and on camera adjustments
|
||||
|
||||
### 4. CameraScreenVisualizer
|
||||
Usage tips:
|
||||
- Use `continuousUpdate` for elements that must track aspect changes at runtime.
|
||||
- Combine with local offsets/parenting for precise UI/HUD placements in world space.
|
||||
|
||||
Helper component for visualizing camera boundaries in the editor.
|
||||
## Common Case Studies
|
||||
|
||||
## Setup Instructions
|
||||
### HUD Top Bar (safe top margin)
|
||||
- Set `marker.topMargin` to the vertical padding you want from the top edge.
|
||||
- Add `EdgeAnchor` to your top bar root:
|
||||
```csharp
|
||||
anchor.anchor = EdgeAnchor.AnchorEdge.Top;
|
||||
anchor.additionalOffset = 0.1f; // small breathing room
|
||||
```
|
||||
- Keep `continuousUpdate = true` for device rotation/platform switches.
|
||||
|
||||
### Basic Setup
|
||||
### Side Buttons (left/right margins)
|
||||
- For a left button column:
|
||||
```csharp
|
||||
anchor.anchor = EdgeAnchor.AnchorEdge.Left;
|
||||
anchor.additionalOffset = 0.2f;
|
||||
```
|
||||
- Mirror for right side with `Right`.
|
||||
- Adjust `marker.leftMargin/rightMargin` to push them inward safely.
|
||||
|
||||
1. **Create the Reference Marker:**
|
||||
- Add a new empty GameObject to your scene
|
||||
- Add the `ScreenReferenceMarker` component to it
|
||||
- Position it at the center of your target view
|
||||
- Set the `targetWidth` to match your level's ideal width
|
||||
- Adjust margin values as needed
|
||||
### Cinemachine Virtual Camera
|
||||
- Add `CameraScreenAdapter` to the GameObject with `CinemachineCamera`.
|
||||
- The adapter detects Cinemachine and writes to `Lens.OrthographicSize`.
|
||||
- Everything else (margins and `EdgeAnchor`) works the same.
|
||||
|
||||
2. **Configure the Camera:**
|
||||
- Add the `CameraScreenAdapter` component to your main camera
|
||||
- Reference the ScreenReferenceMarker you created
|
||||
- Optionally, add `CameraScreenVisualizer` for better editor visualization
|
||||
## Tips & Best Practices
|
||||
- Pick a `targetWidth` that matches the core gameplay area horizontally; verify extremes (16:9, 19.5:9, 4:3).
|
||||
- Prefer `EdgeAnchor` for HUD/world labels instead of manual scripts—anchors auto-update on resize and on `OnCameraAdjusted`.
|
||||
- For one-off bespoke layouts, listen to `OnCameraAdjusted` and move objects yourself.
|
||||
- Ensure regular cameras are set to `orthographic`; the adapter warns if not.
|
||||
|
||||
3. **Anchor Game Objects:**
|
||||
- For any objects that should maintain position relative to screen edges:
|
||||
- Add the `EdgeAnchor` component
|
||||
- Set the desired anchor edge (e.g., Top)
|
||||
- Reference the same ScreenReferenceMarker
|
||||
- Adjust additional offset if needed
|
||||
## Troubleshooting / FAQ
|
||||
- My camera didn’t change size:
|
||||
- Ensure `referenceMarker` is assigned.
|
||||
- For built-in `Camera`, confirm `orthographic` is enabled.
|
||||
- If using Cinemachine, verify the component is `CinemachineCamera` on the same GameObject.
|
||||
- Anchored objects are offset oddly:
|
||||
- Check `additionalOffset` and your object’s pivot/bounds.
|
||||
- Verify marker margins and that the marker sits at the world center you expect.
|
||||
- It works in Play but gizmos look wrong in Scene:
|
||||
- Scene gizmos approximate using Scene/Game view sizes; rely on Play Mode for truth.
|
||||
|
||||
### Example: Top-Edge Anchoring
|
||||
## Paths & Namespaces
|
||||
- Scripts: `Assets/Scripts/AppleHillsCamera/`
|
||||
- `ScreenReferenceMarker.cs`
|
||||
- `CameraScreenAdapter.cs`
|
||||
- `EdgeAnchor.cs`
|
||||
- Namespace: `AppleHillsCamera`
|
||||
|
||||
For objects that need to stay a fixed distance from the top of the screen:
|
||||
|
||||
1. Set up your ScreenReferenceMarker with an appropriate topMargin value
|
||||
2. Add EdgeAnchor to the object you want to anchor
|
||||
3. Set AnchorEdge to Top
|
||||
4. Adjust additionalOffset for fine-tuning
|
||||
|
||||
## Tips for Level Design
|
||||
|
||||
- Place the ScreenReferenceMarker at the center of your scene's critical content
|
||||
- Use the visual gizmos to see how your reference width relates to camera boundaries
|
||||
- Test your scene in different aspect ratios to ensure proper adaptation
|
||||
- For complex levels, you might want multiple reference markers for different sections
|
||||
|
||||
## Runtime Considerations
|
||||
|
||||
- The system automatically adapts when the screen size changes
|
||||
- The CameraScreenAdapter can be disabled if you need to temporarily override the automatic sizing
|
||||
- EdgeAnchor components can be disabled when their objects don't need to maintain edge alignment
|
||||
## Change Log
|
||||
- v1.1: Added Table of Contents, code-first snippets, case studies, and aligned terms with actual APIs (`OnCameraAdjusted`, Cinemachine support).
|
||||
- v1.0: Original overview and setup guide.
|
||||
|
||||
@@ -2,10 +2,73 @@
|
||||
|
||||
This document provides an overview of the dialogue system used in Apple Hills, intended primarily for designers (Damian) working with the dialogue creation tools.
|
||||
|
||||
## Table of Contents
|
||||
- [Overview](#overview)
|
||||
- [Architecture at a Glance](#architecture-at-a-glance)
|
||||
- [Quick Start (Code-First)](#quick-start-code-first)
|
||||
- [QuickStart Guide (Editor-First)](#quickstart-guide)
|
||||
- [Dialogue Structure](#dialogue-structure)
|
||||
- [Node Types](#node-types)
|
||||
- [Dialogue Editor](#dialogue-editor)
|
||||
- [Designer Tips](#designer-tips)
|
||||
- [Technical Details](#technical-details)
|
||||
- [Paths & Namespaces](#paths--namespaces)
|
||||
- [Troubleshooting / FAQ](#troubleshooting--faq)
|
||||
- [Change Log](#change-log)
|
||||
|
||||
## Overview
|
||||
|
||||
The Apple Hills dialogue system is a node-based dialogue management system that allows for interactive conversations with NPCs. The system currently supports linear, condition-guarded dialogue paths that can respond to various game conditions such as puzzle completion, item pickups, and item combinations. While the architecture was designed to facilitate branching dialogue in future extensions, the current implementation follows a linear progression through nodes.
|
||||
|
||||
## Architecture at a Glance
|
||||
- Authoring Asset: `RuntimeDialogueGraph` (`ScriptableObject`) defines speaker name, entry node (`entryNodeID`), and all nodes (`allNodes`). Each `RuntimeDialogueNode` has a `nodeType`, `nextNodeID`, and content fields (`dialogueContent` for text/images and legacy `dialogueLines`).
|
||||
- Runtime Driver: `DialogueComponent` sits on an NPC/prop, subscribes to gameplay events (puzzle steps, item pickups/slots/combinations), and advances the graph. Exposes `OnDialogueChanged`, `IsActive`, `IsCompleted`, and helpers like `StartDialogue()`, `HasAnyLines()`, `GetCurrentDialogueLine()`, `SetDialogueGraph(...)`.
|
||||
- Presentation: `SpeechBubble` renders text or images with instant/typewriter modes and manages a prompt icon when dialogue is available.
|
||||
- External Systems: `PuzzleManager` and `ItemManager` raise events that automatically progress `WaitOn*` nodes.
|
||||
|
||||
## Quick Start (Code-First)
|
||||
|
||||
### Minimal bootstrap in code
|
||||
```csharp
|
||||
using Dialogue;
|
||||
using UnityEngine;
|
||||
|
||||
public class DialogueBootstrap : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private DialogueComponent npcDialogue;
|
||||
[SerializeField] private RuntimeDialogueGraph graphAsset; // assign via Inspector
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 1) Assign a graph at runtime (optional; can also be preassigned in Inspector)
|
||||
npcDialogue.SetDialogueGraph(graphAsset);
|
||||
|
||||
// 2) Optionally subscribe to changes (for analytics/UI hooks)
|
||||
npcDialogue.OnDialogueChanged += line => Debug.Log($"[Dialogue] {npcDialogue.CurrentSpeakerName}: {line}");
|
||||
|
||||
// 3) Start the dialogue flow programmatically (optional)
|
||||
npcDialogue.StartDialogue();
|
||||
|
||||
// Speech bubble prompt visibility is managed automatically via HasAnyLines()
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
npcDialogue.OnDialogueChanged -= null; // Clean up if you subscribed with a lambda, store a delegate instead in real code
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Trigger an interaction by code (bypass Interactable)
|
||||
The component usually advances when the player arrives and interacts (via `Interactable`). If you need to force-show the next line (e.g., a cutscene), you can invoke the same display pathway by starting the dialogue and letting the component manage advancement when it next receives input. For custom flows, prefer wiring your input to call `StartDialogue()` initially and rely on the built-in progression.
|
||||
|
||||
### Switch graphs at runtime
|
||||
```csharp
|
||||
// Swap to a new conversation (e.g., after quest completes)
|
||||
npcDialogue.SetDialogueGraph(newGraph);
|
||||
npcDialogue.StartDialogue();
|
||||
```
|
||||
|
||||
## Dialogue Structure
|
||||
|
||||
The dialogue system uses a graph-based structure with different types of nodes that control the flow of conversation. Each dialogue is represented as a graph containing multiple nodes connected through defined paths.
|
||||
@@ -14,21 +77,20 @@ The dialogue system uses a graph-based structure with different types of nodes t
|
||||
|
||||
### Core Components
|
||||
|
||||
1. **RuntimeDialogueGraph**: The container for all dialogue nodes that make up a conversation
|
||||
- Defined in `Assets/Scripts/Dialogue/RuntimeDialogueGraph.cs`
|
||||
- Contains the entry point node ID and a list of all dialogue nodes
|
||||
- Holds the speaker name that appears in dialogue UI
|
||||
1. `RuntimeDialogueGraph`: Container for dialogue nodes that make up a conversation
|
||||
- File: `Assets/Scripts/Dialogue/RuntimeDialogueGraph.cs`
|
||||
- Fields: `entryNodeID`, `speakerName`, `allNodes`
|
||||
- Helper: `GetNodeByID(string id)`
|
||||
|
||||
2. **DialogueComponent**: Attached to game objects to manage dialogue state and progression
|
||||
- Defined in `Assets/Scripts/Dialogue/DialogueComponent.cs`
|
||||
- Manages the current state of dialogue (active node, line index)
|
||||
- Responds to game events like puzzle completion, item pickup, etc.
|
||||
- Controls dialogue advancement through player interaction
|
||||
2. `DialogueComponent`: Manages dialogue state and progression on an NPC/prop
|
||||
- File: `Assets/Scripts/Dialogue/DialogueComponent.cs`
|
||||
- Tracks current node/line; responds to puzzle/item events
|
||||
- Public: `StartDialogue()`, `GetCurrentDialogueLine()`, `HasAnyLines()`, `SetDialogueGraph(RuntimeDialogueGraph)`
|
||||
- Events/Props: `OnDialogueChanged`, `IsActive`, `IsCompleted`, `CurrentSpeakerName`
|
||||
|
||||
3. **SpeechBubble**: Handles the visual presentation of dialogue text
|
||||
- Defined in `Assets/Scripts/Dialogue/SpeechBubble.cs`
|
||||
- Manages the dialogue UI elements and text display
|
||||
- Implements typewriter effects and visual prompts
|
||||
3. `SpeechBubble`: Visual presentation of dialogue text/images and prompt
|
||||
- File: `Assets/Scripts/Dialogue/SpeechBubble.cs`
|
||||
- Public: `Show()`, `Hide()`, `Toggle()`, `SetText(string)`, `DisplayDialogueLine(string,bool)`, `DisplayDialogueContent(DialogueContent,bool)`, `UpdatePromptVisibility(bool)`, `SetDisplayMode(TextDisplayMode)`, `SkipTypewriter()`, `SetTypewriterSpeed(float)`
|
||||
|
||||
## QuickStart Guide
|
||||
|
||||
@@ -275,3 +337,35 @@ The dialogue system integrates with several other game systems:
|
||||
3. Assign the graph to a DialogueComponent on an NPC GameObject
|
||||
4. Ensure a SpeechBubble component is available (as a child object or referenced)
|
||||
5. Set up any necessary puzzle steps, items, or slots that the dialogue will reference
|
||||
|
||||
|
||||
## Paths & Namespaces
|
||||
- Scripts: `Assets/Scripts/Dialogue/`
|
||||
- `RuntimeDialogueGraph.cs`
|
||||
- `DialogueComponent.cs`
|
||||
- `SpeechBubble.cs`
|
||||
- Editor tooling: `Assets/Editor/Dialogue/`
|
||||
- `DialogueGraph.cs` (graph editor)
|
||||
- `DialogueNodes.cs` (node types for editor)
|
||||
- `DialogueGraphImporter.cs` (asset importer)
|
||||
- Namespace: `Dialogue`
|
||||
|
||||
## Troubleshooting / FAQ
|
||||
- The prompt (“…”) never shows:
|
||||
- Ensure a `SpeechBubble` exists under the same hierarchy and its serialized references are assigned.
|
||||
- Check `DialogueComponent.HasAnyLines()` returns true; verify your graph’s `entryNodeID` and that nodes have content.
|
||||
- Confirm `Interactable` is present and wired if you rely on proximity to trigger dialogue.
|
||||
- Conditions don’t trigger (puzzle/item):
|
||||
- Make sure `PuzzleManager` and `ItemManager` are present in the scene and raise the expected events.
|
||||
- Double-check IDs used in nodes (`puzzleStepID`, `pickupItemID`, `slotItemID`, `combinationResultItemID`).
|
||||
- Text doesn’t animate:
|
||||
- Use `SpeechBubble.SetDisplayMode(TextDisplayMode.Typewriter)` and adjust with `SetTypewriterSpeed()`.
|
||||
- Call `SkipTypewriter()` to reveal the full text instantly.
|
||||
- I want to switch to image-based dialogue:
|
||||
- Use `RuntimeDialogueNode.dialogueContent` and set `DialogueContentType.Image` entries instead of legacy `dialogueLines`.
|
||||
- Can I drive it purely by code?
|
||||
- Yes. Assign a `RuntimeDialogueGraph` via `SetDialogueGraph(...)`, call `StartDialogue()`, and let `DialogueComponent` progress based on input or external events.
|
||||
|
||||
## Change Log
|
||||
- v1.1: Added Table of Contents, Architecture at a Glance, code-first snippets, standardized inline code formatting, and appended Paths/Troubleshooting. Preserved all existing image references.
|
||||
- v1.0: Original overview and setup guide.
|
||||
|
||||
@@ -1,275 +1,253 @@
|
||||
# Apple Hills Interaction System
|
||||
|
||||
This document provides a comprehensive overview of the interaction system in Apple Hills, detailing how interactions are structured, configured, and extended with custom actions.
|
||||
A concise, code-first guide to creating and extending interactions using `Interactable` and modular action/requirement components. Designed to match the style of the other updated docs (TOC, inline code, case studies).
|
||||
|
||||
## Overview
|
||||
## 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)
|
||||
- [Subscribe to Interaction Events](#subscribe-to-interaction-events)
|
||||
- [Create a Custom Action](#create-a-custom-action)
|
||||
- [Trigger Programmatically](#trigger-programmatically)
|
||||
- [Core Components](#core-components)
|
||||
- [`Interactable`](#interactable)
|
||||
- [`CharacterMoveToTarget`](#charactermovetotarget)
|
||||
- [`InteractionActionBase` and concrete actions](#interactionactionbase-and-concrete-actions)
|
||||
- [`InteractionRequirementBase`](#interactionrequirementbase)
|
||||
- [Interaction Event Flow](#interaction-event-flow)
|
||||
- [Case Studies](#case-studies)
|
||||
- [Open a Door on Arrival](#open-a-door-on-arrival)
|
||||
- [Pick Up an Item then Play Timeline](#pick-up-an-item-then-play-timeline)
|
||||
- [Kick Off Dialogue When Player Arrives](#kick-off-dialogue-when-player-arrives)
|
||||
- [Troubleshooting / FAQ](#troubleshooting--faq)
|
||||
- [Paths & Namespaces](#paths--namespaces)
|
||||
- [Change Log](#change-log)
|
||||
|
||||
The Apple Hills interaction system allows players to interact with objects in the game world. It supports character movement to interaction points, timed and conditional interactions, and complex behaviors through a component-based architecture. The system is particularly powerful when combined with the Timeline feature for creating cinematic sequences during interactions.
|
||||
## What This Solves
|
||||
- Standardized interaction lifecycle with reliable events (`InteractionStarted`, `PlayerArrived`, `InteractingCharacterArrived`, `InteractionComplete`, `InteractionInterrupted`).
|
||||
- Composable behavior via components derived from `InteractionActionBase` and `InteractionRequirementBase`.
|
||||
- Clean separation of input, locomotion-to-target, cinematic timelines, and game logic.
|
||||
|
||||
## Architecture at a Glance
|
||||
- Driver: `Interactable` — owns lifecycle, input hook, character selection via `CharacterToInteract`, one‑shot/cooldown, and event dispatch.
|
||||
- Targets: `CharacterMoveToTarget` — editor-authored world points for `Trafalgar`/`Pulver` to path to before executing actions.
|
||||
- Actions: `InteractionActionBase` (abstract) — modular responses to specific `InteractionEventType` values; can pause the flow with async tasks.
|
||||
- Requirements: `InteractionRequirementBase` (abstract) — gatekeepers for availability; multiple can be attached.
|
||||
- Cinematics: `InteractionTimelineAction` — plays one or more `PlayableAsset` timelines per event; optional character auto-binding.
|
||||
|
||||
## Quick Start (Code-First)
|
||||
|
||||
### Subscribe to Interaction Events
|
||||
```csharp
|
||||
using Interactions;
|
||||
using UnityEngine;
|
||||
|
||||
public class InteractDebugHooks : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Interactable interactable;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
interactable.interactionStarted.AddListener(OnStarted);
|
||||
interactable.characterArrived.AddListener(OnCharacterArrived);
|
||||
interactable.interactionInterrupted.AddListener(OnInterrupted);
|
||||
interactable.interactionComplete.AddListener(OnComplete);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
interactable.interactionStarted.RemoveListener(OnStarted);
|
||||
interactable.characterArrived.RemoveListener(OnCharacterArrived);
|
||||
interactable.interactionInterrupted.RemoveListener(OnInterrupted);
|
||||
interactable.interactionComplete.RemoveListener(OnComplete);
|
||||
}
|
||||
|
||||
private void OnStarted(Input.PlayerTouchController player, FollowerController follower)
|
||||
=> Debug.Log("Interaction started");
|
||||
|
||||
private void OnCharacterArrived() => Debug.Log("Character arrived");
|
||||
private void OnInterrupted() => Debug.Log("Interaction interrupted");
|
||||
private void OnComplete(bool success) => Debug.Log($"Interaction complete: {success}");
|
||||
}
|
||||
```
|
||||
|
||||
### Create a Custom Action
|
||||
```csharp
|
||||
using System.Threading.Tasks;
|
||||
using Interactions;
|
||||
using Input;
|
||||
using UnityEngine;
|
||||
|
||||
public class PlaySfxOnArrivalAction : InteractionActionBase
|
||||
{
|
||||
[SerializeField] private AudioSource sfx;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
// React to the arrival event; don't block the flow
|
||||
respondToEvents = new() { InteractionEventType.InteractingCharacterArrived };
|
||||
pauseInteractionFlow = false;
|
||||
}
|
||||
|
||||
protected override bool ShouldExecute(InteractionEventType evt, PlayerTouchController player, FollowerController follower)
|
||||
{
|
||||
return sfx != null;
|
||||
}
|
||||
|
||||
protected override async Task<bool> ExecuteAsync(InteractionEventType evt, PlayerTouchController player, FollowerController follower)
|
||||
{
|
||||
sfx.Play();
|
||||
// non-blocking action returns immediately when pauseInteractionFlow == false
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
Attach this component under the same hierarchy as an `Interactable`. Registration is automatic via `OnEnable()`/`OnDisable()` in `InteractionActionBase`.
|
||||
|
||||
### Trigger Programmatically
|
||||
Normally input goes through `ITouchInputConsumer.OnTap(...)`. For testing, you can call the public tap handler:
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using Interactions;
|
||||
|
||||
public class TestTrigger : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Interactable interactable;
|
||||
|
||||
[ContextMenu("Trigger Interact (dev)")]
|
||||
private void Trigger()
|
||||
{
|
||||
interactable.OnTap(interactable.transform.position);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Core Components
|
||||
|
||||
The interaction system consists of several key components that work together:
|
||||
|
||||
### Interactable
|
||||
|
||||
The `Interactable` component is the foundation of the interaction system. It:
|
||||
- Handles player input (tapping/clicking)
|
||||
- Manages which character(s) should interact (Trafalgar, Pulver, or both)
|
||||
- Coordinates character movement to interaction points
|
||||
- Dispatches events during the interaction lifecycle
|
||||
- Manages one-time interactions and cooldowns
|
||||
### `Interactable`
|
||||
- Handles input, cooldowns (`cooldown`), one‑shot (`isOneTime`), and which character participates (`characterToInteract`).
|
||||
- Exposes events: `interactionStarted`, `characterArrived`, `interactionInterrupted`, `interactionComplete`.
|
||||
- Discovers and dispatches to child `InteractionActionBase` components; awaits those that request to pause.
|
||||
|
||||

|
||||
|
||||
### CharacterMoveToTarget
|
||||
|
||||
The `CharacterMoveToTarget` component defines positions where characters should move when interacting:
|
||||
- Can be configured for specific characters (Trafalgar, Pulver, or both)
|
||||
- Supports position offsets for fine-tuning
|
||||
- Provides visual gizmos in the editor for easy positioning
|
||||
- Multiple targets can be set up for complex interactions
|
||||
### `CharacterMoveToTarget`
|
||||
Defines the world positions characters should reach before actions evaluate.
|
||||
- Can target `Trafalgar`, `Pulver`, or `Both` via configuration.
|
||||
- Supports offsets and editor gizmos; multiple instances allowed.
|
||||
|
||||

|
||||
|
||||
### Interaction Actions
|
||||
|
||||
Actions are components that respond to interaction events and execute custom behavior:
|
||||
- Derive from the abstract `InteractionActionBase` class
|
||||
- Can be attached to interactable objects
|
||||
- Multiple actions can be added to a single interactable
|
||||
- Actions can optionally block the interaction flow until completion
|
||||
|
||||
The inspector for all interaction action components shows the key parameters from InteractionActionBase, with custom configuration options for specific action types:
|
||||
### `InteractionActionBase` and concrete actions
|
||||
- Filter by `InteractionEventType` using `respondToEvents`.
|
||||
- Control flow with `pauseInteractionFlow` and async `ExecuteAsync(...)`.
|
||||
- Built‑in example: `InteractionTimelineAction` for cinematics.
|
||||
|
||||

|
||||
|
||||
### Interaction Requirements
|
||||
|
||||
Requirements are components that determine whether an interaction can occur:
|
||||
- Derive from the abstract `InteractionRequirementBase` class
|
||||
- Can prevent interactions based on custom conditions
|
||||
- Multiple requirements can be added to a single interactable
|
||||
- Used for creating conditional interactions (e.g., requiring an item)
|
||||
### `InteractionRequirementBase`
|
||||
- Attach one or more to gate the interaction based on items, puzzles, proximity, etc.
|
||||
|
||||
## Interaction Event Flow
|
||||
1. `InteractionStarted`
|
||||
2. `PlayerArrived`
|
||||
3. `InteractingCharacterArrived`
|
||||
4. `InteractionComplete` (bool success)
|
||||
5. `InteractionInterrupted`
|
||||
|
||||
Interactions follow a defined event flow:
|
||||
Actions receive these events in order and may run concurrently; those with `pauseInteractionFlow` true are awaited.
|
||||
|
||||
1. **InteractionStarted**: Triggered when the player initiates an interaction
|
||||
2. **PlayerArrived**: Triggered when the player character reaches the interaction point
|
||||
3. **InteractingCharacterArrived**: Triggered when the interacting character (often Pulver) reaches the interaction point
|
||||
4. **InteractionComplete**: Triggered when the interaction is completed
|
||||
5. **InteractionInterrupted**: Triggered if the interaction is interrupted before completion
|
||||
|
||||
## InteractionActionBase
|
||||
|
||||
The `InteractionActionBase` is the abstract base class for all interaction actions. It provides the framework for creating custom behaviors that respond to interaction events.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Event Filtering**: Actions can choose which interaction events to respond to
|
||||
- **Flow Control**: Actions can optionally pause the interaction flow until completion
|
||||
- **Asynchronous Execution**: Actions use `async/await` pattern for time-consuming operations
|
||||
- **Character References**: Actions receive references to both player and follower characters
|
||||
|
||||
### Implementation
|
||||
## Case Studies
|
||||
|
||||
### Open a Door on Arrival
|
||||
```csharp
|
||||
public abstract class InteractionActionBase : MonoBehaviour
|
||||
using System.Threading.Tasks;
|
||||
using Interactions;
|
||||
using Input;
|
||||
using UnityEngine;
|
||||
|
||||
public class DoorOpenOnArrival : InteractionActionBase
|
||||
{
|
||||
// Which events this action should respond to
|
||||
public List<InteractionEventType> respondToEvents;
|
||||
[SerializeField] private Animator animator; // expects a bool parameter "Open"
|
||||
|
||||
// Whether to pause the interaction flow during execution
|
||||
public bool pauseInteractionFlow;
|
||||
|
||||
// The main execution method that must be implemented by derived classes
|
||||
protected abstract Task<bool> ExecuteAsync(
|
||||
InteractionEventType eventType,
|
||||
PlayerTouchController player,
|
||||
FollowerController follower);
|
||||
|
||||
// Optional method for adding execution conditions
|
||||
protected virtual bool ShouldExecute(
|
||||
InteractionEventType eventType,
|
||||
PlayerTouchController player,
|
||||
FollowerController follower)
|
||||
private void Reset()
|
||||
{
|
||||
respondToEvents = new() { InteractionEventType.InteractingCharacterArrived };
|
||||
pauseInteractionFlow = false;
|
||||
}
|
||||
|
||||
protected override async Task<bool> ExecuteAsync(InteractionEventType evt, PlayerTouchController p, FollowerController f)
|
||||
{
|
||||
animator.SetBool("Open", true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## InteractionTimelineAction
|
||||
|
||||
The `InteractionTimelineAction` is a powerful action that plays Unity Timeline sequences during interactions. It enables cinematic sequences, character animations, camera movements, and more.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Multiple Timeline Support**: Can play different timelines for different interaction events
|
||||
- **Timeline Sequences**: Can play multiple timelines in sequence for a single event
|
||||
- **Character Binding**: Automatically binds player and follower characters to timeline tracks
|
||||
- **Flow Control**: Waits for timeline completion before continuing interaction flow
|
||||
- **Timeout Safety**: Includes a configurable timeout to prevent interactions from getting stuck
|
||||
- **Looping Options**: Supports looping all timelines or just the last timeline in a sequence
|
||||
|
||||
### Timeline Event Mapping
|
||||
|
||||
Each mapping connects an interaction event to one or more timeline assets:
|
||||
### Pick Up an Item then Play Timeline
|
||||
Attach two actions: your `PickupItemAction` that pauses until the item is collected, and an `InteractionTimelineAction` mapped to `InteractionEventType.InteractionComplete` to celebrate.
|
||||
|
||||
### Kick Off Dialogue When Player Arrives
|
||||
```csharp
|
||||
public class TimelineEventMapping
|
||||
using System.Threading.Tasks;
|
||||
using Dialogue;
|
||||
using Input;
|
||||
using Interactions;
|
||||
using UnityEngine;
|
||||
|
||||
public class StartDialogueOnArrival : InteractionActionBase
|
||||
{
|
||||
// The event that triggers this timeline
|
||||
public InteractionEventType eventType;
|
||||
[SerializeField] private DialogueComponent dialogue;
|
||||
|
||||
// The timeline assets to play (in sequence)
|
||||
public PlayableAsset[] timelines;
|
||||
private void Reset()
|
||||
{
|
||||
respondToEvents = new() { InteractionEventType.PlayerArrived };
|
||||
pauseInteractionFlow = false;
|
||||
}
|
||||
|
||||
// Character binding options
|
||||
public bool bindPlayerCharacter;
|
||||
public bool bindPulverCharacter;
|
||||
public string playerTrackName = "Player";
|
||||
public string pulverTrackName = "Pulver";
|
||||
|
||||
// Playback options
|
||||
public float timeoutSeconds = 30f;
|
||||
public bool loopLast = false;
|
||||
public bool loopAll = false;
|
||||
protected override async Task<bool> ExecuteAsync(InteractionEventType evt, PlayerTouchController p, FollowerController f)
|
||||
{
|
||||
dialogue.StartDialogue();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Editor
|
||||
## Troubleshooting / FAQ
|
||||
- Interaction doesn’t fire:
|
||||
- Confirm `Interactable` is active and not in cooldown or already completed (`isOneTime`).
|
||||
- Ensure `CharacterMoveToTarget` exists for the selected `CharacterToInteract`.
|
||||
- Actions not running:
|
||||
- Verify `respondToEvents` includes the lifecycle moment you expect.
|
||||
- Check that the component sits under the same hierarchy so it registers with the `Interactable`.
|
||||
- Timeline never finishes:
|
||||
- Make sure `InteractionTimelineAction` has valid `PlayableAsset` entries and binding flags.
|
||||
- Double triggers:
|
||||
- Guard reentry in your actions or check `_interactionInProgress` usage in `Interactable` by following logs.
|
||||
|
||||
The `InteractionTimelineAction` includes a custom editor that makes it easy to configure:
|
||||
- Quick buttons to add mappings for common events
|
||||
- Character binding options
|
||||
- Timeline sequence configuration
|
||||
- Validation warnings for misconfigured timelines
|
||||
## Paths & Namespaces
|
||||
- Scripts: `Assets/Scripts/Interactions/`
|
||||
- `Interactable.cs`
|
||||
- `InteractionActionBase.cs`
|
||||
- `InteractionTimelineAction.cs`
|
||||
- `InteractionEventType.cs`
|
||||
- `InteractionRequirementBase.cs`
|
||||
- Editor tooling: `Assets/Editor/InteractableEditor.cs`
|
||||
- Primary namespace: `Interactions`
|
||||
|
||||
## Additional Editor Visuals
|
||||
- Timeline mapping configuration UI:
|
||||
|
||||

|
||||
|
||||
### Implementation Pattern
|
||||
|
||||
For a cinematic interaction with timelines:
|
||||
|
||||
1. Add an `Interactable` component to your object
|
||||
2. Add an `InteractionTimelineAction` component to the same object
|
||||
3. Set up character move targets if needed
|
||||
4. Create timeline assets for each interaction phase
|
||||
5. Configure the timeline mappings in the inspector
|
||||
6. Test the interaction in play mode
|
||||
|
||||
### Timeline Configuration
|
||||
|
||||
When setting up a timeline for interaction, you'll need to create a Timeline asset and configure it in Unity's Timeline editor:
|
||||
- Unity Timeline editor when authoring cinematics for interactions:
|
||||
|
||||

|
||||
|
||||
## Working with the Interactable Editor
|
||||
|
||||
The `Interactable` component includes a custom editor that enhances the workflow:
|
||||
|
||||
### Character Move Target Creation
|
||||
|
||||
The editor provides buttons to easily create character move targets:
|
||||
- "Add Trafalgar Target" - Creates a move target for the player character
|
||||
- "Add Pulver Target" - Creates a move target for the follower character
|
||||
- "Add Both Characters Target" - Creates a move target for both characters
|
||||
|
||||

|
||||
|
||||
### Target Visualization
|
||||
|
||||
The editor displays the number of targets for each character type and warns about potential conflicts:
|
||||
```
|
||||
Trafalgar Targets: 1, Pulver Targets: 1, Both Targets: 0
|
||||
```
|
||||
|
||||
If multiple conflicting targets are detected, a warning is displayed to help prevent unexpected behavior.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Target Positioning
|
||||
|
||||
- Place character targets with appropriate spacing to prevent characters from overlapping
|
||||
- Consider the character's facing direction (targets automatically make characters face the interactable)
|
||||
- Use the position offset for fine-tuning without moving the actual target GameObject
|
||||
- Example target placement in Scene view:
|
||||
|
||||

|
||||
|
||||
### Timeline Design
|
||||
|
||||
- Keep timelines modular and focused on specific actions
|
||||
- Use signals to trigger game events from timelines
|
||||
- Consider using director notification tracks for advanced timeline integration
|
||||
- Test timelines with actual characters to ensure animations blend correctly
|
||||
|
||||
### Action Combinations
|
||||
|
||||
- Combine multiple actions for complex behaviors (e.g., dialogue + timeline)
|
||||
- Order actions in the Inspector to control execution priority
|
||||
- Use the `pauseInteractionFlow` option strategically to control sequence flow
|
||||
|
||||
## Technical Reference
|
||||
|
||||
### InteractionEventType
|
||||
|
||||
```csharp
|
||||
public enum InteractionEventType
|
||||
{
|
||||
InteractionStarted, // When interaction is first triggered
|
||||
PlayerArrived, // When player arrives at interaction point
|
||||
InteractingCharacterArrived, // When interacting character arrives
|
||||
InteractionComplete, // When interaction is successfully completed
|
||||
InteractionInterrupted // When interaction is interrupted
|
||||
}
|
||||
```
|
||||
|
||||
### CharacterToInteract
|
||||
|
||||
```csharp
|
||||
public enum CharacterToInteract
|
||||
{
|
||||
None, // No character interactions
|
||||
Trafalgar, // Player character only
|
||||
Pulver, // Follower character only
|
||||
Both // Both characters
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
- **Characters not moving to targets**: Ensure targets have the correct CharacterToInteract type set
|
||||
- **Timeline not playing**: Check that the PlayableDirector has a reference to the timeline asset
|
||||
- **Characters not appearing in timeline**: Verify the track names match the binding configuration
|
||||
- **Interaction getting stuck**: Make sure timelines have a reasonable timeout value set
|
||||
- **Multiple timelines playing simultaneously**: Check that the event mappings are correctly configured
|
||||
|
||||
|
||||
## Setup Example
|
||||
|
||||
Here's how a typical interaction setup might look in the Inspector:
|
||||
|
||||
1. Interactable component with appropriate settings
|
||||
2. Character move targets positioned in the scene
|
||||
3. InteractionTimelineAction component configured with timeline mappings
|
||||
4. PlayableDirector component referencing timeline assets
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
### Timeline Integration with Dialogue
|
||||
|
||||
Timeline actions can be synchronized with dialogue using:
|
||||
- Animation tracks to trigger dialogue displays
|
||||
- Signal tracks to advance dialogue
|
||||
- Custom markers to synchronize dialogue with character animations
|
||||
|
||||
### Interaction Sequences
|
||||
|
||||
Complex interaction sequences can be created by:
|
||||
- Using multiple interactables in sequence
|
||||
- Enabling/disabling interactables based on game state
|
||||
- Using timelines to guide the player through sequential interactions
|
||||
## Change Log
|
||||
- v1.1: Added Table of Contents, code-first snippets, case studies, standardized inline code references, preserved existing editor images, and added troubleshooting/paths.
|
||||
- v1.0: Original overview and setup guide.
|
||||
|
||||
@@ -1,398 +1,186 @@
|
||||
# Apple Hills Settings System
|
||||
|
||||

|
||||
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).
|
||||
|
||||
## Overview
|
||||
## 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)
|
||||
- [Change Log](#change-log)
|
||||
|
||||
The Apple Hills settings system consists of two main categories:
|
||||
- **Game Settings**: Designed for gameplay parameters and configuration values that affect the player experience
|
||||
- **Developer Settings**: Technical settings used for development, debugging, and behind-the-scenes functionality that we don't want Angry Argentinians to accidentaly overwrite
|
||||
## 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.
|
||||
|
||||
The settings framework uses ScriptableObjects to store configuration data, making it easy to modify values in the editor and reference them in code. Settings are organized into logical groups and can be accessed through custom editor windows or directly in code.
|
||||
## 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/<TypeName>` 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.
|
||||
|
||||
Benefits of this approach include:
|
||||
- Central location for all configuration values
|
||||
- Type-safe access to settings in code
|
||||
- Custom inspector support for better editing experience
|
||||
- Settings validation through the OnValidate method
|
||||
- Support for both runtime and development-time settings
|
||||
|
||||
## Quick Start
|
||||
|
||||
### How to Access and Edit Settings in Editor
|
||||
|
||||
#### Game Settings
|
||||
1. Open the Game Settings Editor window through the menu: **AppleHills → Game Settings Editor**
|
||||
2. Navigate through the tabs to find the settings group you want to edit
|
||||
3. Modify values directly in the inspector
|
||||
4. Click "Save All" to apply changes
|
||||
|
||||

|
||||
|
||||
#### Developer Settings
|
||||
1. Open the Developer Settings Editor window through the menu: **AppleHills → Developer Settings Editor**
|
||||
2. Select the appropriate tab (Diving, Debug, etc.)
|
||||
3. Modify values directly in the inspector
|
||||
4. Click "Save All" to apply changes
|
||||
|
||||

|
||||
|
||||
### How to Access Settings from Code
|
||||
|
||||
#### Game Settings
|
||||
## Quick Start (Code-First)
|
||||
|
||||
### Get Settings at Runtime
|
||||
```csharp
|
||||
// Get player follower settings
|
||||
var playerSettings = SettingsProvider.Instance.GetSettings<PlayerFollowerSettings>();
|
||||
float moveSpeed = playerSettings.MoveSpeed;
|
||||
|
||||
// Get interaction settings
|
||||
var interactionSettings = SettingsProvider.Instance.GetSettings<InteractionSettings>();
|
||||
float stopDistance = interactionSettings.PlayerStopDistance;
|
||||
```
|
||||
|
||||
#### Developer Settings
|
||||
|
||||
```csharp
|
||||
// Get diving developer settings
|
||||
var divingDevSettings = DeveloperSettingsProvider.Instance.GetSettings<DivingDeveloperSettings>();
|
||||
bool useObjectPooling = divingDevSettings.BubbleUseObjectPooling;
|
||||
|
||||
// Get debug settings
|
||||
var debugSettings = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>();
|
||||
if (debugSettings.GodMode) {
|
||||
// Apply god mode functionality
|
||||
}
|
||||
```
|
||||
|
||||
### Creating Your Own Settings
|
||||
|
||||
#### 1. Create the Settings Class
|
||||
|
||||
```csharp
|
||||
// For gameplay settings
|
||||
[CreateAssetMenu(fileName = "MyGameplaySettings", menuName = "AppleHills/Settings/My Gameplay Settings", order = 1)]
|
||||
public class MyGameplaySettings : BaseSettings
|
||||
{
|
||||
[Header("Movement")]
|
||||
[Tooltip("Base movement speed")]
|
||||
[SerializeField] private float baseSpeed = 5f;
|
||||
|
||||
public float BaseSpeed => baseSpeed;
|
||||
|
||||
public override void OnValidate()
|
||||
{
|
||||
// Validation logic here
|
||||
}
|
||||
}
|
||||
|
||||
// For developer settings
|
||||
[CreateAssetMenu(fileName = "MyDeveloperSettings", menuName = "AppleHills/Developer Settings/My Feature", order = 3)]
|
||||
public class MyDeveloperSettings : BaseDeveloperSettings
|
||||
{
|
||||
[Header("Configuration")]
|
||||
[Tooltip("Enable advanced features")]
|
||||
[SerializeField] private bool enableAdvancedFeatures = false;
|
||||
|
||||
public bool EnableAdvancedFeatures => enableAdvancedFeatures;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Create the Settings Asset
|
||||
- Right-click in the Project window
|
||||
- Select Create → AppleHills → Settings → My Gameplay Settings (or Developer Settings → My Feature)
|
||||
- Save the asset in the appropriate location (Assets/Settings or Assets/Settings/Developer)
|
||||
|
||||

|
||||
|
||||
#### 3. Register in the Editor Window (for custom tabs)
|
||||
See the detailed workflow section for a complete guide.
|
||||
|
||||
## Architecture Breakdown
|
||||
|
||||
The settings system consists of several key components:
|
||||
|
||||
### Base Classes
|
||||
|
||||
- **BaseSettings**: The root class for all game settings ScriptableObjects
|
||||
- **BaseDeveloperSettings**: The root class for all developer settings ScriptableObjects
|
||||
|
||||

|
||||
|
||||
### Provider Classes
|
||||
|
||||
- **SettingsProvider**: Singleton for accessing game settings
|
||||
- **DeveloperSettingsProvider**: Singleton for accessing developer settings
|
||||
|
||||
Both providers handle loading settings from Addressables or Resources, caching them for efficient access, and providing type-safe retrieval methods.
|
||||
|
||||
### Editor Windows
|
||||
|
||||
- **GameSettingsEditorWindow**: Custom editor window for editing game settings
|
||||
- **DeveloperSettingsEditorWindow**: Custom editor window for editing developer settings
|
||||
|
||||
These windows provide a clean interface for browsing and modifying settings, organized into tabs by category.
|
||||
|
||||
### Settings Storage
|
||||
|
||||
Settings assets are stored in:
|
||||
- **Assets/Settings**: For game settings
|
||||
- **Assets/Settings/Developer**: For developer settings
|
||||
|
||||
These assets are loaded via Addressables during runtime, with a fallback to Resources if needed.
|
||||
|
||||
## Detailed Workflows
|
||||
|
||||
### Creating a New Settings Class
|
||||
|
||||
1. **Determine the Type of Settings**
|
||||
- Game settings: Inherit from `BaseSettings`
|
||||
- Developer settings: Inherit from `BaseDeveloperSettings`
|
||||
|
||||
2. **Create the Class File**
|
||||
- Create a new script in the `Assets/Scripts/Core/Settings` folder
|
||||
- Name it appropriately, e.g., `MyFeatureSettings.cs` or `MyFeatureDeveloperSettings.cs`
|
||||
|
||||
3. **Implement the Settings Class**
|
||||
|
||||
Here's an example of how to implement a settings class with proper structure:
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
[CreateAssetMenu(fileName = "MyFeatureSettings", menuName = "AppleHills/Settings/My Feature", order = 1)]
|
||||
public class MyFeatureSettings : BaseSettings
|
||||
{
|
||||
[Header("Main Configuration")]
|
||||
[Tooltip("Description of this setting")]
|
||||
[SerializeField] private float mainValue = 10f;
|
||||
|
||||
[Tooltip("Another important setting")]
|
||||
[Range(0, 100)]
|
||||
[SerializeField] private int secondaryValue = 50;
|
||||
|
||||
[Header("Advanced Settings")]
|
||||
[Tooltip("Enable special features")]
|
||||
[SerializeField] private bool enableSpecialFeatures = false;
|
||||
|
||||
// Public property accessors (follow this pattern)
|
||||
public float MainValue => mainValue;
|
||||
public int SecondaryValue => secondaryValue;
|
||||
public bool EnableSpecialFeatures => enableSpecialFeatures;
|
||||
|
||||
// Optional validation
|
||||
public override void OnValidate()
|
||||
{
|
||||
// Ensure values are within acceptable ranges
|
||||
mainValue = Mathf.Max(0, mainValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
var playerFollower = SettingsProvider.Instance.GetSettings<PlayerFollowerSettings>();
|
||||
float speed = playerFollower.MoveSpeed;
|
||||
```
|
||||
|
||||
The key elements to include are:
|
||||
- Proper attribute decorations (`Header`, `Tooltip`, `Range`, etc.)
|
||||
- Serialized private fields with default values
|
||||
- Public property accessors using the expression-bodied syntax
|
||||
- Validation in the OnValidate method when needed
|
||||
|
||||
### Creating Settings Assets
|
||||
|
||||
1. **Create the Asset**
|
||||
- Right-click in the Project window
|
||||
- Navigate to Create → AppleHills → Settings → My Feature (or Developer Settings → My Feature)
|
||||
- A new settings asset will be created
|
||||
|
||||
2. **Configure the Asset**
|
||||
- Set the default values for your settings
|
||||
- Organize the asset in the correct folder:
|
||||
- Game settings: `Assets/Settings/`
|
||||
- Developer settings: `Assets/Settings/Developer/`
|
||||
|
||||
3. **Add to Addressables**
|
||||
- Open the Addressables Groups window (Window → Asset Management → Addressables → Groups)
|
||||
- Drag your settings asset to the Settings group
|
||||
- For game settings, ensure the addressable address is `Settings/YourSettingsClassName`
|
||||
- For developer settings, ensure the addressable address is `Settings/Developer/YourSettingsClassName`
|
||||
|
||||

|
||||
|
||||
### Adding to the Settings Editor Window
|
||||
|
||||
For game settings:
|
||||
|
||||
1. Open the `GameSettingsEditorWindow.cs` file in `Assets/Editor`
|
||||
2. Update the tab names array to include your new settings category
|
||||
3. Add a case to the switch statement in the `OnGUI` method to draw your settings
|
||||
4. Add your settings type to the `LoadAllSettings` method's `CreateSettingsIfMissing` calls
|
||||
|
||||
Here's an example of the code changes needed:
|
||||
|
||||
Or fetch interaction settings once and reuse:
|
||||
```csharp
|
||||
// 1. Update tab names array
|
||||
private string[] tabNames = new string[] { "Player", "Interaction", "My Feature" }; // Add your tab
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
// 2. Add to switch statement in OnGUI
|
||||
switch (selectedTab)
|
||||
private IInteractionSettings _interaction;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
case 0: // Player
|
||||
DrawSettingsEditor<PlayerFollowerSettings>();
|
||||
break;
|
||||
case 1: // Interaction
|
||||
DrawSettingsEditor<InteractionSettings>();
|
||||
break;
|
||||
case 2: // My Feature
|
||||
DrawSettingsEditor<MyFeatureSettings>(); // Add your case
|
||||
break;
|
||||
_interaction = SettingsProvider.Instance.GetSettings<InteractionSettings>();
|
||||
}
|
||||
|
||||
// 3. Add to LoadAllSettings method
|
||||
private void LoadAllSettings()
|
||||
void UseIt()
|
||||
{
|
||||
// ... existing code ...
|
||||
|
||||
// Add your settings type
|
||||
CreateSettingsIfMissing<MyFeatureSettings>("MyFeatureSettings");
|
||||
float stopDist = _interaction.PlayerStopDistance;
|
||||
}
|
||||
```
|
||||
|
||||
For developer settings:
|
||||
|
||||
1. Open the `DeveloperSettingsEditorWindow.cs` file in `Assets/Editor`
|
||||
2. Update the tab names array to include your new settings category
|
||||
3. Add a case to the switch statement in the `OnGUI` method to draw your settings
|
||||
4. Add your settings type to the `LoadAllSettings` method's `CreateSettingsIfMissing` calls
|
||||
|
||||
As seen in your current implementation for the Debug tab:
|
||||
|
||||
### Use Editor-Time Values via `SettingsAccess`
|
||||
For scene tools, gizmos, or editors that should reflect current settings outside Play Mode:
|
||||
```csharp
|
||||
// 1. Tab names include the Debug category
|
||||
private string[] tabNames = new string[] { "Diving", "Debug", "Other Systems" };
|
||||
|
||||
// 2. Switch statement handles the Debug tab
|
||||
switch (selectedTab)
|
||||
{
|
||||
case 0: // Diving
|
||||
DrawSettingsEditor<DivingDeveloperSettings>();
|
||||
break;
|
||||
case 1: // Debug
|
||||
DrawSettingsEditor<DebugSettings>();
|
||||
break;
|
||||
case 2: // Other Systems
|
||||
// ... existing code ...
|
||||
break;
|
||||
}
|
||||
|
||||
// 3. LoadAllSettings includes DebugSettings
|
||||
private void LoadAllSettings()
|
||||
{
|
||||
// ... existing code ...
|
||||
CreateSettingsIfMissing<DivingDeveloperSettings>("DivingDeveloperSettings");
|
||||
CreateSettingsIfMissing<DebugSettings>("DebugSettings");
|
||||
}
|
||||
// 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();
|
||||
```
|
||||
|
||||
### Using Settings in Game Code
|
||||
|
||||
1. **Access game settings**:
|
||||
|
||||
### Access Example Fields
|
||||
```csharp
|
||||
public class MyGameplaySystem : MonoBehaviour
|
||||
{
|
||||
private MyFeatureSettings settings;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Get settings from provider
|
||||
settings = SettingsProvider.Instance.GetSettings<MyFeatureSettings>();
|
||||
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;
|
||||
|
||||
if (settings == null)
|
||||
{
|
||||
Debug.LogError("Failed to load MyFeatureSettings!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the settings
|
||||
float value = settings.MainValue;
|
||||
bool specialEnabled = settings.EnableSpecialFeatures;
|
||||
|
||||
// Configure components based on settings
|
||||
// ...
|
||||
}
|
||||
}
|
||||
var inter = SettingsProvider.Instance.GetSettings<InteractionSettings>();
|
||||
LayerMask interactMask = inter.InteractableLayerMask;
|
||||
GameObject pickupPrefab = inter.BasePickupPrefab;
|
||||
float promptRange = inter.DefaultPuzzlePromptRange;
|
||||
```
|
||||
|
||||
2. **Access developer settings**:
|
||||
## 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
|
||||
At runtime, `SettingsProvider` synchronously loads settings via Addressables with keys:
|
||||
- `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<T>()` calls are fast.
|
||||
|
||||
## 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
|
||||
public class MyDevelopmentTool : MonoBehaviour
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
public class InteractDistanceExample
|
||||
{
|
||||
private MyFeatureDeveloperSettings devSettings;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Get developer settings
|
||||
devSettings = DeveloperSettingsProvider.Instance.GetSettings<MyFeatureDeveloperSettings>();
|
||||
|
||||
if (devSettings == null)
|
||||
{
|
||||
Debug.LogError("Failed to load MyFeatureDeveloperSettings!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the settings
|
||||
bool advancedEnabled = devSettings.EnableAdvancedFeatures;
|
||||
|
||||
// Configure development tools based on settings
|
||||
// ...
|
||||
}
|
||||
private readonly IInteractionSettings _s = SettingsProvider.Instance.GetSettings<InteractionSettings>();
|
||||
public bool IsInRange(float dist) => dist <= _s.PlayerStopDistance;
|
||||
}
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
### Follower Handling & Movement
|
||||
```csharp
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
1. **Organization**
|
||||
- Group related settings into a single settings class
|
||||
- Use `[Header]` attributes to create logical sections
|
||||
- Use `[Tooltip]` attributes to document settings
|
||||
public class FollowerMover
|
||||
{
|
||||
private readonly IPlayerFollowerSettings _pf = SettingsProvider.Instance.GetSettings<PlayerFollowerSettings>();
|
||||
public float TargetSpeed(float error) => Mathf.Clamp(error * _pf.FollowerSpeedMultiplier, 0f, _pf.MoveSpeed);
|
||||
}
|
||||
```
|
||||
|
||||
2. **Validation**
|
||||
- Implement `OnValidate()` to ensure values are within acceptable ranges
|
||||
- Consider dependencies between settings
|
||||
### Diving Minigame Tuning
|
||||
```csharp
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
3. **Naming**
|
||||
- Use descriptive names for settings properties
|
||||
- Follow a consistent naming pattern
|
||||
public class SpawnController
|
||||
{
|
||||
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);
|
||||
}
|
||||
```
|
||||
|
||||
4. **Performance**
|
||||
- Cache settings references rather than calling `GetSettings<T>()` repeatedly
|
||||
- Only access settings when needed
|
||||
## Troubleshooting / FAQ
|
||||
- Settings return null at runtime:
|
||||
- Ensure assets are Addressable with keys `Settings/<TypeName>` and Addressables are initialized before first access.
|
||||
- Editor changes don’t 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.
|
||||
|
||||
5. **Defaults**
|
||||
- Provide sensible default values for all settings
|
||||
- Document the expected ranges for numeric values
|
||||
## Paths & Namespaces
|
||||
- Scripts: `Assets/Scripts/Core/Settings/`
|
||||
- `BaseSettings.cs`
|
||||
- `SettingsInterfaces.cs`
|
||||
- `SettingsProvider.cs`
|
||||
- `PlayerFollowerSettings.cs`
|
||||
- `InteractionSettings.cs`
|
||||
- `DivingMinigameSettings.cs`
|
||||
- Editor tooling: `Assets/Editor/SettingsEditorWindow.cs`
|
||||
- Editor-time facade: `Assets/Scripts/Core/SettingsAccess.cs`
|
||||
- Namespaces:
|
||||
- Runtime: `AppleHills.Core.Settings`
|
||||
- Editor window: `AppleHills.Core.Settings.Editor`
|
||||
- Facade: `AppleHills`
|
||||
|
||||
## Required Screenshots
|
||||
|
||||
To complete this documentation, you'll need to take the following screenshots:
|
||||
|
||||
1. **settings_system_overview.png**
|
||||
- Screenshot of both settings editor windows side by side to show the full system
|
||||
|
||||
2. **game_settings_editor.png**
|
||||
- Screenshot of the Game Settings Editor window with the PlayerFollowerSettings tab selected
|
||||
|
||||
3. **developer_settings_editor.png**
|
||||
- Screenshot of the Developer Settings Editor window with the Debug tab selected
|
||||
|
||||
4. **create_settings_asset.png**
|
||||
- Screenshot of the right-click Create menu showing the AppleHills/Settings and AppleHills/Developer Settings options
|
||||
|
||||
5. **settings_class_hierarchy.png**
|
||||
- Screenshot of the Project window showing the folder structure with expanded Settings folder highlighting the base classes
|
||||
|
||||
6. **addressables_configuration.png**
|
||||
- Screenshot of the Addressables Groups window showing settings assets properly configured
|
||||
## Change Log
|
||||
- v1.1: New page with TOC, code-first usage, authoring workflow, Addressables keys, case studies, troubleshooting, and paths.
|
||||
|
||||
Reference in New Issue
Block a user