Files
AppleHillsProduction/docs/interactables_readme.md

254 lines
9.5 KiB
Markdown
Raw Normal View History

# Apple Hills Interaction System
2025-10-21 12:37:53 +02:00
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;
2025-10-21 12:37:53 +02:00
public class InteractDebugHooks : MonoBehaviour
{
[SerializeField] private Interactable interactable;
2025-10-21 12:37:53 +02:00
private void OnEnable()
{
interactable.interactionStarted.AddListener(OnStarted);
interactable.characterArrived.AddListener(OnCharacterArrived);
interactable.interactionInterrupted.AddListener(OnInterrupted);
interactable.interactionComplete.AddListener(OnComplete);
}
2025-10-21 12:37:53 +02:00
private void OnDisable()
{
interactable.interactionStarted.RemoveListener(OnStarted);
interactable.characterArrived.RemoveListener(OnCharacterArrived);
interactable.interactionInterrupted.RemoveListener(OnInterrupted);
interactable.interactionComplete.RemoveListener(OnComplete);
}
2025-10-21 12:37:53 +02:00
private void OnStarted(Input.PlayerTouchController player, FollowerController follower)
=> Debug.Log("Interaction started");
2025-10-21 12:37:53 +02:00
private void OnCharacterArrived() => Debug.Log("Character arrived");
private void OnInterrupted() => Debug.Log("Interaction interrupted");
private void OnComplete(bool success) => Debug.Log($"Interaction complete: {success}");
}
```
2025-10-21 12:37:53 +02:00
### Create a Custom Action
```csharp
using System.Threading.Tasks;
using Interactions;
using Input;
using UnityEngine;
2025-10-21 12:37:53 +02:00
public class PlaySfxOnArrivalAction : InteractionActionBase
{
[SerializeField] private AudioSource sfx;
2025-10-21 12:37:53 +02:00
private void Reset()
{
// React to the arrival event; don't block the flow
respondToEvents = new() { InteractionEventType.InteractingCharacterArrived };
pauseInteractionFlow = false;
}
2025-10-21 12:37:53 +02:00
protected override bool ShouldExecute(InteractionEventType evt, PlayerTouchController player, FollowerController follower)
{
return sfx != null;
}
2025-10-21 12:37:53 +02:00
protected override async Task<bool> ExecuteAsync(InteractionEventType evt, PlayerTouchController player, FollowerController follower)
{
2025-10-21 12:37:53 +02:00
sfx.Play();
// non-blocking action returns immediately when pauseInteractionFlow == false
return true;
}
}
```
2025-10-21 12:37:53 +02:00
Attach this component under the same hierarchy as an `Interactable`. Registration is automatic via `OnEnable()`/`OnDisable()` in `InteractionActionBase`.
2025-10-21 12:37:53 +02:00
### Trigger Programmatically
Normally input goes through `ITouchInputConsumer.OnTap(...)`. For testing, you can call the public tap handler:
```csharp
2025-10-21 12:37:53 +02:00
using UnityEngine;
using Interactions;
public class TestTrigger : MonoBehaviour
{
2025-10-21 12:37:53 +02:00
[SerializeField] private Interactable interactable;
[ContextMenu("Trigger Interact (dev)")]
private void Trigger()
{
interactable.OnTap(interactable.transform.position);
}
}
```
2025-10-21 12:37:53 +02:00
## Core Components
2025-10-21 12:37:53 +02:00
### `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.
2025-10-21 12:37:53 +02:00
![Interactable Inspector](media/interactable_inspector.png)
2025-10-21 12:37:53 +02:00
### `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.
2025-10-21 12:37:53 +02:00
![Character Move Target Inspector](media/character_move_target_inspector.png)
2025-10-21 12:37:53 +02:00
### `InteractionActionBase` and concrete actions
- Filter by `InteractionEventType` using `respondToEvents`.
- Control flow with `pauseInteractionFlow` and async `ExecuteAsync(...)`.
- Builtin example: `InteractionTimelineAction` for cinematics.
2025-10-21 12:37:53 +02:00
![InteractionTimelineAction Inspector](media/interaction_timeline_action_inspector.png)
2025-10-21 12:37:53 +02:00
### `InteractionRequirementBase`
- Attach one or more to gate the interaction based on items, puzzles, proximity, etc.
2025-10-21 12:37:53 +02:00
## Interaction Event Flow
1. `InteractionStarted`
2. `PlayerArrived`
3. `InteractingCharacterArrived`
4. `InteractionComplete` (bool success)
5. `InteractionInterrupted`
2025-10-21 12:37:53 +02:00
Actions receive these events in order and may run concurrently; those with `pauseInteractionFlow` true are awaited.
2025-10-21 12:37:53 +02:00
## Case Studies
2025-10-21 12:37:53 +02:00
### Open a Door on Arrival
```csharp
using System.Threading.Tasks;
using Interactions;
using Input;
using UnityEngine;
2025-10-21 12:37:53 +02:00
public class DoorOpenOnArrival : InteractionActionBase
{
[SerializeField] private Animator animator; // expects a bool parameter "Open"
2025-10-21 12:37:53 +02:00
private void Reset()
{
respondToEvents = new() { InteractionEventType.InteractingCharacterArrived };
pauseInteractionFlow = false;
}
2025-10-21 12:37:53 +02:00
protected override async Task<bool> ExecuteAsync(InteractionEventType evt, PlayerTouchController p, FollowerController f)
{
animator.SetBool("Open", true);
return true;
}
}
```
2025-10-21 12:37:53 +02:00
### 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.
2025-10-21 12:37:53 +02:00
### Kick Off Dialogue When Player Arrives
```csharp
2025-10-21 12:37:53 +02:00
using System.Threading.Tasks;
using Dialogue;
using Input;
using Interactions;
using UnityEngine;
public class StartDialogueOnArrival : InteractionActionBase
{
2025-10-21 12:37:53 +02:00
[SerializeField] private DialogueComponent dialogue;
2025-10-21 12:37:53 +02:00
private void Reset()
{
respondToEvents = new() { InteractionEventType.PlayerArrived };
pauseInteractionFlow = false;
}
2025-10-21 12:37:53 +02:00
protected override async Task<bool> ExecuteAsync(InteractionEventType evt, PlayerTouchController p, FollowerController f)
{
dialogue.StartDialogue();
return true;
}
}
```
2025-10-21 12:37:53 +02:00
## 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:
2025-10-21 12:37:53 +02:00
![Timeline Mapping Editor](media/timeline_mapping_editor.png)
2025-10-21 12:37:53 +02:00
- Unity Timeline editor when authoring cinematics for interactions:
2025-10-21 12:37:53 +02:00
![Timeline Editor](media/timeline_editor.png)
2025-10-21 12:37:53 +02:00
- Example target placement in Scene view:
2025-10-21 12:37:53 +02:00
![Target Positioning In Scene](media/target_positioning_scene.png)
2025-10-21 12:37:53 +02:00
## 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.