Moud

Animation files#

A .anim file is a clip, written as JSON. Most clips are made in the animation editor or imported from Blockbench, and you only read this page to write one by hand or to know what a field does. To play a clip, see Animations.

There are two layouts:

  • Curves per joint, versions 2 and 3. Each joint has its own curves for rotation, position and scale. The Blockbench importer and the animation editor write this layout.
  • Poses at keyframes, version 1, the older layout. It lists whole poses at keyframes.

Version 3 is version 2 with more in it: key types, easings, handle types, curve modifiers, track settings, ping-pong loops, autoplay, a speed, and sound and clip tracks. Every version 2 file is a valid version 3 file and reads the same.

A clip of curves#

json
{
  "version": 2,
  "length": 1,
  "loop": "loop",
  "priority": "movement",
  "euler": "yxz",
  "joints": {
    "rightLeg": {
      "rotation": [
        [0,   [30, 0, 0], "catmullrom"],
        [0.5, [-30, 0, 0], "catmullrom"],
        [1,   [30, 0, 0]]
      ]
    },
    "torso": {
      "position": [
        [0,    [0, 0, 0], "bezier", { "out": [0.1, [0, 0.05, 0]] }],
        [0.25, [0, 0.06, 0], "bezier", { "in": [-0.1, 0], "out": [0.1, 0] }],
        [0.5,  [0, 0, 0], "step"]
      ],
      "weight": 0.8
    }
  },
  "mask": ["rightLeg", "leftLeg", "torso"],
  "blend": "normal",
  "markers": [{ "time": 0.25, "name": "step", "value": "right" }],
  "events": [{ "time": 0.75, "name": "sound", "payload": { "effect": "step", "volume": 0.6 } }]
}

This swings the right leg back and forth once a second and bobs the torso up and down at 0.8 of its weight. Only joints is required.

Clip fields#

Playback:

Field
version 3, or 2 for files from before. A file with joints is read as curves per joint either way
length seconds; the last key or marker when left out
loop loop starts over, once stops at the end, hold stays on the last frame until stopped, pingpong runs back from the end and forth again. once when left out
speed how fast the clip plays, times the track's own speed. 1 when left out
autoplay true starts the clip on its model or body when the place starts, see autoplay
priority core, idle, movement, action, action2, action3, action4, or a number. core when left out. See priority and weight
mask a list of joint names, or one string of names split by commas or spaces. Only those joints are moved
blend normal, or additive
space body, or view for a clip that plays on the view model. A body's animator refuses a view clip

Joints:

Field
joints joint name to its channels, see joints. Required
euler the order the three rotation angles are put together in, like yxz (the default, the same as cframe.angles) or zyx (Blockbench's)
skeleton the hierarchy the clip was made on, see playing an imported clip on a body
retarget joint name in the clip to the body joint it plays on, overriding the guess from its name. An empty name keeps that joint off a body

Things that happen at a time:

Field
markers each with a time, a name and an optional string value. See markers and events
events each with a time, a name, a payload, which is any JSON, and an on: server, client or both, where it fires. both when left out
sounds each with a time, a sound id and an optional volume and pitch. The clients play it where the rig stands when the playhead passes it
plays each with a time, a clip, the res:// path of another .anim, and a fade in seconds. When the server's playhead passes it, the clip starts on the same animator, or starts again if it already plays

For the editor only:

Field
rig the rig the clip was made for: player, view, the res:// path of the .bbmodel a model was made from, or a model's name
fps, viewModel written by the editor for itself, as is an event's preview. The engine reads past them

Joints#

A joint has up to three channels, a weight, and the settings of its track:

Field Values
rotation [x, y, z] angles in degrees, put together in the file's euler order, or [x, y, z, w], a quaternion
position [x, y, z], the offset from the joint's rest, in blocks
scale [x, y, z], 1 is the joint's own size. On a body joint it is the joint's scale
weight 0 to 1, how much of the joint the clip takes; 1 when left out
muted true and the track does not play. Its keys stay in the file
locked true and the editor will not change its keys. The engine reads past it
interpolation the interpolation the editor gives new keys on this track, linear, catmullrom, bezier or step. The engine reads past it
modifiers curve modifiers per channel, { "rotation": [ ... ] }, see curve modifiers

Channels a joint does not have are left alone. A clip with only rotations turns the joints and does not pull their positions back to the rest.

Keys#

A channel is a list of keys, and a key is [time, value, interpolation, extra]. The last two are optional.

json
"rightArm": {
  "rotation": [
    [0,   [24, 0, 0], "bezier", { "type": "extreme", "left": "autoClamped", "right": "autoClamped", "out": [0.1, [-4, 0, 0]] }],
    [0.3, [0, 0, 0],  "back",   { "type": "breakdown", "ease": "out" }],
    [0.6, [-24, 0, 0]]
  ],
  "interpolation": "bezier",
  "modifiers": { "rotation": [{ "type": "cycles", "before": "repeat", "after": "offset" }] }
}

Interpolation#

The interpolation says how the value travels from this key to the next one:

Interpolation
linear a straight line. The default
catmullrom a smooth curve through every key, bent by the keys on either side
bezier a curve shaped by this key's out handle and the next key's in handle
step holds this key's value, then jumps to the next key when its time comes
sine, quad, cubic, quart, quint, expo, circ, back, bounce, elastic an easing from this key to the next, the same curves as tweens. ease in the extra picks in, out or inOut; left out, back, bounce and elastic ease out and the rest ease in

Angles are interpolated one axis at a time, as Blockbench does, so a key can spin a joint more than half a turn. Quaternion keys slerp, and treat catmullrom and bezier as linear.

Two keys at the same time make a jump: the value arrives at the first and leaves from the second.

The extra#

The extra is an object, and every field of it is optional:

Field
in, out the bezier handles, see bezier handles
ease in, out or inOut for an easing key
type keyframe (left out), breakdown, extreme, jitter or hold. They only mark what a key is for; the engine plays them all the same
left, right the type of the in and out handle: autoClamped, auto, vector, aligned or free (left out)

The editor works out autoClamped, auto and vector handles from the keys around them and writes the result in in and out. The engine only ever reads the handles.

Bezier handles#

Handles are { "in": [dt, dv], "out": [dt, dv] }:

  • dt is the handle's time from its key, in seconds, negative for in.
  • dv is how far the handle is from the key's value: one number for all three axes, or [dx, dy, dz].

The two handles of a segment share one time line, so the three axes ease in step, and a handle only moves its own axis's value. A missing handle is flat and a third of the segment long, which gives an ease in and out.

Curve modifiers#

A curve modifier changes a whole channel without touching its keys. Modifiers run in the order they are listed, the ones that move time first. They work on angle and offset channels, not on quaternion keys.

Each has two optional fields:

  • axis: 0, 1 or 2 for x, y or z. Every axis when left out.
  • enabled: true when left out.
type Fields
cycles before, after: none, repeat (the default), offset or mirror repeats the keyed range before the first key and after the last. offset climbs by the change over the range each cycle, mirror runs every second cycle backwards
noise scale (seconds, 1), strength (1), phase (0), offset (0) adds a smooth wobble, the same every time it plays, half the strength either way
stepped step (seconds, 0.1), offset (0) holds the curve on a grid of times, for a choppy look
limits min, max clamps the value; a side left out is not limited
envelope reference (0), min (-1), max (1), points: [time, min, max] stretches the curve: reference plus min and plus max land on the min and max of the points around the time

Version 1: poses at keyframes#

json
{
  "length": 1,
  "looped": true,
  "priority": 1,
  "keyframes": [
    { "time": 0,   "poses": { "rightLeg": { "angles": [30, 0, 0], "easing": "sine" },
                              "leftLeg":  { "angles": [-30, 0, 0], "easing": "sine" } } },
    { "time": 0.5, "poses": { "rightLeg": { "angles": [-30, 0, 0], "easing": "sine" },
                              "leftLeg":  { "angles": [30, 0, 0], "easing": "sine" } } },
    { "time": 1,   "poses": { "rightLeg": { "angles": [30, 0, 0] },
                              "leftLeg":  { "angles": [-30, 0, 0] } } }
  ],
  "markers": [
    { "time": 0.25, "name": "step", "value": "right" },
    { "time": 0.75, "name": "step", "value": "left" }
  ]
}
Field
length optional seconds; the last keyframe's time when left out
looped optional false when left out
priority optional a number or a level name; 0 when left out
keyframes each with a time and poses, a table of joint name to pose
markers optional each with a time, a name and an optional value
mask, blend, space optional as in version 2

A pose:

Field
position [x, y, z], the offset from the joint's rest
rotation [x, y, z, w], a quaternion
angles [x, y, z] in degrees, used when there is no rotation
easing same names as tweens; linear when left out
direction in, out or inOut; inOut when left out
weight 0 to 1; 1 when left out

Everything a pose leaves out is the rest: no offset and no turn. Markers have their own times and do not belong to a keyframe, so you can put a marker between two keyframes.

The animation editor opens a version 1 clip as it is, and writes it as version 3 when you save it.

The .retarget file#

A .retarget file maps the joints of a clip onto a rig that names or rests its bones differently. A track uses it through retargetId, see Playing a clip on another rig.

json
{
  "version": 1,
  "source": "res://models/knight.bbmodel",
  "target": "res://models/goblin.bbmodel",
  "scale": 0.8,
  "joints": {
    "Body": "spine",
    "LeftArm": { "to": "arm_l", "source": [0, 0, 0], "target": [0, 0, -90] },
    "Cape": ""
  }
}
Field
joints clip joint to the rig joint it plays on. An empty name keeps that joint still
to the rig joint, when the entry is an object
source, target optional the joint's rest rotation on the rig the clip was made on and on this rig, in the rig's own space: [x, y, z] degrees in yxz order, or a quaternion. With both, the joint's turns are carried across as the same turn in the world, so a bone that rests pointing sideways still swings the way the clip's bone did
scale optional positions are multiplied by it, for a rig of another size. 1 when left out
source, target at the top optional the rigs the map was made for. Only the editor reads them

A joint the file does not name plays on the rig joint of the same name, if there is one. Masks take either name. The animation editor writes these files, see Retargeting.