Files
AppleHillsProduction/docs/interactables_readme.md
2025-10-21 12:37:57 +02:00

254 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Apple Hills Interaction System
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).
## 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)
## 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`, oneshot/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
### `Interactable`
- Handles input, cooldowns (`cooldown`), oneshot (`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.
![Interactable Inspector](media/interactable_inspector.png)
### `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.
![Character Move Target Inspector](media/character_move_target_inspector.png)
### `InteractionActionBase` and concrete actions
- Filter by `InteractionEventType` using `respondToEvents`.
- Control flow with `pauseInteractionFlow` and async `ExecuteAsync(...)`.
- Builtin example: `InteractionTimelineAction` for cinematics.
![InteractionTimelineAction Inspector](media/interaction_timeline_action_inspector.png)
### `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`
Actions receive these events in order and may run concurrently; those with `pauseInteractionFlow` true are awaited.
## Case Studies
### Open a Door on Arrival
```csharp
using System.Threading.Tasks;
using Interactions;
using Input;
using UnityEngine;
public class DoorOpenOnArrival : InteractionActionBase
{
[SerializeField] private Animator animator; // expects a bool parameter "Open"
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;
}
}
```
### 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
using System.Threading.Tasks;
using Dialogue;
using Input;
using Interactions;
using UnityEngine;
public class StartDialogueOnArrival : InteractionActionBase
{
[SerializeField] private DialogueComponent dialogue;
private void Reset()
{
respondToEvents = new() { InteractionEventType.PlayerArrived };
pauseInteractionFlow = false;
}
protected override async Task<bool> ExecuteAsync(InteractionEventType evt, PlayerTouchController p, FollowerController f)
{
dialogue.StartDialogue();
return true;
}
}
```
## Troubleshooting / FAQ
- Interaction doesnt 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.
## 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:
![Timeline Mapping Editor](media/timeline_mapping_editor.png)
- Unity Timeline editor when authoring cinematics for interactions:
![Timeline Editor](media/timeline_editor.png)
- Example target placement in Scene view:
![Target Positioning In Scene](media/target_positioning_scene.png)
## 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.