Skeletons and Animation
A 3D character that walks, waves and swings a sword is one model with a skeleton: bones at its joints, parts that follow the bones, and animations that turn the bones over time. The engine poses the skeleton every tick and draws each part with its bone's matrix, so an animation costs no extra triangles: a character of a few hundred triangles animates freely on the console.
Free polygons
Primitive parts (boxes, cylinders, spheres, lathes) cover most shapes. For anything else,
write the polygons yourself with a mesh part: vertices are the corners, faces lists
each polygon's corners counter-clockwise seen from outside.
{
"veduta": "model/1",
"pivot": "bottom-center",
"parts": [
{ "shape": "mesh", "material": "stone",
"vertices": [[-0.5, 0, 0.5], [0.5, 0, 0.5], [0.5, 0, -0.5], [-0.5, 0, -0.5], [0, 0.8, 0]],
"faces": [[3, 2, 1, 0], [0, 1, 4], [1, 2, 4], [2, 3, 4], [3, 0, 4]] }
]
}
A face may have any number of corners and may be concave (a star, an L); the compiler cuts it into triangles. If a face shows from the wrong side, reverse its corners.
Bones
bones lists the joints, parents first. position is where the joint is in the model as
you built it (the rest pose); each part names the bone it follows.
{
"veduta": "model/1",
"pivot": "bottom-center",
"bones": [
{ "name": "hips", "position": [0, 0.9, 0] },
{ "name": "chest", "parent": "hips", "position": [0, 1.1, 0] },
{ "name": "head", "parent": "chest", "position": [0, 1.5, 0] },
{ "name": "arm_l", "parent": "chest", "position": [0.28, 1.42, 0] },
{ "name": "arm_r", "parent": "chest", "position": [-0.28, 1.42, 0] },
{ "name": "leg_l", "parent": "hips", "position": [0.12, 0.9, 0] },
{ "name": "leg_r", "parent": "hips", "position": [-0.12, 0.9, 0] }
],
"parts": [
{ "shape": "box", "size": [0.44, 0.25, 0.24], "position": [0, 0.98, 0], "material": "pants", "bone": "hips" },
{ "shape": "box", "size": [0.5, 0.4, 0.25], "position": [0, 1.28, 0], "material": "shirt", "bone": "chest" },
{ "shape": "sphere", "radius": 0.17, "segments": 10, "rings": 6, "position": [0, 1.68, 0], "material": "skin", "bone": "head" },
{ "shape": "box", "size": [0.12, 0.55, 0.12], "position": [0.37, 1.18, 0], "material": "skin", "bone": "arm_l" },
{ "shape": "mirror", "axis": "x", "of": 3, "bone": "arm_r" },
{ "shape": "box", "size": [0.15, 0.85, 0.15], "position": [0.12, 0.45, 0], "material": "pants", "bone": "leg_l" },
{ "shape": "mirror", "axis": "x", "of": 5, "bone": "leg_r" }
],
"animations": {
"idle": { "loop": true, "length": 2, "tracks": {
"chest": [ { "t": 0, "rotation_deg": [0, 0, 0], "ease": "smooth" }, { "t": 1, "rotation_deg": [3, 0, 0], "ease": "smooth" } ] } },
"walk": { "loop": true, "length": 0.8, "tracks": {
"leg_l": [ { "t": 0, "rotation_deg": [30, 0, 0] }, { "t": 0.4, "rotation_deg": [-30, 0, 0] } ],
"leg_r": [ { "t": 0, "rotation_deg": [-30, 0, 0] }, { "t": 0.4, "rotation_deg": [30, 0, 0] } ],
"arm_l": [ { "t": 0, "rotation_deg": [-25, 0, 0] }, { "t": 0.4, "rotation_deg": [25, 0, 0] } ],
"arm_r": [ { "t": 0, "rotation_deg": [25, 0, 0] }, { "t": 0.4, "rotation_deg": [-25, 0, 0] } ] } },
"wave": { "next": "idle", "tracks": {
"arm_l": [ { "t": 0, "rotation_deg": [0, 0, 0] }, { "t": 0.3, "rotation_deg": [0, 0, 150], "ease": "smooth" },
{ "t": 0.9, "rotation_deg": [0, 0, 150] }, { "t": 1.2, "rotation_deg": [0, 0, 0] } ] } }
}
}
Tips:
- Put a joint where the limb turns: the shoulder at the top of the arm, the hip at the top of the leg. Build limbs hanging straight down.
- Build the left side, then
mirrorit with"bone"naming the right side's bone. - A child follows its parent: turn
chestand the arms and head go with it. - 6–12 bones are plenty for a character 20–60 pixels tall on the panel.
Animations
Each animation has tracks: per bone, a list of keys by time. A key sets rotation_deg
(about the joint), position (an offset of the joint) or scale, or several; the engine
moves between keys (ease: linear, step or smooth).
loop: truerepeats everylengthseconds, going from the last key back to the first: the walk above needs only two keys per leg.- Without
loop, the animation holds its last pose, or goes on withnext(the wave goes back to idle).
Playing them
local hero = scene.find("hero")
hero.anim_fade = 0.15 -- changing anim blends over 0.15 s
function game.update()
if input.pressed("a") then
hero:play("wave", 0.1) -- start over, blending in 0.1 s
elseif input.dpad() ~= 0 then
hero.anim = "walk" -- the same name again changes nothing
elseif hero.anim ~= "wave" or hero.anim_done then
hero.anim = "idle"
end
end
A scene can start one too: "anim": "idle" on the entity.
Aiming bones and holding things
e:pose(bone, rx, ry, rz) turns a bone on top of its animation until you change it: a head
that follows the player, an arm that aims. e:pose(bone) takes it back.
local x, _, z = player:position()
local hx, _, hz = guard:bone_position("head")
guard:pose("head", 0, math.deg(math.atan(x - hx, z - hz)), 0)
An entity can ride a bone: give it the character as parent and the bone as bone. Its
position is then relative to the joint and it turns with the bone.
{ "name": "sword", "kind": "static", "model": "sword", "parent": "hero", "bone": "arm_r", "position": [0, -0.5, 0] }
In Lua: scene.spawn{model = "sword", parent = hero, bone = "arm_r"}.
Generating characters
mesh.model(name, t) compiles a table written like a model file, bones and animations
included, into a model the game can use at once: random creatures, procedural buildings.
local legs, bones, tracks = {}, {}, {}
for i = 1, 4 do
local x = (i <= 2) and -0.3 or 0.3
local z = (i % 2 == 0) and -0.3 or 0.3
local name = "leg" .. i
bones[i] = {name = name, position = {x, 0.4, z}}
legs[i] = {shape = "box", size = {0.08, 0.4, 0.08}, position = {x, 0.2, z}, bone = name}
local swing = (i == 1 or i == 4) and 30 or -30
tracks[name] = {{t = 0, rotation_deg = {swing, 0, 0}}, {t = 0.25, rotation_deg = {-swing, 0, 0}}}
end
local parts = {{shape = "sphere", radius = 0.4, segments = 8, rings = 5, position = {0, 0.6, 0}}}
for _, leg in ipairs(legs) do parts[#parts + 1] = leg end
mesh.model("game:beetle", {bones = bones, parts = parts,
animations = {walk = {loop = true, length = 0.5, tracks = tracks}}})
scene.spawn{name = "beetle", model = "game:beetle", anim = "walk"}
Checking them
veduta inspect model hero --sheets poses (or the MCP inspect tool with sheets:
["poses"]) draws every animation at four moments, and the report lists the bones, the
parts each one moves and the animations. render --tick N shows the pose the game has at
tick N; scenarios can check anim and anim_done through state.
Collisions use the rest pose (a swinging arm does not change what the entity touches); give
a character a hitbox for its body.