Skip to content
Work in progress. These docs describe Spawnite at launch, and some parts are still being built.

Npc

<Npc> spawns a non-player character inside a World: the same walker, body, avatar, clips and navmesh path as the player’s character, driven by its routines instead of a person. World of Warcraft makes an NPC the same way, as data: a name, a title, a level, a faction, and a path with a pause at each point. An NPC with no routines stands and plays idle. An avatar is posed less often while it is small on screen or out of view, as another player’s character is, unless cullingMode says otherwise; a model is posed every frame.

import { Faction, Interact, Npc, NpcEvent, routines } from "@spawnite/engine";
const guard = routines("guard")
.routine("post", (r) => r.wait({ between: [10, 20] }).turn(90))
.start("post");
export function Guard() {
return (
<Npc
name="Bram"
title="Gate guard"
level={8}
faction={Faction.Neutral}
position={[2, 0, 4]}
routines={guard}
>
<Interact prompt="Talk" />
</Npc>
);
}

It takes <CharacterSpawn>’s body fields, its look, position and facing, and these of its own:

  • name and title: drawn over its head in Text’s white with a dark edge, the title under the name as WoW draws a subname. The AI tree names it by them and its faction: “Mira, a herbalist, friendly”.
  • level: a number a game reads.
  • faction: Faction.Friendly, Neutral or Hostile, friendly by default. It says which side the NPC is on, and does not make it a target: an ability strikes an NPC that carries TargetableTrait, which Targeting shows how to give it.
  • routines: what routines() built.
  • leash: metres from its spawn it may walk. A route point past the leash is skipped with a warning at spawn, and a walk that carries it past the leash ends and walks it home, as WoW’s evade does. A routed NPC has none by default, since its route bounds it.

Its children are behaviours on it, as on an Entity. <Interact prompt="Talk" /> shows the prompt when a player’s character comes close and emits the InteractedEvent event on a press, which a routine answers with NpcEvent.Interacted. <Dialog tree={...} /> gives it a conversation, which a press of that prompt starts for the player who pressed. While any conversation goes on, the NPC holds the TalkingTrait trait, which holds its routine alone.

An NPC wears one look, from the same props the player’s character takes:

  • avatar: a name registerAvatar registered, or the body itself.
  • model: the name of a model the game registered, such as the example template’s visor-bot. Where its file has clips, it plays the ones its pose asks for, idle at rest, walk and run blended by its speed, and jump off the ground. clips names the file’s own clip for each that the file names another way, such as { run: "Sprint" }, and clipSpeeds gives the ground speed at which its walk and its run play at their own pace. A routine’s play step names one of the poses’ clips, as Routines lists them, and a model plays its clip for that pose, through clips, or else the file’s clip of that name, such as land; a model with neither plays nothing over its pose, and a dev world warns once. A clip under any other name, such as a wave, plays with playClip, as on the player’s model.
  • shape and color: a primitive in a colour, the capsule when it names no look.

Two looks at once fail the typecheck, such as an avatar beside a model, or a color beside either: a color tints the primitive alone. This merchant wears the robot the player wears, and walks where it would run:

packages/engine/test/outside/Merchant.tsx
import { Faction, Interact, Npc } from "@spawnite/engine";
/** The lobby's merchant, in the robot the player wears: the game's
* registered `visor-bot`, which walks where it would run. */
export function Merchant() {
return (
<Npc
name="Tobin"
title="Merchant"
faction={Faction.Friendly}
position={[4, 0, -3]}
model="visor-bot"
clips={{ run: "walk" }}
>
<Interact prompt="Talk" />
</Npc>
);
}

Its npc trait holds its name, title, level and faction, and what its routine is doing: the routine and the step, the seconds on the step, the route point it walks to and the pass along the route it is on, and the line a say step gave it, which draws right over its name, wrapped and faded with the camera’s distance. Its routines compile to a state machine whose trait dumps beside it under the name routines() took, miraRoutines for routines("mira"). A spawnite play dump shows both each step, so a test asserts where it is and what it is doing.

The tree shows each NPC as a row under its name, and the Map tab counts it under the avatar it wears, where it wears one: a registered avatar by its name, and a body handed over whole by its name, or by its file’s name where it has none, which a production build hashes. The inspector shows its npc trait and its machine’s card: the routine, the step in words, such as walk stall to well to herbs, the machine’s state and a wait’s seconds left. The Wiki tab lists each NPC under the npc kind with its routines in words and, where it has a Dialog, its lines by node with the choices each offers. A conversation’s machine shows in the Wiki tab under machine, as any machine does.

The room spawns and steps an NPC, and each page draws the one the room streams at its place in the scene, as it does an Entity. The routine system runs on the server, as Chase does, and a single-player game runs it on the page. A player’s press of its prompt reaches the room as Interact describes, and the stream carries its npc trait and its machine’s trait to every page, so each page draws the same line over its head. The room runs each conversation too, and streams its record to the talking player alone, as Dialog describes. Each page’s <Npc> compiles the same routines as it mounts, so the machine’s name is registered before the stream brings its trait, and the page’s inspector and Wiki tab show the routines in words as the room’s do.