Overview

Loop Adventure ships two ready-to-use tile trap prefabs. Each sits on exactly one tile and hooks into the turn loop through the ITaskReceiver interface, meaning it executes in parallel with the player and enemies every turn — no manual registration required.

PrefabBehaviour
Tile_Interactable_PressurePlateDetects when the player or any pushable object stands on its tile and fires OnActivated / OnDeactivated events.
Tile_Interactable_AppearingSpikesCycles through a configurable stage pattern each turn. On spike stages it deals lethal damage to any entity with a Health component on the tile.
ℹ️
Automatic registration Any ExecutionContextElement that implements ITaskReceiver is discovered automatically during level initialization. You do not need to call any registration method or add the trap to a list.

1 — Pressure Plate

The pressure plate checks its tile once per turn — at the halfway point of the turn interval — and fires events when its activation state changes. Use it to open doors, lower bridges, trigger sounds, or drive any mechanism that depends on tile occupation.

  1. Drag the Tile_Interactable_PressurePlate prefab from Assets/Prefabs/Interactables/ into the scene. Snap it to the tile grid.
  2. Select the prefab instance. In the Inspector, expand On Activated and wire it to the object you want to trigger when the plate is pressed (e.g. call Open() on a door).
  3. Expand On Deactivated and wire the reverse action (e.g. call Close() on the same door).
  4. If the plate should start pre-pressed, set the Activated Animator parameter to true in the prefab's Animator component.
Inspector FieldTypeDescription
OnActivatedUnityEventFired the turn the plate transitions from released → pressed. Wire to doors, bridges, or any reaction.
OnDeactivatedUnityEventFired the turn the plate transitions from pressed → released.
Animator ParameterTypeMeaning
Activatedbooltrue while the plate is pressed down.
💡
What counts as a trigger The plate uses Physics2D.OverlapBoxAll at its tile centre. It detects the player (PlayerController) and any object with an IObstacle component — including pushable crates. Enemies do not activate the plate.

2 — Appearing Spikes

The spikes cycle through a stages array — one entry per turn. A true entry means the spikes are extended and lethal; false means they are retracted and safe to walk on. The pattern loops automatically.

  1. Drag the Tile_Interactable_AppearingSpikes prefab from Assets/Prefabs/Interactables/ into the scene. Snap it to the tile grid.
  2. Select the instance. In the Inspector, find the Stages array. Set its size and fill each element with true (spikes out) or false (retracted).
  3. The first entry is the state on Turn 1. The spikes advance one step each turn and wrap back to index 0 after the last entry.
  4. To offset spikes so they are out of phase with each other, prepend extra false entries to delay when the lethal phase starts.
PatternStages valueBehaviour
Alternating[false, true]Safe turn 1, lethal turn 2, repeating. Default.
Mostly safe[false, false, true]Two safe turns, one lethal turn.
Mostly lethal[true, true, false]Two lethal turns, one safe turn.
Always lethal[true]Never retract — acts as a permanent hazard.
Animator ParameterTypeMeaning
Appearedbooltrue while the spikes are extended.
💡
Damage timing The damage check runs at the midpoint of the turn interval so the spikes visually extend before the kill is applied. An entity that moves off the tile before the check runs survives. Use this to make spike puzzles fair — players who react correctly can escape.

3 — Placing Multiple Traps

Multiple trap instances in the same scene are fully independent. They each maintain their own stage index and run their own turn task in parallel. To create staggered spike patterns across adjacent tiles, use copies of the same prefab with different stages offsets:

TileStagesEffect
Tile A[false, true]Safe → Lethal → Safe → …
Tile B[true, false]Lethal → Safe → Lethal → …

This creates a corridor where one tile is always safe but alternates — the player must time movement to step onto the safe tile each turn.

ℹ️
Scripting reference For the full class API — properties, events, and method signatures — see the Traps scripting reference.

4 — Creating a Custom Trap

Any class that extends ExecutionContextElement and implements ITaskReceiver is automatically discovered and scheduled every turn — the same way the built-in traps work. You only need to implement two methods.

Required interface

MemberResponsibility
GetTask()Return the Task the runner should await each turn. Everything inside that task is your trap's per-turn behaviour.
Initialize(context, parent)Called once during level init. Cache the Animator or any scene references here. Always call base.Initialize() first.
currentInstructionRequired property — return the Instruction currently running, or null if idle. Used by HUD indicators.

Step-by-step

  1. Create a new script. Extend ExecutionContextElement and add ITaskReceiver to the class declaration.
  2. Implement Initialize(). Call base.Initialize(context, parent) first, then cache any component references (Animator, AudioSource, Collider, etc.).
  3. Implement GetTask(). In Initialize(), assign a coroutine method (IEnumerator) to _instruction.tick. In GetTask(), return Task.GetTask(this, _instruction, context) — this wraps the coroutine for the runner. The coroutine uses yield return to wait and yield return null to hand control back.
  4. Implement the currentInstruction property. Return null if your trap does not use the instruction system.
  5. Implement the four ITaskReceiver events (OnStart, OnActionChange, OnEnd, OnExecute). If you do not need them, declare them with empty setters.
  6. Add the script to a GameObject with a BoxCollider2D (one tile, trigger or solid depending on your logic).
  7. Save as a prefab under Assets/Prefabs/Interactables/ and place it in the scene.

Example — Proximity alarm

This trap checks every turn whether the player is within two tiles and sends a notification if so — no damage, just feedback.

using System;
using System.Collections;
using UnityEngine;

namespace LoopAdventure
{
    public class Tile_Interactable_ProximityAlarm : ExecutionContextElement, ITaskReceiver
    {
        // ITaskReceiver events
        public event Action<EnemyAction> OnStart;
        public event Action<EnemyAction> OnActionChange;
        public event Action                OnEnd;
        public event Action<ExecutionContext> OnExecute;

        public Instruction currentInstruction => null;

        CustomInstruction _instruction;

        public override void Initialize(ExecutionContext ctx, ExecutionContextElement parent)
        {
            base.Initialize(ctx, parent);
            _instruction      = new CustomInstruction();
            _instruction.tick = CheckProximity;
        }

        public Task GetTask() => Task.GetTask(this, _instruction, context);

        IEnumerator CheckProximity(ExecutionContext ctx)
        {
            Vector2 myPos     = transform.position;
            Vector2 playerPos  = ctx.player.transform.position;

            if (Vector2.Distance(myPos, playerPos) <= 2f * MapManager.cellSize)
            {
                NotificationCenter.Notify(
                    NotificationCenter.MessageType.Warning,
                    "The alarm triggered!"
                );
            }

            yield return null;
        }
    }
}
💡
The tick coroutine The tick delegate is an IEnumerator that the runner drives as a Unity coroutine. Use yield return new WaitForSeconds(t) to pause mid-turn (e.g. wait for an animation before applying damage). Use yield return null to simply hand control back for one frame. The turn does not advance until the coroutine completes.