---
title: "Doors buttons levers and press-to-interact"
description: "The player walks up to a door and it opens, or presses a key near a lever and a gate lifts. There is no `InteractableComponent`, `DamageOnContactComponent`, or button component (all stripped patterns); interaction is authored as a trigger or an input action plus a script that mutates the target."
engineVersion: v1.0.234
date: 2026-09-28
license: "(c) Gessa, proprietary. Cite with attribution to https://gessa.ai/docs/. Terms: https://gessa.ai/terms/."
canonical: https://gessa.ai/docs/knowledge/playbooks/doors-buttons-and-interactions/
---

# Doors buttons levers and press-to-interact

## When to use

The player walks up to a door and it opens, or presses a key near a lever and a
gate lifts. There is no `InteractableComponent`, `DamageOnContactComponent`, or
button component (all stripped patterns); interaction is authored as a trigger
or an input action plus a script that mutates the target.

## The recipe

Author with `project_apply_script_semantic_patch` using the `ai_safe` op pack.

1. Give the interactive object a trigger `ColliderComponent`
   (`collision:"trigger"`) sized to the reach zone, via `project_add_component`.
   A `solid` collider blocks the player instead of detecting them.
2. Auto path (open on approach): `addTriggerHandler` `{ "handlerKey":"onApproach" }`.
   Inside the handler body (whole-body GessaScript via
   `replaceScriptFromGessaScript`) open the door: `ctx.entity.patchComponent`
   the door's `TransformComponent` to the open `position` or `rotation`, then
   `ctx.audio.play` a latch sound. Set `setBooleanState`
   `{ "field":"doorOpen", "value": true }` so re-entry is idempotent.
3. Press path (open on key): declare an `interact` action (see
   input-mapping-actions-bindings) and `addActionHandler`
   `{ "actionKey":"interact" }`. In the body, confirm the player is in range
   before acting: `ctx.physics.overlapTest` a small shape at the player, or
   `ctx.world.find` the nearest door and compare positions, THEN patch it open.
4. Show that an object is usable with a `HighlightComponent` (`enabled:true`,
   pick a `color`) toggled on while the player is inside the trigger.
5. Hinged swing vs slide: to swing, parent the door mesh under a hinge entity
   with `ctx.entity.setParent` and patch the hinge `rotation`; to slide, patch
   the door `position`. If the mesh carries an `open` clip, drive an
   `AnimationStateComponent` (`clipKey:"open"`, `playMode:"once"`) instead.

## Pitfalls

- `addActionHandler` fires for the action everywhere, not only near the object.
  Always gate with an overlap or distance check, or the far door opens too.
- Transform and component patches run on the server authority
  (`hierarchy.server_authority`); the door opens for every player, which is what
  you want for a shared door. Do not try to open it client-side.
- A door whose mesh origin is centered rotates around its middle, not its edge.
  Parent it under a hinge pivot and rotate the parent.
- Track open/closed with `setBooleanState`, not a local variable, so a reload or
  a second player sees the same door state.

## Verify

- `simulation_run` (`qa.run.start`) to prove approach and press both open the
  door and that a closed door blocks passage.
- `project_get_graph_snapshot` to confirm the trigger collider and the trigger
  or action handler landed on the graph.
