Models and animation
Lesson 12.6
Your own entity types can look like anything: a model you made in Blender, with bones, animations and faces that change shape. This lesson gives a type a model, plays animations, mixes them in layers, reacts to moments in an animation and makes a head follow the player.
The model file
Models are glTF 2.0 files, the common 3D format every modelling tool exports. Put the file in your mod’s assets:
my_mod/
assets/my_mod/models/entity/creature.glb
assets/my_mod/models/entity/creature_clips.json (optional)
A few rules:
- 1 unit in the file is 1 block, and the model faces +Z (towards you in Blender’s front view, after the export turns Y up).
- Each animation in the file is a clip with the animation’s name:
"idle","walk","attack". - Textures are PNG files.
The clips file adds what glTF can’t say: how long a clip is, whether it loops, and events, named moments in a clip:
{ "clips": { "attack": { "loop": false, "events": { "strike": 0.3 } } } }
Give a type its model
Right after registering the type:
cl.on("lifecycle.register", function()
cl.entities.register{ id = "creature", kind = "mob", fallback = "minecraft:zombie", width = 0.8, height = 1.2 }
cl.render.entity_model("my_mod:creature", "my_mod:models/entity/creature.glb", { default_clip = "idle" })
end)
Now every my_mod:creature looks like your model on Celestial clients with your mod, and loops "idle" until you play something else. Light, the red flash when it gets hurt, its name tag and its shadow work like on any mob. Players without your mod still see the zombie.
The options: scale (default 1), yaw_offset (degrees; 180 if your model faces the other way), default_clip, and no_shadow = true, nearest = true (crisp pixel-art textures) or no_death_tilt = true (it doesn’t fall over when it dies).
Play a clip
local c = player.world:spawn("my_mod:creature", player.pos + cl.vec(0, 0, 3))
c:anim_play("walk") -- loops (the clip's own setting)
c:anim_play("attack", { loop = false }) -- cross-fades from walk in 0.2 s, plays once, fades out
print(c:anim_clip(0)) --> attack
Calls on the server reach every player: the game sends the animation to their clients. The options you will use most:
| Option | Means |
|---|---|
fade |
Seconds to blend from what played before. 0 cuts at once. |
speed |
2 is twice as fast, 0 freezes. |
loop |
true or false; without it, the clip’s own setting. |
layer |
Which layer, 0 to 7 (next section). |
c:anim_stop() fades every layer out; c:anim_stop(1, 0.5) only layer 1, over half a second.
Layers
Each layer plays one clip, and higher layers go on top. With a mask, a layer moves only some bones (each with its children):
c:anim_play("walk") -- layer 0: the whole body
c:anim_play("wave", { layer = 1, mask = { "right_arm" } }) -- layer 1: just the arm
An additive layer adds its clip on top instead of replacing the pose. A “breathing” clip made that way works while walking, running or standing:
c:anim_play("breathe", { layer = 2, additive = true, weight = 0.5 })
c:anim_layer(2, { weight = 1, fade = 1 }) -- breathe harder, blended over 1 second
Faces that change shape
Morph targets (Blender: shape keys) change the shape of a mesh. Clips can animate them, and you can set them yourself:
c:set_morph("inhale", 0.8) -- 0 to 1
c:set_morph("inhale", -1) -- give it back to the clips
Events
entity.anim_event fires when a clip passes one of its events:
cl.on("entity.anim_event", function(event)
if event.client then return end -- it fires on clients too
if event.name == "strike" then
for _, p in ipairs(cl.entity.server_players()) do
local d = p.pos - event.entity:position()
if d.x * d.x + d.z * d.z < 4 then p:set_health(p.health - 3) end
end
end
end, { filter = "my_mod:creature" })
The damage lands exactly when the claw does, at any speed. Sounds and particles belong in the event.client branch, where they play on each player’s own game.
Look at the player
c:anim_look_at({ "neck", "head" }, player, 120, { yaw = 60, pitch = 30 })
The neck and head share the turn, at most 60 degrees left or right and 30 up or down, turning at 120 degrees per second. c:anim_look_stop() turns them back.
Where is that bone?
c:bone_transform("jaw") tells you where a bone is right now: pos, and the directions forward, up and right. Useful for hits, particles and attaching things:
local jaw = c:bone_transform("jaw")
print(jaw.pos.y > c:position().y) --> true
Items and blocks with models
The same glTF files work for items and blocks (API 1.15):
cl.on("lifecycle.register", function()
-- the item draws the model in the hand, the inventory, item frames and on the ground
cl.render.item_model("my_mod:crystal_staff", "my_mod:models/item/staff.glb", {
default_clip = "glow",
display = { gui = { rotation = { 20, 30, 0 }, scale = 0.9 } },
})
-- every my_mod:statue block draws the model; it turns with the block's facing
local statue = cl.render.block_model("my_mod:statue", "my_mod:models/block/statue.glb", {})
statue:set_lod_distances({ 12, 32 }) -- the file's statue_lod1 / statue_lod2 nodes from 12 and 32 blocks
end)
fit (on by default) makes an item model the size of a block item; display uses the names of vanilla item models (gui, ground, fixed, firstperson_righthand, …). A block entity such as minecraft:chest draws the model instead of its vanilla look. More in the guide Item and Block Models.
Try it
Make a creature that walks while you are farther than 6 blocks away, and plays "attack" once when you come closer (check it every 10 ticks).
Show a solution
local state = {}
cl.on("entity.tick", function(event)
if event.client then return end
local c = event.entity
local s = state[c] or { n = 0 }
state[c] = s
s.n = s.n + 1
if s.n % 10 ~= 0 then return end
local p = cl.entity.server_players()[1]
if not p then return end
local d = p.pos - c:position()
local near = d.x * d.x + d.z * d.z < 36
if near and s.mode ~= "attack" then
c:anim_play("attack", { loop = false, fade = 0.1 })
s.mode = "attack"
elseif not near and s.mode ~= "walk" then
c:anim_play("walk")
s.mode = "walk"
end
end, { filter = "my_mod:creature" })
The mode field keeps the clip from restarting every 10 ticks.
Recap: cl.render.entity_model gives a type a glTF model, e:anim_play plays clips with fades on layers, masks and additive mix them, set_morph changes shapes, entity.anim_event fires on named moments, anim_look_at turns a head, and bone_transform finds a bone in the world. More in the guide Entity Models and Animation.