Mob Models
You define a mob's look in code as a box model. If you give no model, the mob is drawn with the player-shaped model wearing a 64×64 skin texture.
mod.content().entity("wisp", EntityDefinition.builder(ResourceId.of("my_mod", "textures/entity/wisp.png"))
.size(0.6f, 0.8f)
.maxHealth(6)
.noGravity()
.spawnEgg(CreativeTab.SPAWN_EGGS)
.model(EntityModelDefinition.builder(32, 32) // texture size
.part("body", part -> part.pivot(0, 16, 0)
.box(0, 0, -3, -3, -3, 6, 6, 6) // u, v, x, y, z, width, height, depth
.animation(PartAnimation.BOB))
.part("ring", part -> part.parent("body")
.pivot(0, 0, 0)
.box(0, 12, -4, 0, -4, 8, 1, 8)
.animation(PartAnimation.SPIN))
.build())
.build());
Coordinates
- Coordinates are in the game's model space. The unit is the pixel (1/16 block), y increases downward, and the ground is at y=24.
- These are the same coordinates as Blockbench's "Modded Entity" format.
box(u, v, x, y, z, width, height, depth): the texture position (u, v) and the box's corner and size.mirroredBoxflips the texture left to right.pivot(x, y, z)is the center of rotation,rotation(x, y, z)is the base rotation, andparent(name)is the parent part.
Part animations
PartAnimation | Behavior |
|---|---|
NONE | Does not move |
HEAD | Turns to follow the gaze |
WALK_SWING, WALK_SWING_OPPOSITE | Swings back and forth while walking (opposite phase) |
BOB | Moves up and down |
SPIN | Rotates |
Keyframe animations
As in Blockbench animations, you give each part values over time (keyframes), and the part moves smoothly between them. The values are added to the part's base pose. The game's keyframe animation engine does the drawing.
.model(EntityModelDefinition.builder(32, 32)
.part("body", part -> part.pivot(0, 16, 0).box(0, 0, -3, -3, -3, 6, 6, 6))
.part("ring", part -> part.parent("body").box(0, 12, -4, 0, -4, 8, 1, 8))
// Always repeats: grows and shrinks smoothly over 2 seconds
.animation("pulse", KeyframeAnimation.builder(2)
.looping()
.trigger(KeyframeAnimation.Trigger.ALWAYS)
.smooth()
.scale("body", 0, 1, 1, 1)
.scale("body", 1, 1.15f, 1.15f, 1.15f)
.scale("body", 2, 1, 1, 1)
.build())
// Only when the server starts it: lifts the ring for 1 second
.animation("wave", KeyframeAnimation.builder(1)
.rotation("ring", 0, 0, 0, 0)
.rotation("ring", 0.5f, 45, 0, 0)
.rotation("ring", 1, 0, 0, 0)
.build())
.build())
When it plays (Trigger) | Meaning |
|---|---|
ALWAYS | Always, while the mob is visible (breathing, spinning) |
WHILE_MOVING | While moving, faster the faster the mob moves (walking, wing flaps, tail) |
ON_DEMAND (default) | When the server starts it with MobView.playAnimation(name). Every client that sees the mob plays it from the start |
| Keyframe | Unit |
|---|---|
rotation(part, time, x, y, z) | Degrees |
position(part, time, x, y, z) | Model pixels, y is up (same as Blockbench) |
scale(part, time, x, y, z) | Scale factor, 1 is the original size |
timeis in seconds and must be within0..length.- Keyframes added after
smooth()are joined by a curve (Catmull-Rom). Keyframes added before it are joined by straight lines. - An
ON_DEMANDanimation withoutlooping()stops when it ends. With other triggers, it keeps its last value.
Playing from the server:
MobView wisp = (MobView) world.spawnEntity(WISP, x, y, z);
world.schedule(10, () -> wisp.playAnimation("wave")); // after the client has received the mob
wisp.stopAnimation("wave"); // stop a repeating animation
- If you move a part the model does not have, or a name does not match
[a-z0-9_]+, defining it throwsIllegalArgumentException. - A model can have up to 64
ON_DEMANDanimations. - If the entity is not a mod mob, or has no such
ON_DEMANDanimation,playAnimationthrowsIllegalArgumentException.
Needs human verification
A person must check whether the drawn result looks right. Automatic verification only confirms that the drawing path runs.