Update readme docs for our systems
This commit is contained in:
@@ -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
|
||||
{
|
||||
// Which events this action should respond to
|
||||
public List<InteractionEventType> respondToEvents;
|
||||
|
||||
// Whether to pause the interaction flow during execution
|
||||
public bool pauseInteractionFlow;
|
||||
using System.Threading.Tasks;
|
||||
using Interactions;
|
||||
using Input;
|
||||
using UnityEngine;
|
||||
|
||||
// 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)
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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;
|
||||
|
||||
// The timeline assets to play (in sequence)
|
||||
public PlayableAsset[] timelines;
|
||||
|
||||
// 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;
|
||||
[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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
Reference in New Issue
Block a user