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

9.5 KiB
Raw Permalink Blame History

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

  • 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

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

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:

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

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

InteractionActionBase and concrete actions

  • Filter by InteractionEventType using respondToEvents.
  • Control flow with pauseInteractionFlow and async ExecuteAsync(...).
  • Builtin example: InteractionTimelineAction for cinematics.

InteractionTimelineAction Inspector

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

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

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

  • Unity Timeline editor when authoring cinematics for interactions:

Timeline Editor

  • Example target placement in Scene view:

Target Positioning In Scene

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.