Onceworlds
Engine · Navigation

Navigation: things that find their own way

Enemies, guards and bots that find their own way: NavAgent, bot orders, links.

Enemies that chase the player round walls and up ramps, guards on their rounds, animals that wander and bolt, bots in a 3D level with platforms, platformer enemies that jump to the next ledge, cars that drive to a place. One NavAgent line on the walker does it.

What you do. Build the level from static colliders as usual (Body3D.static() and a Collider3D, or Body2D.static() and a Collider2D): that is the walkable area. Give the walker its body and controller as usual (Character3D, TopDown, Platformer2D, ArcadeVehicle), and one NavAgent line. Controllers() already brings navigation.

What you don't. No waypoints placed by hand, no baking step, no grid to draw: the walkable area is baked from the static colliders the first time something needs it, baked again when they change, and kept by a hash of the level, so a level built again each round is free. The agent's size comes from its collider, its speed from its controller.

Recipes

Enemies chase the nearest player. Players join a group as their bodies spawn and enemies chase the group; in a networked game chase a replicated component instead (NavAgent.chase(Player)), since groups are a page's own.

import { Transform } from '@onceworlds/engine';
import { Body3D, Collider3D, Character3D, Intent, NavAgent } from '@onceworlds/engine/modules';

export const Enemy = (at) => [
  Transform({ position: at }), Body3D.kinematic(), Collider3D.capsule({ radius: 0.4, height: 1.8 }),
  Character3D({ walkSpeed: 3.5 }), Intent(),
  NavAgent.chase('players', { range: 30 }),    // round walls, up ramps, to the nearest player within 30 m
];
// Where a player's body is made (Flow's round.spawn, say): body.addToGroup('players');

An NPC patrols waypoints, waiting at each. In a scene file, "route": { "$node": "../Rounds" } names a node whose children are the stops.

import { NavAgent } from '@onceworlds/engine/modules';

export const guard = NavAgent.patrol([[-8, 0, -8], [8, 0, -8], [8, 0, 8]], { wait: 2, loop: 'pingpong' });

Flee when hurt, chase when well: a bot's brain gives the order in one line when it thinks (on the host, at its reaction time).

import { defineBotSystem } from '@onceworlds/engine/modules';
import { Health } from './components.js';

export const Monsters = defineBotSystem({
  name: 'monsters',
  query: [Health],
  think(ctx, bot, health) {
    if (health.hp < 30) ctx.flee('players', 12);
    else ctx.chase('players');
  },
});

The same anywhere else, through the nav service (ctx.nav in a system, node.nav in a script):

import { defineSystem } from '@onceworlds/engine';
import { Health } from './components.js';

export const Scatter = defineSystem({
  name: 'scatter',
  stage: 'fixed',
  authority: 'host',
  query: [Health],
  run({ query, nav }) {
    query.each((entity, health) => {
      if (health.hp < 30) nav.flee(entity, health.lastHitBy || 'players', 12);
    });
  },
});

A platformer enemy follows the player from ledge to ledge: jumps and drops between platforms are found from its Platformer2D numbers.

import { Transform } from '@onceworlds/engine';
import { Body2D, Collider2D, Platformer2D, Intent, NavAgent } from '@onceworlds/engine/modules';

export const Slime = (x, y) => [
  Transform({ position: [x, y, 0] }), Body2D.kinematic(), Collider2D.capsule({ radius: 0.3, height: 1 }),
  Platformer2D({ speed: 4 }), Intent(), NavAgent.chase('players'),
];

In a scene file (a Build project), the same monster is a body with the component:

{ "id": "monst1", "name": "Monster", "kind": "CharacterBody3D", "components": {
  "Transform": { "position": [6, 1, 0] }, "Collider3D": { "shape": "capsule", "radius": 0.4, "height": 1.8 },
  "Character3D": { "walkSpeed": 3.5 }, "NavAgent": { "mode": "chase", "group": "players", "range": 30 } } }

Its script hears arrived, stuck and unreachable, and gives new orders with node.nav.flee(node.entity, 'players').

When something is in the way

Checking it

The reference, with every option: API.md, "Navigation (1.2)".