NPC CLI

Home

Quickstart

Let's spawn, move and remove an NPC. A "Non-Player Character" (NPC) is a video game character not controlled by the Player.

Desktop users can follow along.

Mobile users can watch the video then skip to the next section.

To follow along:

  • open the Viewer on the right.
  • click play and sync tabs.

Mobiles have the Viewer at the bottom; see the next section.

The video above steps through the commands below. They're a natural place to start, but far from the whole story.

Copy all?
Our first commands
spawn npc:rob at:$( click 1 )
 
move npc:rob to:$( click 1 )
move npc:rob to:"$( click 2 )"
move npc:rob to:$( clicks 2 )"
 
# respawn with skeleton key
spawn npc:rob at:$( click 1 ) granted:.
 
make npc:rob do:$( click 1 )
 
# dust to dust
remove rob

Then we've seen a Non-Player Character (NPC) driven by a Command Line Interface (CLI), which is why our site is called NPC CLI.

Duh!

Running a profile

There are two basic roles we play whilst developing NPCs: the Game Master controls the game, the Player experiences the game.

Desktop users might alternate between these roles, designing behaviours while trying to understand the Player's perspective. Mobile users should probably assume the Player role. This means they only need to open a terminal whose profile (initial script) sets everything up.

To use the quickstart profile in the Viewer, sync tabs.

Copy all?
quickstart.profile.sh
source /etc/{util,core}.sh
source /etc/{util,core}.js.sh
import demoClickToMove from demo
 
awaitWorld
 
spawn npc:rob at:'{x:4.5,y:7.5}' \
  as:soldier-0, granted:.
 
ptags always; click meta.floor |
  demoClickToMove npc:rob &
 
Copy all?
demoClickToMove from demo.js
/**
 * @param {NPC.ClickOutput} input
 * @param {NPC.RunArg} ctxt
 * @param {{ npcKey: string }} [opts]
 */
export function demoClickToMove(input, { api, args, w }, opts = api.jsArg(args, { npc: 'npcKey' })) {
  const npc = w.npc.get(opts.npcKey);
  // catch permits override and ignores points too far from nav
  npc.move({ to: input, close: 0.5 }).catch(() => {});
}

Backdrop

The 3D worlds on this site implement Robert Pearce's Starship Geomorphs i.e. architectural drawings of starships blocks, connectable along their edges.

We use Eric Smith's PNGs to recursively define SVG symbols. Each hull symbol For example 102--hull.svg induces a 3D geomorph. Our maps amount to possibly edge-sharing rectangles, where each rectangle corresponds to a hull symbol.

For characters we use a basic Blender model, texture-mapped with simplifications of Minecraft skins.

Motivation

Our underlying motivations are:

  • NPCs indistinguishable from human-controlled characters.

    A human Game Master makes this tractable. Can one human act indistinguishably from many humans playing individual parts? It also motivates realtime control systems.

  • Understand Recast-Detour.

    Our NPCs are driven by Recast-Detour. We'll analyse this important C++ library in depth.

  • Generic scripting for three.js.

    We interpret mvdan-sh syntax trees as JavaScript. In production one can run commands. In development, shell functions and processes are synced with JS modules.Technically this approach is independent of three.js.

What's next

We provide two blogs. Our main blog is about creating, combining and controlling NPC behaviour in realtime. Our dev blog is concerned with the underlying software (internal and external). They're separated in line with our underlying motivations.

main

🚧 Multiple NPCs

dev

🚧 Introducing the World

Comment below to start a GitHub discussion