Models and animation
Lesson 7.6
Your own entity types can look like anything: a glTF model with bones, animations and morph targets (shape keys). This lesson gives a type a model, plays animations in layers, reacts to events in a clip and turns a head towards the player.
The model file
Export glTF 2.0 (.glb) into your mod’s assets: assets/my_mod/models/entity/creature.glb is "my_mod:models/entity/creature.glb". 1 unit is 1 block, the model faces +Z, every glTF animation is a clip named after it, and textures are PNG. A creature_clips.json next to it sets loop flags and events (named moments in a clip):
{ "clips": { "attack": { "loop": false, "events": { "strike": 0.3 } } } }
Give a type its model
void on_register() override {
cl_entity_type_desc d = CL_ENTITY_TYPE_DESC_INIT;
d.id = CL_STR("my_mod:creature");
d.vanilla_fallback = CL_STR("minecraft:zombie");
d.width = 0.8F;
d.height = 1.2F;
clight::registry::entity_type_register(&d);
cl_model_opts mo = CL_MODEL_OPTS_INIT;
mo.default_clip = CL_STR("idle"); // looped until you play something else
auto model = clight::render::entity_model("my_mod:creature", "my_mod:models/entity/creature.glb", &mo);
if (!model) clight::log::error("creature model failed");
else clight::log::info("walk lasts " + std::to_string(model->clip_length("walk")) + " s");
}
cl_model_opts also has scale, yaw_offset (180 for a model facing -Z), offset, shadow_radius and flags (CL_MODEL_NO_SHADOW, CL_MODEL_NEAREST for pixel art, CL_MODEL_DOUBLE_SIDED, CL_MODEL_NO_DEATH_TILT).
Play clips
void start(clight::Entity c) {
c.anim_play("walk", nullptr); // layer 0, defaults
cl_anim_play_opts look = CL_ANIM_PLAY_OPTS_INIT;
look.layer = 1;
look.mask = CL_STR("neck"); // only the neck and its children
c.anim_play("look_around", &look);
cl_anim_play_opts breathe = CL_ANIM_PLAY_OPTS_INIT;
breathe.layer = 2;
breathe.additive = true; // on top of layers 0 and 1
breathe.weight = 0.5F;
c.anim_play("breathe", &breathe);
c.set_morph("inhale", 0.3F);
}
cl_anim_play_opts: loop (-1 the clip’s own, 0 once, 1 loop), fade, speed, layer (0…7), weight, additive, mask, start, root_motion, hold. On the server, the state goes to every Celestial client that sees the entity. c.anim_stop(-1, 0.2F) fades every layer out, c.anim_layer(layer, &opts) changes a layer without restarting it, c.anim_state(layer) reads it.
Events
clight::on<clight::ev::EntityAnimEvent>([](cl_ev_anim_event& e) {
if (e.client) return; // fires on clients too (sounds, particles)
if (std::string_view(e.name.ptr, e.name.len) == "strike") clight::log::info("the claw lands");
}, {.filter = "my_mod:creature"}).detach();
Look-at and bones
void follow(clight::Entity c, clight::Entity player) {
cl_look_at_opts o = CL_LOOK_AT_OPTS_INIT;
o.bones = CL_STR("neck,head"); // parent first; they share the turn
o.target_entity = player.raw(); // its eyes; or o.target = a position
o.max_yaw = 60.0F;
o.max_pitch = 30.0F;
c.anim_look_at(&o);
if (auto jaw = c.bone_transform("jaw")) clight::log::info("jaw y " + std::to_string(jaw->pos.y));
}
cl_bone_transform has pos, the unit vectors forward, up, right, and scale.
Items and blocks with models
The same glTF files work for items and blocks (API 1.15):
cl_item_model_opts io = CL_ITEM_MODEL_OPTS_INIT;
io.default_clip = CL_STR("glow");
io.display = CL_STR(R"({"gui": {"rotation": [20, 30, 0], "scale": [0.9, 0.9, 0.9]}})");
clight::render::item_model("my_mod:crystal_staff", "my_mod:models/item/staff.glb", &io);
cl_block_model_opts bo = CL_BLOCK_MODEL_OPTS_INIT;
if (auto statue = clight::render::block_model("my_mod:statue", "my_mod:models/block/statue.glb", &bo))
statue->set_lod_distances("12,32", 0); // statue_lod1 / statue_lod2 from 12 and 32 blocks
io.fit (true by default) makes an item model the size of a block item; display is the "display" JSON of vanilla item models. A block entity such as minecraft:chest draws the model instead of its vanilla look. Guide: Item and Block Models.
Try it
Play "attack" once (no loop, a 0.1 s fade) on every creature that takes damage, then go back to "walk" when its "attack" clip fires "strike".
Show a solution
clight::on<clight::ev::EntityDamage>([](cl_ev_entity_damage& e) {
cl_anim_play_opts o = CL_ANIM_PLAY_OPTS_INIT;
o.loop = 0;
o.fade = 0.1F;
clight::Entity(e.entity).anim_play("attack", &o);
}, {.filter = "my_mod:creature"}).detach();
clight::on<clight::ev::EntityAnimEvent>([](cl_ev_anim_event& e) {
if (e.client || std::string_view(e.name.ptr, e.name.len) != "strike") return;
clight::Entity(e.entity).anim_play("walk", nullptr);
}, {.filter = "my_mod:creature"}).detach();
Recap: clight::render::entity_model gives a type a glTF model, Entity::anim_play plays clips on layers (masks, additive, fades), ev::EntityAnimEvent fires on named moments, anim_look_at turns bones toward a target and bone_transform finds a bone in the world. Guide: Entity Models and Animation.