Tile Traps
Add pressure plates and appearing spikes to a level. Both traps are self-scheduling — they run automatically every turn without any wiring beyond the Inspector.
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.
| Prefab | Behaviour |
|---|---|
Tile_Interactable_PressurePlate | Detects when the player or any pushable object stands on its tile and fires OnActivated / OnDeactivated events. |
Tile_Interactable_AppearingSpikes | Cycles through a configurable stage pattern each turn. On spike stages it deals lethal damage to any entity with a Health component on the tile. |
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.
- Drag the Tile_Interactable_PressurePlate prefab from
Assets/Prefabs/Interactables/into the scene. Snap it to the tile grid. - 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). - Expand On Deactivated and wire the reverse action (e.g. call
Close()on the same door). - If the plate should start pre-pressed, set the Activated Animator parameter to
truein the prefab's Animator component.
| Inspector Field | Type | Description |
|---|---|---|
OnActivated | UnityEvent | Fired the turn the plate transitions from released → pressed. Wire to doors, bridges, or any reaction. |
OnDeactivated | UnityEvent | Fired the turn the plate transitions from pressed → released. |
| Animator Parameter | Type | Meaning |
|---|---|---|
Activated | bool | true while the plate is pressed down. |
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.
- Drag the Tile_Interactable_AppearingSpikes prefab from
Assets/Prefabs/Interactables/into the scene. Snap it to the tile grid. - Select the instance. In the Inspector, find the Stages array. Set its size and fill each element with
true(spikes out) orfalse(retracted). - 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.
- To offset spikes so they are out of phase with each other, prepend extra
falseentries to delay when the lethal phase starts.
| Pattern | Stages value | Behaviour |
|---|---|---|
| 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 Parameter | Type | Meaning |
|---|---|---|
Appeared | bool | true while the spikes are extended. |
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:
| Tile | Stages | Effect |
|---|---|---|
| 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.
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
| Member | Responsibility |
|---|---|
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. |
currentInstruction | Required property — return the Instruction currently running, or null if idle. Used by HUD indicators. |
Step-by-step
- Create a new script. Extend
ExecutionContextElementand addITaskReceiverto the class declaration. - Implement
Initialize(). Callbase.Initialize(context, parent)first, then cache any component references (Animator, AudioSource, Collider, etc.). - Implement
GetTask(). InInitialize(), assign a coroutine method (IEnumerator) to_instruction.tick. InGetTask(), returnTask.GetTask(this, _instruction, context)— this wraps the coroutine for the runner. The coroutine usesyield returnto wait andyield return nullto hand control back. - Implement the
currentInstructionproperty. Returnnullif your trap does not use the instruction system. - Implement the four
ITaskReceiverevents (OnStart,OnActionChange,OnEnd,OnExecute). If you do not need them, declare them with empty setters. - Add the script to a GameObject with a BoxCollider2D (one tile, trigger or solid depending on your logic).
- 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; } } }
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.