The movement system
A character exported from the Gameable Studio is moved by whole takes of a performer: standing, moving off, walking, running, turning, slowing, stopping. Ten times a second the engine asks which frame of which take best carries on what the body is doing now and goes where the game wants it in the next second, and plays on from there. A start, a stop and a turn are not clips wired to a state machine: they are what the performer did when asked the same thing. The jump from one take to another is carried by an inertial blend (no cross-fade through a mix of two poses), and a foot that is down is held where it landed.
It is on by default. A game does nothing to get it, and one line turns it off. Building a game? Movement for a game's builder is the one page: the calls, the cost, what is not right yet.
What a game gets without asking
- A character with a motion set walks, runs, starts, stops and turns by its takes. The velocity a game already gives it (
character.setState) is the request. - The take carries the walker. A character's body (
physics.moveCharacter) travels over the ground at the take's own speed, so nothing slides, and a start or a stop takes the time the performer took. Up and down stay the game's. - A stop comes to a stand. A character in motion asked to stand stops by the take that comes to a stand soonest among those that carry on from what it is doing (a run on the shared set: about one to two seconds and two metres), not by a long slow-down.
- What carries the game's place is the take's own travel. The body is also drawn back to the game's place, softly; that pull is not handed to the game as part of what carries its place (
carriedVelocity, the layer'scarried), or the place would move away from the body as fast as the body came, and whatever distance had come between the two (a capsule on a slope travels less than it is carried) would never close and would go on carrying the place: a character standing on the set's own standing glided back the way it came at 0.2 to 0.3 m/s for as long as it stood. - A turn on the spot is stepped. Where the set marks its turns on the spot, a standing character whose game turns its facing is given one of them whole, made for the angle asked, quick by default; nothing else turns it meanwhile, so its feet are not turned where they stand.
- A turn is the performer's. A game may turn its own facing as fast as it likes (the third-person controller spins it right round in a third of a second); the body is turned by the take that makes that turn, by stepping, in the time the performer took (a turn right round into a walk: about a second and a half on the shared set; into a run: about two), and by nothing else on top of it. The drawn body is behind the game's facing meanwhile, and what the take leaves is closed softly after.
- The feet are held where they land, on the ground under them (the engine's physics, so steps and slopes are stood on), and every landing is said: an
anim-eventnamedstepon the clipleft-footorright-foot. It is said the moment the foot has come to rest on the floor (in a walk, as the heel stops), or as the foot lifts where a run's short stance never lets it rest: a footstep's sound played on it falls on the landing. - A clip that is not drawn says nothing. A character's own standing, walking and running clips (the walk blend the velocity drives, as before the system) still go round underneath a take. While a take's pose is the one written on the body, their
loop(anim-event, the clip's name, nameloop) is not said; it is said again from the first frame the body is its own stance's. See "What comes back" below. - A steady walk is one take going round. Asked to keep going as it is (in its gait, no turn to speak of), a character is on ONE steady stretch of a take and stays there: from the end of the stretch's loop it goes on from the loop's start, at the same place of its stride (the same foot, the same phase), and its heading is its own, not the take's (a performer never walks dead straight). It meets the speed asked by the LENGTH of its steps first, its steps a minute staying the performer's own within a few hundredths; only at the edge of what the set covers is a stride played slower or faster. See "A steady walk" below.
- At rest it stands as it was captured. A standing character is in its own stance and idle, with its own idle adjustment; the body eases into a take inside the move-off and settles back into its own stance at a stop. Nothing changes a character's rest pose by itself. (
standing: 'set'stands it on the takes' own standing instead; see the options. A character standing on the set used to glide over the ground for as long as it stood; the cause is cured by the numbers, see "What carries the game's place" above, and is to be seen cured in a game before the option is recommended.) - Loading by order. The character stands and idles at once, as before. Its set comes after it stands, is read and its table built on a thread of its own, and the takes take the body over by the inertial blend: the standing and walking parts first, the rest behind them. A
motion-eventof kindreadysays when. - A character with no set, or a page where the shared set's file is missing, moves exactly as before (the walk blend), with one line in the console and no warning.
A steady walk
What a character does while a game keeps asking for the same speed, the same way:
- Which take. Every stretch of a take that holds a gait at a level speed is read at load, with its own pace and cadence and a loop (two frames at which the body is in the same place of its stride). The character goes round the stretch whose stride has to leave what was performed least to go at the speed asked: a long loop before a short one (four seconds or more counts as long), a take the set does not mark as a fallback before one it does. It moves over to that stretch once, at the same place of its stride, and is not searched for again while the request stays as it is; a speed that changes a little is followed on the same take.
- A slow pace is the slow walk that was performed. A set speeds its slow walks up so that they can serve the gait's main pace (a take's
retime: 1.26 plays it 1.26 times faster than it was performed). Asked for a pace near the one such a take was performed at, the character plays it as the performer walked it: at 0.8 m/s the shared set's slow walk at 88 steps a minute with steps of 0.53 m, where a brisk take with its steps cut short gave 111 steps a minute and 0.42 m. So steps a minute and the length of a step fall together with the speed, as a walker's do. The rate stays the performer's own within a few hundredths, counted from the pace the take was performed at (gaits.stretches[i].performed;debug.ownwhile it plays). The pose is read from the take's frames as the file stores them, kept beside the faster copy (data.asRead): the performance itself, within 0.05 degrees, not the faster copy played slowly (which is up to 10 degrees off at a toe for a frame as a foot lands). Only a walk is played so; a run is played as the set stores it. - Where a fallback counts. Where a take that is no fallback reaches the speed at its own rate (the gait's main band: 1.13 to 1.7 m/s on the shared set), everything is as it was: a slow take is a fallback there, however it is played. Below that, a slow take played at its own performed pace is no fallback: it is in the band it was performed in. Which of two slow takes answers is then the one whose steps a minute are nearest a walker's own at that speed (they go as the square root of the speed, read off the gait's main steady take). A take the set marks
performed: { asPerformed: false }is never played at its performed pace. - A slow start and a slow stop too. A take that starts, walks slowly and stops, and that the set plays faster, has its move-off and its slow-down to a stand played the way its own steady walk is played at the pace asked: as performed in the slow band (frame by frame as slow as the set made it fast; a stop out of a walk at 1.0 m/s then slows steadily from 0.97 m/s where the faster copy first sped the body up to 1.02), as the set stores them in the main band. Which take answers a start or a stop is the search's choice as before: on the shared set a slow walk starts by a take of short steps that is not retimed at all, and stops by the short stop when the stop is asked on its foot.
- A start straight ahead goes where it is asked. A body that stands carries nothing on, and every frame a stand may be left by stands too, so a plain match there mostly says whose standing feet are nearest the body's own. For a start straight ahead the place of a take's standing feet counts half: the move-off that leads into the walk asked wins (on the shared set at 1.4 m/s the start-and-stop walk's own start, at 90 % of speed after 1.25 s, where a start of short steps took 1.41 s). A start with a turn in it is the search's choice as before.
- A curve is not a walk ahead. A stretch whose take turns the body more than 6 degrees a second (a walk round a gentle curve: 9; a walk "straight ahead" as performed: 3 to 5) answers a body asked to go straight only when no straighter stretch can be made to reach the speed. Its turning would be left out, but the lean and the crossing steps of the curve would stay on a body going straight.
- Between the slow pace and the main one the take changes once, by the same carried jump as any other, at the same place of the stride. A take that would have to change the way it is played to follow the new speed (from its own pace to the faster one) has no more hold on the body than a stride played off its rate: the main take answers.
- How it meets the speed. By the length of its steps first: up to 18 % shorter or 4 % longer than performed (longer than that bends the front knee as the heel lands; the numbers are in
gait.ts). Its rate stays within 4 % of the take's own while the steps' length reaches. Only beyond that is a stride played slower or faster, never more than a seventh either way. A run has no such room (its foot is down for a fifth of a second and is not held where it lands, so a step made shorter or longer is a foot that slides): its steps stay as performed, and it meets a speed by its rate alone. - What a set covers. Per gait, from its slowest steady take AS PERFORMED with the shortest steps at the slowest rate to its fastest with the longest steps at the fastest rate (the shared set's walk: 0.47 to 1.75 m/s on a body of Lars's size). The takes of a set's
getupspart never count: a body crawling to its feet is no pace of a walk. Inside that the rate is at its limit only at the very edge; outside it the game is told (motion-event, kindlimited) and the character goes at the nearest speed covered. - A sprint is a gait of its own, and only a set can say so. To the body's reading a sprint is a run (the hips sink, both feet fly), and a speed must not decide it: a run played fast is no sprint. So a set marks it: the takes of its part
sprintare the sprint gait in every frame that does not stand, with a band of their own beside the walk's and the run's, their own start from a stand, their own steady stretches to go round and their own stop. The part may hold more than one pace (the shared set's will hold a fast run of about 5.5 m/s on long steps and a flat-out sprint of about 6.6): a speed asked is answered with the take performed nearest it, at a rate near 1, a start from a stand with the one whose drive-off gets to that pace, and the drive-off is played until the body is nearly at speed. A sprinting body asked to stand is given the sprint's own stop. A speed over the fastest sprint, or in a gap between the run's top and the sprint's slowest, islimited(the gait named issprintorrun). - Beside a sprint part the run's band ends where a run take really goes. On a set without the part a run's stride is played up to a seventh faster, as ever (the shared set's run reaches 5.06 m/s that way, at 226 steps a minute on 1.34 m steps: too many short steps for that speed). On a set WITH the part the run's top is the fastest a run take goes played at most a twentieth faster, a fallback take not faster at all (the shared set's: about 4.6 m/s); what lies above is the sprint part's, whose own strides are never played faster than that either. A start's pace sets no edge of the band: only a stretch whose loop holds two strides or more sets that top, and a take of the
runpart that states aturnStartblock (a start that turns the body round: itsturnDegandfrom) is left out of the run's level speeds. - The part changes nothing below that. A walk, a run under the run's top, every start, stop and turn are exactly what they are on the same set without the part, to the last number: a run's stride there is played as on any set, and the sprint's frames are left out of the set's measure of how much paths and poses vary (which every search weighs a frame by), so no search among walking and running frames is weighed differently because a sprint was added.
- What a game asking 5.2 m/s gets. Without a sprint part: the run at its top, 5.06 m/s, and
limited, namerun. With the part: the sprint gait's fast run at 5.2 m/s, played 7 % slower than performed (about 190 steps a minute on 1.6 m steps), nothing limited. Asked 4.5: the run, on either set. Asked 6.6: the sprint, at 0.97 of its own rate. Asked 7.5: the sprint at its top, 7.1 m/s,limited, namesprint. (Lars, on the with-sprint set as built on 5 October 2026.) - What a set should hold for it. One long steady stretch per pace a game will ask for (a walk at about 1.0, 1.2 and 1.45 m/s covers 0.8 to 1.55 m/s with every stride at the performer's own rate), each ten seconds straight ahead, and
fallback: trueon every take that is slow, retimed or off its gait's pace. A slow walk is best given as it was performed with itsretimestated ({ factor, applied: false }): the engine then has both, the performed pace for the slow band and the faster one for the main band.
Turning it off
One switch for a game, in src/game.ts:
ts
export default defineGame({
features: { characters: { motion: false } },
});A page that makes its own bridge says the same there (createCharacterBridge({ motion: false })). One character alone, in src/assets.json:
json
{ "id": "char.guard", "type": "character", "src": "guard/character.json",
"rig": { "backend": "aosrig-splat", "motion": false } }With the system off, every character walks by the clips' blend, as before, and nothing of this page applies. Games built before the system (the bundles under docs/public/games/) are not touched until they are rebuilt; Tala Me This and Millennial say motion: false in their own pages.
Which set a character moves by
| A character | Moves by |
|---|---|
whose package carries its own set (motion in its character.json, or rig.motion in the manifest naming a file beside it) | its own takes, laid over the shared set: the shared set gives every part (module) its own does not carry |
| with the shared skeleton and no set of its own | the engine's shared set, Everyday |
| not on the shared skeleton (the sample character, a glTF body) | the walk blend, as before |
- The shared set is one file,
packages/animation/assets/everyday-v1.motion.bin(EVERYDAY_MOTION_URLingameable/animation). A newer set drops in by replacing that file: no code changes. A page can point somewhere else (motion: { shared: '<address>' }) or have none (shared: false). - Parts (modules). A take belongs to a part of its set:
idle,walk,run,blend(walk to run and back) oractions. By default a character loads every part but the actions, the standing and walking parts first.motion: { modules: ['idle', 'walk'] }loads only those (a presenter, a character in a room): a smaller table and a quicker search. - By name.
'own'and'shared'are always names; a page can add more (motion: { sets: { patrol: '<address>' } }) and a game picks one per character withcharacter.motion(entity, { set: 'patrol' }).
The calls a game makes
Everything goes out with the frame's other commands: nothing here is a call across the boundary per character.
ts
import { character } from 'gameable';
// The request, as before: a velocity. Its direction is where to go.
character.setState(hero, 'run', vx, 0, vz, grounded);
// How one character is moved: off, another set, whether the take carries its
// walker, and the two dials. What is left out stays as it is.
character.motion(hero, { responsiveness: 0.9 });
character.motion(guard, { on: false });
// Send a character to a spot: it turns, walks there and arrives facing the way asked.
character.walkTo(guide, { x: 2, y: 0, z: -3 }, { facing: Math.PI, pace: 1.2 });
character.walkTo(guard, post, { arrive: 'quick' }); // the shortest stops and turns the set has
character.walkTo(rider, bikeSide, { pace: 3.4 }); // a pace above the walk's: it runs there
character.face(guide, Math.PI / 2); // a turn on the spot, by the set's own turns
character.stopWalk(guide);
// An action of its set, at an object when the action needs one (see "Actions").
character.do(hero, 'hurdle', { id: bar, position: barAt, yaw: 0, height: 0.6, width: 1.2, depth: 0.05 });
character.cancelAction(hero);- The two dials, each 0 to 1, default 0.6.
responsiveness: how hard a character follows what is asked (higher: sharper takes are chosen sooner).naturalness: how far the drawn character may leave its place to move as the take does (0 glues it to the game's place and the feet slide to keep up). carry(default true): whether the take carries the walker.character.motion(entity, { carry: false })moves the body exactly as the game asks.- A walk to a spot is walked on the host: a few steps are one take played through at the performer's own rate (a take that never holds a steady walk: up to about 3 m on the shared set), any longer way a walk with the takes' own start and stop whose steady part is the very walk a velocity gets (the same take, rate and steps: never one take's strides stretched over the way), a turn on the spot the set's own turn. The entity (and its physics body, when it has one) is put where the walk has the character every frame, on the level it stands on. The game's own record of the place follows when the walk ends.
- Its stop is one stop of the set, chosen where it has to begin. Over the last metres the walk asks every frame which stop the body would be carried into best, and takes it, at that frame, the moment its own travel is what is left to the spot (measured along the way the body travels); the stop is then played through and its walking steps made up to a quarter shorter or longer so it ends on the spot. It does not pass the spot and come back: a stop grown too long for what is left is not taken (the best of those that still land there is), the one more small step at the end is never a turn round, and when no stop of the set lands there the one that passes it least is taken and
limitedsays so. - One take played through is in the gait asked: a walk's pace never plays a take that jogs or runs, however well its length fits.
- A pace above the walk's is a run, when the set has one and the spot is far enough: the layer's own move-off into a run, its steady run at the performer's own rate (a run's steps are never made longer or shorter), and one of the set's stops, chosen as a walk's is; only the walking steps the stop ends with are fitted. A run needs room for its move-off and its stop, both read from the set (the metres a move-off takes to nine tenths of its pace; the least any slow-down of the set still travels from that speed): the stop may begin the moment the move-off is up to speed. Nearer than that it runs more slowly; too near to run at all, it walks at the walk's own fastest steady walk;
limitedsays the speed given. A pace between the two bands goes in the nearer; a band a set brings above the run is read the same way. Witharrive: 'quick'a run takes, of the stops that end on the spot, the one that has the body still soonest. - A spot off its facing is turned to as the set turns. A standing body sent to a spot more than 35 degrees off its facing either moves off by a take of the set that turns the body that way AS IT STARTS (read from the set by what each move-off does; the take is entered and held until its turn is made, its turn made up to a quarter smaller or larger), or turns on the spot first, by the turn the set marks for the angle, and goes when the body is within 20 degrees of the way. Of the two, the one that has it travelling sooner by the set's own times; a spot under 2 m away is always turned to first. A turn on the spot is the layer's own marked turn: the game's facing is the way asked at once, and the layer plays the turn (whole; before a run, or for a quick arrival, from where its turning begins, without the performer's wind-up: the layer's
turnEntry).character.faceand a facing asked at arrival turn the same way. Before the few steps to a spot under 2 m away the body goes only when it is within 8 degrees of the way (those steps go where the body faces), and both turns of such a move are entered where their turning begins. "Where its turning begins" is a tenth of a second before the take first turns the body faster than 60 degrees a second OR has turned it 5 degrees, whichever comes first (an unhurried turn never turns it that fast; read by the speed alone it was entered at its end, nothing turned). And a turn toward a spot is never waited for when it is not coming: one the layer has played that left the body off the way, or one it has not begun within a second, and the body goes from the way it faces, the walk's steering doing the rest. - A turn to a facing asked holds the place it stands on. A marked turn travels as its performer did (on the shared set 3 to 28 cm, to one side or the other), and the game's place would go with it, off the spot. So through
character.faceand the facing asked at arrival the take's travel is kept, frame by frame, only while it carries the body toward the spot (within about 45 degrees of the way to it), and taken out while it carries it away: the body turns about where it stands, the foot it stands on kept in place by the engine's hold of the feet. No more is taken out under one planted foot than that hold reaches (0.10 of its 0.12 m, counted from when the foot came down): past it the foot would be dragged, and the rest of the travel is the take's. (The planner'sholdPlace; the layer's scale of a take's travel,direction.stride, is its dial meanwhile and is given back at arrival.) A facing asked that needs no turn (under 25 degrees) is taken up as the body stands; a body sent on before its turn is made leaves the turn at once, the dial given back. - The pace named
normalis the walk's main take's own pace (the longest steady stretch the set holds at a walk; 1.43 m/s on the shared set), so a walk with no pace named is played as performed (measured: 114 to 120 steps a minute on 0.63 to 0.73 m). It was the middle between the band's slowest and fastest steady takes, which a slow walk in the set pulled down to 1.1 m/s: the main take on steps a tenth shorter. - A way of 2 m or more is walked by the walk itself (a move-off, the walk's own strides, a stop:
WALK_FROM_M); one take of a few steps, played at its own slow rate, is for what is shorter. A move-off, two strides and a stop need about 3.8 m on the shared set (0.6 m, 2.6 m, 0.6 m); from 2 m there is room for a move-off, a stop and about half a stride between. - A stop is aimed for the stand it ends in. Where the body has to stop for its FEET to stand on the spot (the take's stand has the body a little ahead of its feet) is read before the stop is taken, from the stop the layer would take now, turned as that stop will have turned the body; and a stop that ends to one side of the line the body walks (a stop out of a curve) has the last strides before it walked up to 15 degrees to the other side.
- A stop is its own gait's, by what the set says each frame serves: a walk to a spot is stopped by a walk's stop (never by the slow last steps of a jog's or a run's stop, which read as walking), a run by a run's, a sprint by a sprint's.
- A go-to sprints on a set with a sprint part, when the pace asked is the sprint's and there is room: the room is the metres the drive-off takes to nine tenths of the pace (read to the frame it gets there) and the least a stop of that gait still travels from that speed. Nearer than that it goes in a slower band, and
limitedsays the speed given. It is stopped by the fast run's own stop or the sprint's skid, whichever ends on the spot from the stride it is in. - Sent somewhere else on the way: in its stop, it finishes the stop and goes from there; walking, to a near spot well off its way, it stops first; otherwise it goes on from the stride it is in.
- The feel pass (
motion: { feel: true }on the bridge,feelofcreateDirectedWalk; off by default, and off a go-to is to the last bit as it was) gives weight to what the go-to itself drives, never to a performed take. Four parts, each on with it unless put off (feel: { steer, pace, settle, facing }):steer: a walk's turn rate toward the spot comes on and off no faster than 8 rad/s2, the head and then the chest lead the hips toward the spot on springs (half-life 0.12 s and 0.26 s), and a run leans into its curve and comes off the lean past upright by a sixth;pace: a new pace asked on the way is come to eased (from a stand the pace is asked at once: the layer chooses its performed start by it);settle: after arrival the body comes back over its feet on a spring, the chest after the hips;facing: at a turn on the spot the head goes first to where it will face, and a small facing is taken up from rest. The body's part iscreateWalkFeelLayer, givenwalk.body.
What comes back
In ctx.events:
| Event | Says |
|---|---|
anim-event, name step, clip left-foot / right-foot | a foot came down and has come to rest on the floor, or lifts again before it could (time: seconds since the last one) |
motion-event, kind ready, name the set's | the takes have the body from here |
motion-event, kind started, name stop | a walk to a spot begins its stop (once per walk): amount is the metres still to the spot, point where it is |
motion-event, kind done, name stop | it no longer travels (under 10 cm a second for a tenth of a second): amount is the metres it is from the spot. A stop's last settling step and the body settling over its feet come after. (These two ride on kinds the contract already has; a later revision of the contract may give them a kind of their own.) |
motion-event, kind arrived, name walk | a walk to a spot ended, settled: point is where it stands, facing which way, amount how far from the spot asked (metres) |
motion-event, kind arrived, name face | a turn on the spot ended: facing is the way it faces, amount how far from the facing asked (radians) |
motion-event, kind limited, name the gait (walk, run, or sprint on a set with a sprint part) | the speed asked is one the set does not cover: the character moves at amount metres a second instead (never a faster gait played slowly); said once, when it becomes so. Of a walk to a spot: said once per walk, when the speed it is given is not the pace asked (a pace outside what the set covers, or a run asked to a spot too near to run to), or when no stop of the set ended on the spot |
motion-event, kind started / contact / release / landed / done, name the action's | an action's moments: limb (0 left hand, 1 right hand, 2 left foot, 3 right foot), object (the id the game gave), point and velocity (a release gives the thing let go this). For landed, point is where the body landed, at the height of the floor it landed on |
motion-event, kind failed | why, in reason, a word a game can branch on. Refused at once: not-available, unknown-action, needs-object, too-high, too-low, busy, seated (it sits on or rides something). Given up on the way: too-close, not-lined-up, wrong-foot, hit, no-floor, cancelled. And clipped: a limb met the object, and the action goes on |
Something asked that cannot be done is always said as failed, never silence.
The clips' own loop. The walk blend's clips (a character's standing, walking and running clips, mixed from the speed asked) say loop each time they come round, as before the system, but only on a frame where the mixer's pose is the one drawn: its own stance at rest, the settling back into it after a take, and the moment a standing body waits for a marked turn. On every frame a take's pose is written on the body (from the first frame of the blend in), the clip underneath is unseen and its loop is not said: what the body does then is said by the feet's step. The rest is as it was:
- a clip the game names itself (
character.setClipWeights) saysloopandendedwhether a take has the body or not, while it plays and while it fades out; - a character with
motion: false(the game's, or its own), one that is not on the shared skeleton, and one whose takes are shut (seated or riding, a performed get-up, a fall) say every event they said before; - the
loopof a walk blend's clip is told once the takes have run that frame, still inside the same update: in one frame's list it can come after an event it used to come before. Nothing drawn changes.
A character that sits or rides
A character sat on something (a made world's thing with rider spots: things.getOn; on a page, characters.seat) is its seat's, and the movement system leaves it alone until it gets off:
- its takes write nothing and its feet are not held: the seated pose is exactly what it is with the system off;
- no take carries its walker;
- a walk to a spot under way when it sits down is given up, as
character.stopWalkgives one up (arrivedsays where it stopped); an action asked or playing is taken back (failed); a performed get-up under way lets go at once, and its entity is brought nowhere; character.walkTo,character.faceandcharacter.doasked while it sits are answeredfailedwith the reasonseated;- when it gets off, its takes take the body back by their own inertial blend, from the pose it stands in, as a character that never sat moves off.
A performed move from the seat (a kick, a punch, a throw, a reaction to a hit) can be played on one limb over the riding pose, on a page that names a riding set (motion: { riding: '<address>' }, then characters.seatedMove(entity, 'kick', { side, target })): off by default, a set of its own beside the one the character moves by (its part riding, each take with its own seated block), never laid into that set. The acting limb leaves its grip or rest and is eased back onto it; the trunk and head take a share of the take's turn; the other limbs stay on their spots and the hips on the seat; a target bends the acting wrist or ankle to it around the take's strike frame. Its moments are motion records named by the move (started, contact, release, done, failed). In gameable/animation: createSeatedMoves, the seated pose's overlay. The whole of it for a game's builder: Movement for a game.
The options, in one table
| Where | Field | Default | Does |
|---|---|---|---|
features.characters in src/game.ts | motion: false | on | the one switch for a game |
motion: { carry, responsiveness, naturalness, standing, footPlant } | true, 0.6, 0.6, 'own', true | how every character is moved | |
createCharacterBridge (a page) | motion: false or the same fields, and: | ||
shared | the engine's Everyday set | the shared set's address, or false for none | |
set, sets | none | one set for every character; sets by name | |
modules | every part but actions | the parts to load | |
worker | the engine's own thread | false reads sets on the page's own thread | |
turn | 'quick' | the pace of a turn on the spot where the set marks its turns by pace: 'quick' (a game that turns a standing character's facing itself) or 'easy' (unhurried) | |
getUps | off | where the get-ups are (true: the shared set; an address; { set, style }): a fallen character gets up by a take (see "Getting up") | |
riding | off | the address of a set whose part riding holds seated takes: a rider plays a performed move over the riding pose, and the bridge has seatedMove (see "A character that sits or rides") | |
rig of a character in src/assets.json | motion: false | on | this character by the clips, as before |
motion: "<file>" | the package's own | its own set, beside its character.json | |
character.motion(entity, …) in the game | on, set, carry, responsiveness, naturalness | as the game's | one character's own |
Actions
A set can state actions: ranges of its takes (a hurdle, a vault, a jump) a character is asked to do, at an object when the action needs one. They are off by default in version 1 (motion: { actions: true } on the bridge turns them on): the default is standing, walking, running, starts, stops, turns and the foot hold. With them off nothing of a character's movement changes, and a request is answered failed, not-available.
A game turns them on in src/game.ts, where it would turn the system off:
ts
export default defineGame({
features: { characters: { motion: { actions: true } } },
});Its page can say the same where it makes the bridge, in src/main.ts:
ts
const FEATURES = clientFeatures({ characters: { motion: { actions: true } } });(a page that makes its own bridge says createCharacterBridge({ motion: { actions: true } })).
- They come with the set. The set is then read with its
actionspart, behind the standing and walking parts as before, and a character's actions arrive with its takes. Asked before that, a request isfailed,not-available. - Asking.
character.do(entity, name, object)asks andcharacter.cancelAction(entity)takes it back (not between the gathering and the landing). The way in is chosen by the feet and the distance, the take is fitted to the real object, and the time in the air is gravity's. Every moment is amotion-event(the table above); what cannot be done isfailedwith the reason. - Only when asked. A take of the
actionspart is never one the search lands on: a character does not wander into a hurdle. - The physics. A limb that meets the object (
failed,clipped) is a small hit on the character's physics body: it flinches and stays in its action. A landing with no floor under it (no-floor) is a fall. - An action that ends on another floor. Up and down stay the game's.
landedsays inpointwhere the body landed, at the height of the floor it landed on (the physics' own reading under the foot that is down, when the engine has the physics module; else the height of that foot's sole): onlanded, a game moves its own walker to that height. - Not with a walk to a spot.
character.dowhile a walk to a spot or a turn on the spot is under way isfailed,busy; a walk or a turn asked while an action is asked or playing isfailed. - Where each action stands is in
ACTION_LIST(gameable/animation): today only the hurdle and the jump are seen working as meant.
Getting up
A character that has fallen (its physics body lies on the floor: see Characters have physics) can get up as a performer did, by a take of a body getting up, instead of having its lying pose blended back to standing. It is off by default: character.recover then does what it always did.
One option turns it on, where a page makes its bridge: where the get-ups are.
ts
// a game, in src/main.ts
const FEATURES = clientFeatures({ characters: { motion: { getUps: '/sets/getups.motion.bin' } } });
// a page that makes its own bridge
createCharacterBridge({ motion: { getUps: '/sets/getups.motion.bin' } });getUps: true reads them from the shared set (shared); getUps: { set, style: 'quick' } asks for the spring straight back onto the feet where the set has one for how the body lies (default 'ordinary').
- Asked as before.
character.recover(entity)on a character that is down. Nothing else changes in a game. - How the body lies decides. The body is read as it lies: on its back, its front, its left or its right side, and which way its head points. The take whose lying pose is nearest is chosen (the pose distance: what is left between the body's points and the take's, in metres, after the take is turned about the vertical and moved to lie over the body). A body no take lies like (a side the set has no take for; further than half a metre from every take) is blended back as before, said once in the console.
- Eased in, never snapped. The fallen pose is carried into the take over a blend as long as its furthest point has to travel (0.9 metres a second) and its most turned joint has to turn (3 radians a second): never under 0.3 s, never over 1.2 s. The take's own clock starts slowly under the blend.
- Hands and feet stay where they push. A hand or a foot the take has planted is held to the floor under it (the physics' floor); nothing is let under the floor.
- It gets up where it lies. The entity is brought, eased, to where the character will stand, the way a walk to a spot moves it (
stepWalkson the host: the engine's adapter does it), and is there when the character stands. Then the body is handed back: to its own stance, or straight to its takes when the game already asks it to move. - What comes back.
motion-eventstartedandarrived, both namedget-up(arrivedcarriespointandfacing: where the character stands now, for the game's own record;amountis the pose distance at the hand-over), and the physics body's ownanim-eventrecoveredwhen it stands. A character floored again while it rises lets go at once and falls from the pose it is in. - Only with the movement system. With
motion: false(the game's, or one character's) nothing of this applies.
The set: a take of the part (module) getups, marked "getup": { "lying": <seconds>, "standing": <seconds> } (the last moment it lies on the floor, the moment it stands), with "style": "quick" for a spring and "air": [<from>, <to>] for a stretch it is off the floor. The engine puts the floor under the body itself (a read of a body on the floor is not trusted for its height) and finds the planted hands and feet from their height and stillness. The getups part is never part of what a character moves by: a set that carries it is read without it unless it is asked for by name.
A body on a side is raised by a take that lies on that side: the engine does not mirror a take, so a set carries one for the right side and one for the left (the shared set does from revision 6b on). A style asked that no take of the set fits falls back to an ordinary get-up for how the body lies.
Not built: a roll from a side onto the back before an ordinary get-up; a get-up on a slope or a step (the floor is taken level under the body); a word for it in a game's own features (the page turns it on). Two faults measured on revision 6c have cures, on by default: a hand going 4 to 10 cm under the floor for a moment at the push-off of the get-up from the front (handsOverFloor), and a foot that is down sliding 20 to 45 cm while the body is handed back into a walk or a run (feetStay); getUps: { handsOverFloor: false } or { feetStay: false } in motion draws them as before. Known fault: a body that comes to rest propped up on an arm (its trunk more than 40 degrees up) is not read as lying and rises by the blend as before. getup.html of examples/character-showcase shows it (?fall=back|front|side|left, &mode=today for the blend as before, &set=shared&takes=shared for the shared set's own, &then=walk|run for what it does after).
A character's own pose adjustment
A person may straighten a character by hand in the studio (a chest a little more upright, a head level again) and say it holds under every animation. The studio bakes that into the character's package: its skeleton at rest and its own clips carry it. A shared set's takes do not: they are a performer's pose, written onto the joints. So the package also states the adjustment, and the engine lays it on the takes:
json
"poseAdjust": {
"version": 1,
"space": "parent",
"adjust": { "Chest": [-0.0262, 0, 0, 0.9997] },
"move": { "Hips": [0, 0.01, 0] }
}- Where:
poseAdjustin the package'scharacter.json, written by the studio's export only when the adjustment is on and set to hold under every animation.adjustis a turn per joint (quaternion x y z w, in the frame of the joint's parent, laid before the joint's own rotation);moveis metres in the parent's frame. - What the engine does: every pose it reads from a take (a walk, a turn, an action, a get-up) is turned by it before the blend, before the feet are held and before the look and the life. The character's own stand and clips are never touched, so it is never on twice; a blend between an own clip and a take runs between two poses that both carry it, so the hand-over has no step.
- What it does not do: of
moveonly the hips' entry is used (a take places no other joint); a get-up places the hips in the world, so the hips' own turn is not laid while it rises. - Absent: nothing changes: the takes are written as stored. A record the engine cannot read, or a joint in it the body lacks, is one line in the console and the character moves as it would without (the joints it does have are laid).
The files
motion.json(soma-motion, version 1) is what the studio writes: a list of takes on the shared skeleton, each with its joints' rotations and the hips' path per frame, the feet's contacts, its module, its ranges and its measures. The engine reads it as it is.*.motion.bin(soma-motion-bin, version 1) is the same set, every take, frame and word of it, in about a sixth of the bytes, read without parsing millions of numbers:shnode packages/animation/tools/pack_motion.mjs <motion.json> [<out.motion.bin>]One header (everything that is words), then a block per take: rotations as "smallest three" in 15 bits a number (10 for the fingers), the root path in steps of a twentieth of a millimetre, contacts as bytes, a joint that never moves stored once, every number its difference from the frame before with low and high bytes apart (so a host that compresses its files sends about half again less). Takes are written in the order of their modules, the walk first. The layout in full is at the top of
packages/animation/src/motion/motionBinary.ts.The engine reads either form wherever a set is named (
parseMotionBytes).A set marks its fallbacks. A take the set calls
fallback: true(slow, retimed or off its gait's pace) answers a request only when no other take does about as well: every frame of it costs a search a little before anything is compared, and a steady walk goes round a fallback's stretch only when no other take reaches the speed within about a tenth of its own rate. The mark never keeps a slow take out of the pace it was performed at (see "A steady walk").A set marks its turns on the spot. A take the set calls
kind: "turn"lists itsturns: each{ from, to, pace }(frames into the take, from the last standing frame before the turn to the first standing frame after it;paceis"easy"or"quick"). Such a stretch is a turn on the spot and nothing else: no search lands inside it, the stand before it is no start of a walk, and a standing character asked to face another way is given it fromfromtoto. A list that states no pace is read as a note.A set marks its sprint by a part. Takes with
"module": "sprint"are the sprint gait (see "A steady walk"); nothing else makes one, and a set without the part is read exactly as it was, to the last number. The part is a part like any other: it is read with the whole set (never with the first, walking table), a character's ownsprintpart replaces the shared one, a host that names its parts namessprintto have it, and in the compact file it is written after the actions and before the get-ups. What the part should hold: per pace a start (stand, drive off, up to speed), a steady stretch of two strides or more that begins and ends at the same place of the stride (it is gone round; a second of take is enough), and a stop; the root's path brought to the feet and marked so (travelOverFeet.applied), since a sprinting foot is down for two or three frames and the engine's own guard cannot measure it; feet that skid stated up, so that no step is said for them. The floor under a sprinting body is read from the lowest sole, as a running body's is.performed.bandand the other words on a take are for people.
What it costs
Measured on the shared set (revision 5: 47 takes, 7.5 minutes; 34 takes and 10,300 frames in a character's table after the actions and the takes the reader refuses), on a desk that other work was loading at the time.
| Text | Compact | |
|---|---|---|
| Stored | 36.4 MB | 6.29 MB (836 KB a minute of motion) |
| As a compressing host sends it | 11.8 MB | 1.95 MB (260 KB a minute) |
In memory the shared set's rotations are 13.2 MB (revision 6c: 37 takes and 11,231 frames after the get-ups). The eight slow walks kept as the file stores them, for playing at their own pace, add 3.0 MB (2,528 frames); the file itself does not grow: it stores each take once, and the faster copy is made when the set is read, as before.
First sight is not delayed. On a 40 Mbit/s line one character stood at 47.5 s with the system off and at 46.8 s with it on; her takes took the body 4.3 s later (2.4 s of that was the file sent uncompressed by a development server; a host that compresses sends a third of it).
Reading the set and building a table run on their own thread: the longest gap between two frames while the set came was 40 ms. On the page's own thread the same work is about 0.3 s of reading and 0.3 s of table on a desk.
Per frame, in the real loop (
crowd.html; a search is made about ten times a second per character and the searches are spread over the frames):Characters Movement, all Feet held, all One search Searches on one frame, at most 1, desk 0.12 ms 0.06 ms 0.07 ms 1 4, desk 0.33 ms 0.16 ms 0.07 ms 2 8, desk 0.63 ms 0.32 ms 0.08 ms 4 1, processor slowed 1.2 ms 0.6 ms 0.36 ms 1 4, processor slowed 5.0 ms 2.6 ms 0.47 ms 3 8, processor slowed 8.2 ms 3.2 ms 0.40 ms 4 "Processor slowed" is the browser's four-times slow-down on that loaded desk, which measured about ten times slower than the desk itself, not four: read those rows as worse than a mid-range phone, not as one. Most of a frame's movement cost is writing the pose (77 joints), not the search.
The search looks at frames through a tree of boxes on the features' own main axes: the same answer as a look at every frame, about seven times sooner on the shared set (0.03 ms against 0.34 ms in
motion-bench.html).Without the new graphics path (a page on the older one) the system is the same: it is bones, not drawing. Four characters there: 0.26 ms a frame for all.
The view for us
Three pages of examples/character-showcase (its dev server; the character packages and sets under public/lessons/ are never committed):
| Page | Shows |
|---|---|
motion-bench.html?set=<file>&skeleton=<skeleton.json> | a set by numbers: reading, table build and one search, whole and per part |
crowd.html?names=Lars,Vesper,… | several characters at once as a new game gets them, with what the system costs per frame on screen; ?backend=webgl for a page without the new graphics path, ?motion=off for the comparison |
lessons.html?mode=move&motion=default | one character through the scripted moves; &view=1 draws the path asked for and the take's, &standing=set the other stance |
In code, per character: bridge.entryOf(entity).motion is the layer (debug says which take plays, its gait, the last search's cost and time, the stride's rate and the length of its steps (stride), the steady stretch it goes round (stretch, an index into table.gaits.stretches) and how many times it has gone round (loops); pace what the speed asked came to on this set) and .motionLoad how its set came (which set, bytes, milliseconds for the download, the reading and the table, and when the takes took the body).
Not in it yet
- Actions are off by default, and of those listed only the hurdle and the jump are seen working as meant; the vault works but reads as a crouch over the box, not yet a clean vault; "throw" and "tumble" are designed only (never run).
- An action that ends on another floor says the floor's height (
landed), and no more: the drawn body is not eased over a walker moved up or down under it, and the actions that need it (a jump onto a box, a climb, a step up) are not built. - A walk to a spot keeps the level the character stands on.
- A walk to a spot never sprints: a pace above the run's is brought to the run's top and
limitedsays so, on a set with a sprint part too. - Between a run and a sprint there is no take that speeds up or slows down: the body moves from one to the other by a carried jump, at the same place of the stride.
- A walk or a run to a spot is measured on a level floor only.
- On a headless server there are no takes: a character there moves as the game asks, and a walk to a spot is ignored with a warning.