Create an Interactable Object
Add a new world object the player can interact with, carry, or push — by implementing one or more of the interactable interfaces.
Overview
World objects in Loop Adventure express their capabilities through interfaces. Each interface adds a distinct interaction type, and a single object can implement several at once.
| Interface | What it enables | Triggered by |
|---|---|---|
IInteractable | Custom interaction — open chests, collect items, trigger doors | Instruction_Interact or automatic collection on turn end |
ICarryObject | Player can pick up and drop the object | Instruction_Carry and Instruction_Drop |
IPushable | Player can push the object one tile in a direction | Instruction_Push |
IObstacle | Object blocks player movement and push validation | Checked by IPushable.Validate() and movement instructions |
ExecutionContextElement to participate in the execution lifecycle. The engine clones scene objects at runtime — without this base class, the object won't be included in execution.
1 — Implement IInteractable
Use IInteractable when you want the player to trigger a one-shot action: collect an item, open a container, activate a mechanism. If automatic is true, the interaction fires at the end of any turn the player occupies the same tile — no instruction needed.
- Create a new script that extends
ExecutionContextElementand implementsIInteractable. - Set
automatic: returntruefor pickups that collect on touch; returnfalsefor chests or mechanisms that require an explicit Interact instruction. - Implement
Validate(): returntrueif the interaction is currently legal (e.g. chest has an item). Returnfalseto silently block — add aNotificationCenter.Notify()call before returning to give the player feedback. - Implement
Interact(): put your behaviour here — add inventory items, play animations, destroy the object, fire events. - Declare
OnInteractwith a[field: SerializeField]attribute so it is wirable in the Inspector.
using UnityEngine; using UnityEngine.Events; namespace LoopAdventure { public class InteractableObject_Sign : ExecutionContextElement, IInteractable { [SerializeField] string message; [field: SerializeField] public UnityEvent OnInteract { get; set; } // false = requires Interact instruction; true = collected automatically public bool automatic => false; public bool Validate() => !string.IsNullOrEmpty(message); public void Interact() { NotificationCenter.Notify(NotificationCenter.MessageType.Message, message); OnInteract?.Invoke(); } } }
2 — Add ICarryObject Support
Add ICarryObject to let the player pick up the object with Instruction_Carry and drop it with Instruction_Drop. The engine parents the object to the player during carry and clears the carry state on drop.
- Add
ICarryObjectto the class declaration. - Implement
Take(): disable the collider so the carried object doesn't interfere with movement; play a pickup sound. - Implement
Drop(): re-enable the collider and restore the object's normal state. - The engine handles parenting and unparenting automatically — you do not need to call
transform.SetParent().
public class InteractableObject_Pot : ExecutionContextElement, ICarryObject { [SerializeField] AudioSource audioSource; [SerializeField] AudioClip pickupSound, dropSound; // ICarryObject requires Transform — MonoBehaviour provides it automatically Transform ICarryObject.transform => transform; public void Take() { GetComponent<Collider2D>().enabled = false; audioSource.PlayOneShot(pickupSound); } public void Drop() { GetComponent<Collider2D>().enabled = true; audioSource.PlayOneShot(dropSound); } }
3 — Add IPushable Support
Add IPushable to let the player move the object one tile with Instruction_Push. The interface includes a default Validate() implementation that checks for IObstacle components in the target cell — you usually don't need to override it.
- Add
IPushableto the class declaration. - Implement
Push(context, direction): move the object to the adjacent tile. UseMapManager.cellSizefor the offset and match the movement style of the built-in objects (lerp or instant). - Implement
Stop(): stop looping sounds or animations when the push completes. - If you also implement
IObstacle, the defaultValidate()will detect your own collider as a blocker — make sure the object's collider is on a layer that doesn't trigger its ownIObstaclecheck, or overrideValidate().
Transform IPushable.transform => transform; public void Push(ExecutionContext context, Direction direction) { Vector2 target = transform.position.ToVector2() + direction.AsVector2() * MapManager.cellSize; transform.position = new Vector3(target.x, target.y, transform.position.z); audioSource.PlayOneShot(pushSound); } public void Stop() { } // stop looping sounds here if any
4 — Add IObstacle Support
IObstacle tells the engine that this object physically blocks movement. IPushable.Validate() uses it to decide whether a push is legal. Player movement instructions also check for it.
- Add
IObstacleto the class declaration. - Implement
Validate(): returntruewhile the object should block (e.g. a closed door); returnfalsewhen it should not (e.g. after a door opens). For static solid objects, always returntrue.
// Solid object — always blocks public bool Validate() => true; // Togglable obstacle — door or gate bool isOpen; public bool Validate() => !isOpen;
5 — Combining Interfaces
Interfaces are designed to be combined freely. The built-in objects demonstrate the most common combinations:
| Class | IInteractable | ICarryObject | IPushable | IObstacle |
|---|---|---|---|---|
InteractableObject_Pickup | ✓ (automatic) | |||
InteractableObject_Chest | ✓ (manual) | |||
InteractableObject_Carry | ✓ | ✓ | ✓ | |
InteractableObject_PushableObject | ✓ | ✓ | ||
InteractableObject_Door | ✓ (togglable) |
6 — Prefab Setup
- Create an empty GameObject. Name it to match your class (e.g.
Interactable_Sign). - Add a BoxCollider2D — size it to one grid cell (
1 × 1units). This is what the engine detects withPhysics2D.OverlapBox. - Add your script component. Configure all Inspector fields.
- Add a SpriteRenderer and an Animator if the object has visual feedback.
- Add an AudioSource if the object plays sounds.
- Save as a prefab under
Assets/Prefabs/Interactables/. - Place the prefab in your level scene aligned to the tile grid.
MapManager.cellSize (default 1.0). Use the scene snapping tool or set position manually in the Inspector.