2025-10-07 10:57:11 +00:00
# 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` , 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;
2025-10-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
public class InteractDebugHooks : MonoBehaviour
{
[SerializeField] private Interactable interactable;
2025-10-07 10:57:11 +00:00
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-07 10:57:11 +00:00
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-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
private void OnStarted(Input.PlayerTouchController player, FollowerController follower)
=> Debug.Log("Interaction started");
2025-10-07 10:57:11 +00:00
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-07 10:57:11 +00:00
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-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
public class PlaySfxOnArrivalAction : InteractionActionBase
{
[SerializeField] private AudioSource sfx;
2025-10-07 10:57:11 +00:00
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-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
protected override bool ShouldExecute(InteractionEventType evt, PlayerTouchController player, FollowerController follower)
{
return sfx != null;
}
2025-10-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
protected override async Task< bool > ExecuteAsync(InteractionEventType evt, PlayerTouchController player, FollowerController follower)
2025-10-07 10:57:11 +00:00
{
2025-10-21 12:37:53 +02:00
sfx.Play();
// non-blocking action returns immediately when pauseInteractionFlow == false
2025-10-07 10:57:11 +00:00
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-07 10:57:11 +00:00
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:
2025-10-07 10:57:11 +00:00
```csharp
2025-10-21 12:37:53 +02:00
using UnityEngine;
using Interactions;
public class TestTrigger : MonoBehaviour
2025-10-07 10:57:11 +00:00
{
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-07 10:57:11 +00:00
}
```
2025-10-21 12:37:53 +02:00
## Core Components
2025-10-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
### `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.
2025-10-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00

2025-10-07 10:57:11 +00:00
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-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00

2025-10-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
### `InteractionActionBase` and concrete actions
- Filter by `InteractionEventType` using `respondToEvents` .
- Control flow with `pauseInteractionFlow` and async `ExecuteAsync(...)` .
- Built‑ in example: `InteractionTimelineAction` for cinematics.
2025-10-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00

2025-10-07 10:57:11 +00:00
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-07 10:57:11 +00:00
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-07 10:57:11 +00:00
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-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
## Case Studies
2025-10-07 10:57:11 +00:00
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-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
public class DoorOpenOnArrival : InteractionActionBase
{
[SerializeField] private Animator animator; // expects a bool parameter "Open"
2025-10-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
private void Reset()
{
respondToEvents = new() { InteractionEventType.InteractingCharacterArrived };
pauseInteractionFlow = false;
}
2025-10-07 10:57:11 +00:00
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-07 10:57:11 +00:00
```
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-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
### Kick Off Dialogue When Player Arrives
2025-10-07 10:57:11 +00:00
```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-07 10:57:11 +00:00
{
2025-10-21 12:37:53 +02:00
[SerializeField] private DialogueComponent dialogue;
2025-10-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
private void Reset()
{
respondToEvents = new() { InteractionEventType.PlayerArrived };
pauseInteractionFlow = false;
}
2025-10-07 10:57:11 +00:00
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-07 10:57:11 +00:00
}
```
2025-10-21 12:37:53 +02:00
## 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.
## 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-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00

2025-10-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
- Unity Timeline editor when authoring cinematics for interactions:
2025-10-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00

2025-10-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00
- Example target placement in Scene view:
2025-10-07 10:57:11 +00:00
2025-10-21 12:37:53 +02:00

2025-10-07 10:57:11 +00:00
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.