Moods and moves
What a figure shows for each mood, how it reacts as one changes, its fourteen faces, and the moves it makes on demand.
// What each of an agent's states shows: its expression, the loop
// it moves in, and now and then a move.
const MOOD = {
idle: { expression: "neutral", loop: "breathe" },
thinking: { expression: "thinking", loop: "think" },
working: { expression: "working", loop: "work" },
happy: { expression: "happy", loop: "breathe", now: "pop" },
trouble: { expression: "trouble", loop: "worry", now: "shake" },
sleep: { expression: "sleeping", loop: "rest" },
};
// As it changes, a reaction, each shape playing it its own way.
reactionTo("working", "happy"); // "celebrate"
reactionTo("idle", "sleep"); // "doze"
// A loop that turns something comes home every few seconds and
// stops only there, so the next never starts from a jump.
act.stop ??= stopFor(act, now, shape, speed);
A figure shows what its agent is doing through mood, one of six:
idle: waiting. It breathes, blinks, glances about and does its habit now and then.thinking: setting up. It looks up and aside, and the cube scrambles and solves itself like a Rubik's cube.working: at work, its eyes narrowed and scanning, each shape busy its own way: the cube's layers tick like clockwork, the cloud rains.happy: done, something to look at. Happy eyes, and now and then it turns into a star.trouble: in trouble. Squeezed eyes, worrying, shaking now and then.sleep: switched off. Eyes shut, grey and dim, z's drifting off; it doesn't follow the pointer or answer a pet.
Reactions
As the mood changes it reacts, each shape its own way:
- Into
happy, it celebrates: the cube bursts and snaps back, the sphere splashes, the pyramid launches its capstone, the block spins its dials like a slot machine, the cloud gets a rainbow. - Into
trouble, it fails: its eyes cross and its colour drains. - Into
thinkingorworkingfrom anything else, it gets going. - Into
sleep, it dozes off, yawning as its lids close; out of it, it wakes with a stretch. - From
happyback toidle, a nod: what it finished has been seen to.
When one celebrates, fails or is petted, the other figures on the page turn to look. reactionTo(from, to) says which reaction a change plays, for anything else that should follow along.
Faces
// A face is a few numbers: where the upper lid cuts each eye at
// its inner and outer side (li, lo), a lower lid rising in an
// arc (smile), the eyes' size and gaze, and the head's turn.
const FACES = {
neutral: { glyph: "rect", li: 0, lo: 0, size: 1 },
sad: { glyph: "rect", li: 0.05, lo: 0.5, pitch: -14 },
thinking: { glyph: "rect", li: 0.2, dx: 0.045, yaw: 14 },
love: { glyph: "heart", size: 1.15, pitch: 6, roll: -4 },
failed: { glyph: "cross", pitch: -10, roll: -6 },
// …fourteen in all
};
// Every frame the face eases toward the next, number by number,
// and a new glyph is blended in over a sixth of a second.
for (const key of FACE_KEYS) {
face[key] += (goal[key] - face[key]) * k;
}
glyphMix = Math.min(1, glyphMix + dt * 6);
Fourteen expressions (EXPRESSIONS): neutral, happy, sad, tired, thinking, working, listening, speaking, sleeping, surprised, trouble, love, excited and failed. Each sets the eyes' lids, size and gaze and how the head is held, and some turn the eyes into a shape. A mood picks one; expression holds one whatever the mood:
<AgentFigure figure={ada} expression="listening" />Listening, its eyes swell as if to a voice; speaking, they bob; working, they scan.
Moves
// Every move is a pose as a function of time, from 0 to 1: a
// crouch before a hop, the hop, and a squash as it lands.
const pose = moveAt("hop", p, shape, energy.amp);
// Energy scales them all: how far each goes, and how long it
// takes.
const ENERGY = {
calm: { amp: 0.65, speed: 1.3 },
lively: { amp: 1, speed: 1 },
bouncy: { amp: 1.35, speed: 0.82 },
};
const duration = ONCE_FOR[move] * ENERGY[energy].speed;
play makes a move now, and again each time its at changes:
const [play, setPlay] = useState<{ motion: Once; at: number }>();
const spin = () => setPlay({ motion: "spin", at: Date.now() });
<AgentFigure figure={ada} play={play} />
<button onClick={spin}>Spin</button>The moves (MOVES): hop, bounce, spin, turn (round to its next face), flip, nod, shake, wave (a wobble), pop (a star for a beat), melt (a blob) and love (a heart). The reactions play the same way (REACTIONS: pet, celebrate, fail, start, doze, wake). Every move is a pose worked out from time alone, a crouch before a hop and a squash as it lands, and energy scales them all.
Looking and walking
On a desktop a figure turns toward the pointer, head and eyes (look, on unless set to false). aim sets where it looks instead, from −1 to 1 each way, right and down: at whoever it's talking to, say; only another figure's moment turns it away. lift, a function of the frame's time giving 0 to 1, hops it off the ground as it walks, its shadow staying put; it's read every frame, so it can follow a walk without a render.
Petting and turning
Clicked, a figure is petted, heart eyes and hearts, and the link or button it's in doesn't open (pet, on unless set to false). With grab, dragging it turns it round, and it springs home when let go. Hovering the link or button it's in, or anything marked data-figure-host, makes it smile with a small bounce, once in 2.5 seconds at most (react).