anim
How the vagant moves. No clips and no skeleton: the rig is entities, and a pose is a set of Euler angles written onto them.
vagant.anim
How the vagant moves. No clips and no skeleton: the rig is entities, and a pose is a set of Euler angles written onto them.
local Anim = require("vagant.anim")
Anim.pose(rig, {
t = 3.2, -- seconds since the body was built
gait = 1.05, -- the gait phase, radians; advances with distance covered
speed = 4.2, -- horizontal speed, m/s, smoothed
runT = 0, -- 0 at a walk, 1 at a run
wade = 0, -- 0 dry, 1 up to the knees in a cut
reach = 0, -- 0..1, the reach-and-hold of a relight
hood = 0, -- 0..1, the jasey pulled down
caught = 0, -- 0..1, the moment the fen takes you
oil = 1, -- 0..1, what is left in the lantern
lit = true, -- is the flame alive
})
Anim.rest(rig) -- the same, standing still with a full lantern
One function is handed the whole of what the vagant is doing this frame and writes every joint from it, so a walk, a wade, a reach and a collapse are the same code reading different numbers rather than four state machines that can disagree. Everything the caller passes is a blend, so nothing on the body pops.
Angles are DEGREES, in each joint's own parent frame. A limb hangs down -Y, so
a positive X rotation swings it FORWARD (-Z is forward here) and a positive Z
rotation swings it out to the vagant's right.
Three decisions worth reading
The coat is the walk. The legs are invisible under a coat this long, so the skirt is its own entity hung off the hip: it lags the stride by about a fifth of a cycle and swings wider the faster the vagant goes. That is what tells a walk from a run at the gameplay camera, where no leg is ever seen.
The lantern hangs. Whatever the arm above it did, the bail counter-rotates by the accumulated pitch so the lantern stays upright, and then swings as a pendulum lagging the stride. The carrying arm is held well out from the body — not a comfortable way to carry a lantern, and the reason is the camera: an arm any closer puts the flame behind the coat and the player watches a pool of light with no lantern in it.
The flame gutters. A full lantern burns tall and steady; the last of the oil
burns short, quick and uneven — the warning the player reads before the number
is. Note that localEulerAngles reads back a plain table, so writing a field on
the value it returns changes nothing; the whole property has to be assigned.
Scoped to this part · feeds back into the world's score.