> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/core-engine-concepts/navmesh-and-collision.md).

# Navmesh & Collision

Navmeshes define the **walkable areas** in your world. Use them to:

* Snap spawned items onto reachable ground
* Lock players/NPCs to walkable regions

Colliders can also be used to carve navmeshes in the editor.

### Components (quick overview)

* **`Navmesh`**: the baked navigation mesh you query and rebuild
* **`Navmesh_Loop`**: polygon loops that define walkable boundaries (and holes)
* **Colliders**: `Box_Collider`, `Circle_Collider`, `Edge_Collider`, `Polygon_Collider`

{% hint style="info" %}
For movement/pathfinding (`Movement_Agent`) see [Movement Agents/NPCs](/core-engine-concepts/movement-agents-npcs.md).
{% endhint %}

### Navmesh API reference

```go
Navmesh :: class : Component {
    // Project a point onto this navmesh.
    // triangle_hint is an acceleration hint you can reuse for nearby queries.
    try_find_closest_point_on_navmesh :: method(
        to_point: v2,
        result: ref v2,
        triangle_hint: ref s64
    ) -> bool;

    rebuild_immediately :: method() -> bool;
}
```

### Editor setup (building a navmesh)

1. Create an entity and add a `Navmesh` component.
2. Create child entities with `Navmesh_Loop` components to define walkable polygons.
3. For each `Navmesh_Loop`:
   * Add points to define the loop shape
   * Toggle **Flip Inside Outside** to make a hole/obstacle instead of walkable space
4. Use the navmesh debug options in the inspector to visualize the triangles.

{% hint style="info" %}
By default, a `Navmesh_Loop` defines a walkable area. Flip it to "punch holes" (unwalkable islands) into an existing navmesh.
{% endhint %}

### Colliders and navmesh loops

Colliders can also contribute loops via collider inspector options (for example "Make Navmesh Loop" / "Flip Navmesh Loop").

Important caveats:

* Navmeshes hash their loop, collider, tilemap, and child-navmesh inputs each frame and rebuild automatically when those inputs change.
* Use an explicit rebuild only if you need to query the updated navmesh immediately in the same frame.
* If you want a navmesh that ignores colliders entirely, use the navmesh inspector option to ignore colliders.

### Spawning on navmesh (clamp to reachable ground)

Use `try_find_closest_point_on_navmesh` to project a desired position onto the closest valid point on a navmesh:

```go
spawn_on_navmesh :: proc(navmesh: Navmesh, desired_position: v2) -> Entity {
    spawn_pos: v2;
    triangle_hint: s64; // 0 = not set yet

    if navmesh.try_find_closest_point_on_navmesh(desired_position, ref spawn_pos, ref triangle_hint) {
        e := Scene.create_entity();
        e.set_local_position(spawn_pos);
        return e;
    }

    return null;
}
```

{% hint style="info" %}
Reuse `triangle_hint` for repeated queries in the same area (loot spawners, wave spawns, etc). It can significantly speed up projection.
{% endhint %}

### Rebuilding navmeshes immediately

Most geometry changes are picked up automatically. Use this only when you need to force timing:

* **`rebuild_immediately()`**: rebuilds now (use only if you must query the updated mesh in the same frame)

```go
ok := navmesh.rebuild_immediately();
if !ok {
    log_info("Navmesh rebuild failed", {});
}
```

{% hint style="info" %}
Parent navmeshes refresh after child navmeshes, so stitched parent meshes automatically pick up child mesh input changes.
{% endhint %}

### CSL movement physics and triggers

Movement\_Agent entities have a CSL physics path for simple ballistic movement and/or trigger detection.

Rigidbody collision: the agent's enabled non-trigger colliders block against enabled non-trigger world colliders, taking `category_bits` / `mask_bits` filtering into account. Movement stops at the first hit and slides along the hit surface.

Trigger overlap detection: if a collider has `is_trigger == true`, it can report when another collider enters, stays inside, or exits its bounds through callbacks.

{% hint style="warning" %}
**Use colliders sparingly.** Only add them to gameplay-critical surfaces: map boundaries, platforms the player must land on, key obstacles, and deliberate trigger volumes. Do **not** add colliders to decorative entities (trees, bushes, background props, etc.). Most entities in a scene should have no collider.
{% endhint %}

```go
Trigger_Listener :: class : Component {
    ao_start :: method() {
        trigger_collider := entity.get_component(Circle_Collider); // any collider type works, as long as `is_trigger` is true
        trigger_collider.on_trigger_start = proc(self: Collider, other: Collider) {
            log("OVERLAP START: %", {other.entity.get_name()});
        };

        trigger_collider.on_trigger_stay = proc(self: Collider, other: Collider) {
            log("OVERLAP STAY: %", {other.entity.get_name()});
        };

        trigger_collider.on_trigger_end = proc(self: Collider, other: Collider) {
            log("OVERLAP END: %", {other.entity.get_name()});
        };
    }
}
```

Movement\_Agent has velocity/friction fields but you do not have to use them. For a stationary trap, teleporter, pickup zone, or similar trigger volume, add a Movement\_Agent and a trigger collider to the entity and assign trigger callbacks.

### Proximity checks and custom hit logic

For non-Movement\_Agent trigger zones, pickups, projectile checks, or gameplay that does not need collider trigger callbacks, use:

* `Scene.get_all_components_in_range` / `Scene.get_closest_component_in_range`
* Simple distance checks (`in_range`) to decide "inside", "picked up", "hit", etc.

#### Scene query helpers

```go
Scene :: struct {
    get_all_components_in_range :: proc(position: v2, range: float, results: ref [..]$T);
    get_closest_component_in_range :: proc(position: v2, range: float, $T: typeid) -> T, bool;
}
```

{% hint style="info" %}
Prefer server-side for gameplay consequences (damage, pickups, scoring). Use local-only checks for cosmetic feedback.
{% endhint %}

#### Trigger volume (enter / stay / exit)

To simulate a trigger zone, keep a list of who was inside last frame and diff it against the current frame’s results.

```go
contains_id :: proc(list: []u64, id: u64) -> bool {
    for x: list if x == id return true;
    return false;
}

Trigger_Zone :: class : Component {
    radius: float @ao_serialize;
    last_inside: [..]u64;

    ao_update :: method(dt: float) {
        center := entity.world_position;

        players: [..]Player;
        Scene.get_all_components_in_range(center, radius, ref players);

        current_inside: [..]u64;

        for p: players {
            if in_range(p.entity.world_position, center, radius) {
                current_inside.append(p.entity.id);

                if !contains_id(last_inside, p.entity.id) {
                    // on_enter
                    Notifier.notify(p, "Entered zone!");
                }
                else {
                    // on_stay
                }
            }
        }

        for id: last_inside {
            if !contains_id(current_inside, id) {
                // on_exit (you may want your own id.player lookup)
            }
        }

        last_inside = current_inside;
    }
}
```

{% hint style="warning" %}
This manual trigger pattern is still useful when you do not want to add a Movement\_Agent and collider trigger volume. If something is teleported/despawned between updates, you may not get a clean "exit" unless you handle cleanup.
{% endhint %}

#### Pickups (closest in range)

```go
try_pickup_near_player :: proc(player: Player) {
    pos := player.entity.world_position;

    pickup, ok := Scene.get_closest_component_in_range(pos, 1.5, Pickup);
    if ok && pickup != null {
        if in_range(pickup.entity.world_position, pos, 1.5) {
            pickup.claim(player); // your own logic (grant + destroy)
        }
    }
}
```

#### Fast movement "hits" (simple sub-stepping)

If you move quickly (dash, projectile), you can miss narrow targets when checking only the final position. A simple workaround is sub-stepping: sample a few points between last and new position and run the same range queries.

```go
hit_check_move :: proc(last_pos: v2, new_pos: v2) {
    steps := 4;
    for i := 1; i <= steps; i += 1 {
        t := (i.(float)) / (steps.(float));
        p := lerp(last_pos, new_pos, t);

        enemies: [..]Enemy;
        Scene.get_all_components_in_range(p, 1.0, ref enemies);
        for e: enemies {
            if in_range(e.entity.world_position, p, 1.0) {
                // apply hit once, start cooldown, etc.
            }
        }
    }
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.allout.game/core-engine-concepts/navmesh-and-collision.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
