Skip to content

Raycasts, Triggers & Events

Ask the physics world questions with raycasts, and react to overlaps with sensor volumes and the onCollision script hooks.

Raycasting

A raycast fires a ray into the simulation and returns the nearest thing it hits. From a script:

ts
const hit = api.raycast(ox, oy, oz, dx, dy, dz, maxDist);
if (hit) {
  // hit.point   - where the ray struck (world space)
  // hit.normal  - the surface normal at that point
  // hit.distance - distance along the ray
  // hit.entity  - the GameObject that was hit, or null
}
  • ox, oy, oz is the ray origin, dx, dy, dz the direction, and maxDist how far to search.
  • The result includes the hit point and surface normal (Awaken uses Rapier's ray-and-normal cast), the distance, and the entity that owns the collider.
  • hit.entity is null when the ray strikes a body with no owning GameObject - most commonly the implicit ground plane. The hit is still returned; only the entity mapping is absent.
  • If no physics is running this session, api.raycast returns null (a clean miss), so scripts are safe to call unconditionally.

Raycasts are handy for line-of-sight checks, ground probes, shooting, and click-to-select in gameplay.

Triggers (sensors)

A trigger is a collider shape flagged as a sensor. It detects overlaps and reports them, but does not physically block anything - bodies pass straight through it. Use triggers for pickups, checkpoints, damage zones, doorways, and "player entered the room" events.

Turn on the trigger flag on a collider shape in the Inspector. A shape can be a trigger regardless of kind; the object can carry a solid shape and a trigger shape at the same time (a compound collider). To make a mesh collider a trigger, Awaken automatically builds it as a convex hull, because a hollow triangle mesh can't reliably report overlaps.

To react to a trigger, add a Script with one of the collision hooks below. Triggers and solid contacts flow through the same hooks - the difference is that a trigger doesn't stop movement.

Collision events

Under the hood, colliders are created with collision events enabled and with ActiveCollisionTypes.ALL, so even a kinematic character vs a fixed wall or static trigger generates events (Rapier would otherwise only report pairs involving a dynamic body). Each step the runner drains Rapier's begin/end contact events and folds them into a live set of currently-touching body pairs. The contacts() method maps those body pairs back to entity pairs - sourced from real engine contacts, not an AABB guess.

The script host turns that per-frame pair set into three hooks on your ScriptBehavior:

HookFires
onCollisionEnter(api, other)On the first frame this object's collider begins overlapping other (the "hit" edge).
onCollision(api, other)Every frame while the two colliders overlap ("stay").
onCollisionExit(api, other)On the first frame they stop overlapping (the "leave" edge). Not called if other was destroyed while overlapping - there's no entity to hand back.

Both entities in a touching pair receive the event, each told about the other. A pair is dropped the frame either entity dies. An entity with a pinned shape owns two bodies, but each entity pair is still reported only once.

Example - a pickup trigger

ts
class Coin implements ScriptBehavior {
  onCollisionEnter(api: ScriptApi, other: Entity) {
    if (api.world.getName(other) === "Player") {
      api.state.score = (api.state.score as number ?? 0) + 1;
      api.destroy(); // remove this coin (destroys the entity running this script)
    }
  }
}

📸 Screenshot - save as img/physics-trigger-pickup.png

A coin object selected in the editor, its Collider showing a sphere shape with the trigger flag checked, and a Script component with a coin pickup behaviour attached.

Fallback when physics is off

If a Play session runs without Rapier (WASM not ready), the script host falls back to a simple bounding-box overlap test to drive the same hooks - so onCollision still fires, just with coarser AABB accuracy instead of exact contacts. When physics is running, scripts always see the real engine contacts (including sensors).

See also

Awaken — browser-native WebGPU game engine.