A hackable template for creating small and fast browser games.
1import {mat4_get_translation} from "../../lib/mat4.js";
2import {Vec3} from "../../lib/math.js";
3import {path_find} from "../../lib/pathfind.js";
4import {quat_multiply} from "../../lib/quat.js";
5import {
6 vec3_add,
7 vec3_distance_squared,
8 vec3_normalize,
9 vec3_transform_position,
10} from "../../lib/vec3.js";
11import {Entity} from "../../lib/world.js";
12import {Game} from "../game.js";
13import {Has} from "../world.js";
14
15const QUERY = Has.Transform | Has.NavAgent | Has.Move;
16
17export function sys_nav(game: Game, delta: number) {
18 for (let i = 0; i < game.World.Signature.length; i++) {
19 if ((game.World.Signature[i] & QUERY) == QUERY) {
20 update(game, i);
21 }
22 }
23}
24
25function update(game: Game, entity: Entity) {
26 let agent = game.World.NavAgent[entity];
27 if (agent.Goal !== undefined) {
28 console.time("path_find");
29 // Search FROM the goal TO the origin, so that the waypoints are ordered
30 // from the one closest to the origin.
31 let path = path_find(agent.NavMesh, agent.Goal.Node, agent.Origin);
32 console.timeEnd("path_find");
33
34 if (path) {
35 // Discard the first waypoint, which is always the origin node, and
36 // the last waypoint, which is the goal's node.
37 let waypoints = path.slice(1, path.length - 1);
38 agent.Waypoints = waypoints.map((w) => ({
39 Node: w,
40 Position: agent.NavMesh.Centroids[w],
41 }));
42 // Add the destination's world position as the last waypoint. The
43 // waypoint list has always at least one waypoint, which is
44 // important for cases when the path is empty, i.e. the origin and
45 // the goal were the same node.
46 agent.Waypoints.push(agent.Goal);
47 }
48 agent.Goal = undefined;
49 }
50
51 if (agent.Waypoints) {
52 let transform = game.World.Transform[entity];
53 let position: Vec3 = [0, 0, 0];
54 mat4_get_translation(position, transform.World);
55
56 let current_waypoint = agent.Waypoints[0];
57 let distance_to_current_waypoint = vec3_distance_squared(
58 position,
59 current_waypoint.Position,
60 );
61 if (distance_to_current_waypoint < 1) {
62 let origin = agent.Waypoints.shift();
63 if (origin !== undefined) {
64 agent.Origin = origin.Node;
65 }
66 if (agent.Waypoints.length === 0) {
67 agent.Waypoints = undefined;
68 }
69 }
70
71 // Transform the waypoint's position into the agent's self space which
72 // is where sys_move runs.
73 vec3_transform_position(position, current_waypoint.Position, transform.Self);
74 vec3_normalize(position, position);
75
76 let move = game.World.Move[entity];
77 vec3_add(move.Direction, move.Direction, position);
78
79 if (position[0] < 0) {
80 // The target is on the right.
81 quat_multiply(transform.Rotation, move.LocalRotation, [0, -1, 0, 0]);
82 } else {
83 // The target is on the left or directly behind.
84 quat_multiply(transform.Rotation, move.LocalRotation, [0, 1, 0, 0]);
85 }
86 }
87}