Home/GANDER docs/GANDER navigation and AI

GANDER navigation and AI

Navmeshes, paths, cover and crowds of agents that walk them.

ai.mdllms-full.txt

Navmeshes, paths, cover, and crowds of agents that walk them. Everything is gander:: in Goose. Enemy brains stay your own code (a state machine with enum + match); the engine answers "how do I get there", "where can I hide" and moves bodies around walls and each other.

Build a navmesh once

var nav: gander::Nav;

fn main() {
    // ... build the level (walls as MESH_CUBE boxes, models, terrain)
    nav = gander::nav(gander::NavSettings { radius: 0.4, height: 1.8 });
    gander::nav_add_mesh(nav, gander::MESH_CUBE, floor_xf);
    for w in walls { gander::nav_add_mesh(nav, gander::MESH_CUBE, w); }
    gander::nav_add_model(nav, house, house_xf);                     // rest pose, skinned parts skipped
    gander::nav_add_terrain(nav, land, gander::v3(0.0, 0.0, 0.0), 150.0);   // ground within 150 m
    gander::nav_link(nav, ledge_top, ledge_bottom, false);           // a drop only walked down
    if !gander::nav_build(nav) { gander::fail("no walkable navmesh"); }
}

Add every walkable and every blocking surface: floors, walls, stairs, props big enough to walk around. NavSettings: radius (how close to walls), height (headroom), climb (tallest step), slope (degrees), cell (voxel size: 0 = radius / 2, good indoors; 0.3 to 0.5 for large outdoor areas, it builds much faster), tile (meters per tile, 32). nav_add_terrain samples the ground every 4 cells (at least 1 m), too coarse for trenches and ditches a few metres wide: nav_add_terrain_detail(nav, land, center, radius, step) takes the step (0 = every height sample; keep the radius to where agents walk). One Nav per agent size: a soldier and a tank need two. The log prints tiles, polygons and build time; nav_debug(nav, color) draws the polygon edges.

Paths and queries

nav_path(nav, a, b, out: float3[>..]&) -> bool   corners of the shortest walk; false when no way
                                                  (cut off: ends as near as it gets, still true)
nav_closest(nav, p) -> float3                     snap a point onto the mesh
nav_on(nav, p) -> bool                            is p standing on it
nav_raycast(nav, a, b) -> f32                     1 = a straight walk gets there; less = a wall or drop first
nav_random(nav, center, radius) -> float3         a reachable point (patrols, wandering, spawns)
nav_cover(nav, near, radius, threat, out&) -> i32 spots beside walls that hide them from threat, nearest first
nav_block(nav, center, size, yaw) -> i32 id       closed doors, barricades, a burning car
nav_unblock(nav, id)                              (the touched tiles rebuild over the next frames)

nav_raycast is line of sight on the floor plan: a cheap "can that guard see the player" before a real raycast against physics bodies. nav_cover uses the navmesh's walls (tall cover).

Agents (crowds)

The engine walks agents every frame with steering, avoidance and separation; your code decides where they go.

let a = gander::agent_add(nav, spawn, 3.5);            // speed m/s
gander::agent_move(nav, a, gander::camera_pos());      // re-send when the target moves (a few times a second is plenty)
let p = gander::agent_pos(nav, a);                     // draw the character here
let v = gander::agent_vel(nav, a);                     // face it: yaw = atan2(-v.x, -v.z); pick walk / run by length(v)
if gander::agent_arrived(nav, a, 1.0) { ... }          // within 1 m of where it was sent

agent_stop, agent_teleport, agent_speed (sprint, limp), agent_remove (dead: then play a ragdoll from its last position). Up to 128 agents per Nav. Agents stand on the navmesh; for jumping or flying enemies, move them yourself and use nav_path for the route.

Recipes

Patrol, chase, search. State machine per enemy: Patrol sends the agent to nav_random points (or a list), Chase re-sends agent_move to the player every 0.25 s while nav_raycast(nav, eye, player) == 1.0 or a physics raycast sees them, Search goes to the last seen point then patrols nearby.

Turn to face, then act. A creature standing still that must face the player (to attack, to breathe fire) turns with its turn clips, not by a rate over its idle: when the angle to the target passes ~40 degrees, play turn_l / turn_r / turn_180 with anim_root_turn on and add anim_root_yaw to its yaw (the recipe "Turning in place with turn clips" in the API sheet's animation section); attack once it ends. Moving agents keep facing their velocity.

Take cover and shoot. On first contact: nav_cover(nav, my_pos, 12.0, player, spots), send the agent to spots[0], and when it arrives alternate crouched (behind cover) and standing (fire) every couple of seconds.

Circle and take turns (melee). Let only one or two enemies attack at once; send the rest to points on a ring around the player (player + v3(cos(a), 0, sin(a)) * 4, snapped with nav_closest), moving their angles a little each second.

Doors. nav_block the doorway while it is shut and nav_unblock when it opens; agents re-route by themselves.