What a villager is

Four villagers live round the cottage. This page writes out one of them, the follower: a tall slim man, 1.864 m, with light skin, blue eyes and close-cropped hair, in a blue-gray shirt tucked into brown trousers. He lives on the lawn west of the cottage's front, at (-25, 342), and starts there facing north. He keeps his distance: whichever visitor is nearest, he walks to stay about 6 m from them, following when they walk off and stopping when they stop, keeping to the walkway where that does not cost him much more distance.

He walks as a person does: his feet stay planted on the ground while they bear his weight, his heel strikes first with the toe up, his pelvis dips and rises with his steps, his arms swing against his legs; when he stops he finishes two more steps and stands with both feet flat, breathing and shifting his weight. His face is alive: he blinks 15 to 20 times a minute, his eyes dart and his head follows, his brows and lips drift a little. Anyone, villager or visitor, can hail: press H and your figure raises its right arm and waves for 3 seconds, turning its head and leaning its chest whatever its legs are doing, and smiles while it waves. A villager does the same when its mind decides to. By default nobody answers a hail; this page adds, as an illustration, a villager smiling back for 2 seconds at anyone who hails within 4 m.

The listing

// ===== The person: body, face and clothes, a design the humanbody package provides (shown here in part) =====
enum sexKind { male, female }

part person                                              // a design: no field changes while the world runs
  field sex     : sexKind = male
  field build   : humanbody.build = average             // the 19 survey measures (height among them), muscle, belly, bust, age
  field face    : humanbody.face = usual                // seven numbers, 0 to 1, 0.5 the sex's usual
  field look    : humanbody.look                        // skin, lips, brows, iris; how much the crop darkens the scalp
  field outfit  : humanbody.outfit = shirtAndTrousers
  field cloth   : (color, color)
  field boots   : color
  part body: {
                var made = humanbody.body(sex, build, face);
                humanbody.faceRig(made);                // adds 50 morph targets, a jaw and two eye bones, ears, teeth, tongue, mouth
                made
              }, material: humanbody.skin(look)
    skinnedTo(self, body)
    part leftEye  : humanbody.eyes(look.iris), material: materials.eyeGloss, at: body.bone(leftEye)
    part rightEye : humanbody.eyes(look.iris), material: materials.eyeGloss, at: body.bone(rightEye)
    part shirt    : humanbody.top(outfit, color: cloth.0)
      fittedTo(self, body)
      under(self, trousers)
      skinnedTo(self, body)
    part trousers : humanbody.bottom(outfit, color: cloth.1)
      fittedTo(self, body)
      skinnedTo(self, body)

part people                                              // the cast: persons placed nowhere
  part tallSlimMan: person(build: (height: 1.864 m, muscle: 0.45, belly: 0.1),
                        face: (brow: 0.4, nose: 0.7, noseWidth: 0.4, lips: 0.4, jaw: 0.45),
                        look: (skin: srgb(236, 196, 176), iris: srgb(80, 120, 160), crop: 0.5),
                        cloth: (srgb(70, 92, 120), srgb(78, 70, 60)), boots: srgb(70, 48, 34))

// ===== Where minds may walk: built once from the land and the solids =====
field walkable: navmesh.mesh = navmesh.build(from: [land, solids],
                   agent: (radius: 0.3 m, height: 1.9 m, climb: 0.4 m, slope: 40 deg),
                   cells: (across: 0.2 m, up: 0.1 m),
                   costs: (path: 1, other ground: 3))

// ===== The villager: a person who is a being, with a mind =====
part villager
  field who  : person
  field home: place
  field mind: npcbehave.mind                             // a whole behavior, by name, with its dials
  field seed: seed
  part body: who
    being(self)                                          // a being: it acts each step; no visitor takes it over

  // what the being's pose and face are: the author's formulas over the behaviors' values
  field gait      : animator = walkgait.walker(body, seed: seed)   // built once; it writes gait.pose
  field hailPose  : pose                                 // written by the hail's clip
  field wave      : fraction = 0, mutable: true          // how much of the hail is laid over the walk
  field hailing   : ?handle = None, mutable: true
  field smile     : fraction = 0, mutable: true, lives: client   // the hail's own smile
  field reply     : fraction = 0, mutable: true, lives: client   // answering another's hail (illustration)
  derive(body.pose, blend(gait.pose, hailPose, weight: wave,
                          mask: bones([rightUpperArm, rightForearm, rightHand, neck, chest])))
  derive(body.face.happiness, max(smile, reply))

  on(created, {
    start(gait);                                                   // feet planted; walk, run, idle
    start(mind(body, on: walkable));                               // writes the body's actions
    start(facerig.alive(body, [blink, gaze, idle]));               // each view's own, within 40 m of the eye
    start(chase(smile, toward: if hailing != None { 0.85 } else { 0 },
                settle: rate(99%, per: sim(0.35 s))));
  })

  // the hail: a wave over five bones for 3 s; a second press during it is ignored
  on(action(hail, by: body), {
    if hailing == None {
      set(hailing, Some(start(together([
        clip(humanbody.hail, on: hailPose),
        keys(wave, [key(sim(0 s), 0), key(sim(0.33 s), 1), key(sim(2.5 s), 1), key(sim(3 s), 0)],
             ease: smoothstep)]))));
    }
  })
  on(ended(hailing), set(hailing, None))

  // the response: smile back at anyone who hails within 4 m           // illustration
  on(action(hail, by: v), {
    if v != body && distance(v, body) < 4 m {
      start(keys(reply, [key(sim(0 s), 0), key(sim(0.35 s), 0.85), key(sim(1.65 s), 0.85), key(sim(2 s), 0)]));
    }
  })

// ===== The follower, placed =====
part village
  part follower: villager(who: people.tallSlimMan, home: (-25, 342), seed: 37,
                          mind: npcbehave.follow(nearest(visitors), keep: 6 m)),
                 at: (-25, land.height((-25, 342)), 342)                // facing north

Walking through it

The body, fitted and grown

person is a design the humanbody package provides (Fitted and grown: the person in full), whose fields describe a body: its sex (an enum), build (the survey measures, stature among them), face, look, outfit, cloth colors and boots. Its body part is a pipeline of the humanbody package's tools: a body built from survey measures (mesh, a skeleton of 22 bones, skin weights), then a face built on the body's head (50 morph targets, a jaw and two eye bones, with its ears, teeth, tongue and mouth), drawn in a skin painted from the look, bent by its own skeleton. The eyes ride two bones of the skeleton rigidly, one part for each (two places are two parts). The shirt and trousers are fitted: each is the body's own surface over a region, pushed out by an ease plus the thickness beneath and cut at its hems, the shirt (4 mm ease, 3 mm thick) under the trousers (6 mm, 4 mm), tucked in; both bend with all the body's bones (Fitted and grown: the relations). A new outfit rebuilds only the clothes and which of the body's triangles are drawn; a new build rebuilds everything below the body.

people.tallSlimMan is a copy of person with the follower's own values, placed nowhere: one of a cast of seven. A record field takes a partial override, build: (muscle: 0.45, belly: 0.1), leaving the rest at the preset's.

Where minds may walk

walkable is a world field holding a navigation mesh, the map a mind plans routes on: built once from the land and the solids, for an agent 0.3 m round and 1.9 m tall that can step up 0.4 m and climb slopes of 40°, in cells 0.2 m across and 0.1 m high, with ground off the walkway costing three times as much to cross (Package behaviors).

The villager: a person who is a being, with a mind

villager is a design with four own fields: who the villager is (a person), its home, its mind and its seed. part body: who makes the body a copy of the person, and the call being(self) in its body makes it a being: a part that acts each step, which no visitor may take over. mind is a field whose value is a whole behavior from the npcbehave package, with its dials: here npcbehave.follow(nearest(visitors), keep: 6 m), given per copy (Package behaviors: a behavior is a value). nearest(visitors) is whichever visitor-driven being is nearest at this step, without naming any visitor.

When a villager is made, its on(created, ...) start act (Loading) starts four things as animators, each writing one value of the being every step: the walk (walkgait's walker, made for the person's height and the villager's seed, writing gait.pose); the mind (writing the body's actions, the same actions a visitor's keys would produce); the face (facerig's blinks, glances and idle drift, each view's own); and the smile, a settling chase that closes 99% of the gap to 0.85 in 0.35 s while the being hails, and back to 0 after.

The pose and the face: the author's formulas

The body's pose is not written by the walk directly: it is a subscription over two poses, the gait's and the hail's, blended by wave over five bones (the right upper arm, forearm and hand, the neck and the chest). The face's happiness is the larger of the being's own smile and its answering smile. Several effects on one value meet in a formula the author wrote, never in a combining rule hidden in the engine (Subscriptions: several effects on one value). smile and reply live on each visitor's machine (lives: client): two visitors may see a smile at slightly different moments, and that is correct.

The hail and its response

hail is a standard action every being may take (Visitors: three tiers): H for a visitor, the mind's choice for a villager, the same action either way. The handler is an event on the being's own hail. It starts a together of two animators, the humanbody package's hail pose played as a clip, and a key track on wave that rises over the first 0.33 s, holds, and falls over the last 0.5 s of 3 s, each with an S-shaped ease. Its handle is kept in hailing, so a second press during a hail is ignored: "the newest takes over" would restart the wave, and the guard says no (Animators: one value per name). When the group ends, hailing is cleared.

The response is the illustration: when any other being hails within 4 m, this villager's reply rises to 0.85 over 0.35 s, holds, and falls by 2 s. It is a world rule on top of the library's mind; the mind knows nothing of it. Because reply is a client value, each view can decide it from the hail it already sees, with nothing sent.

The follower, placed

follower is a copy of villager under village, with its own person, home, seed and mind, standing on the land at its home (Terrain). Its defined place is only where it starts: once it is made, a being's place is execution state the engines own (Frames: beings place themselves). The other three villagers would be three more lines: the round (npcbehave.patrol), the gardener (npcbehave.graze) and the shy one (npcbehave.flee), as on Package behaviors.

← The land | Contents | A boss and its army →