Skip to content

Entity Models & Animations ​

A custom mob can be attached to a 3D model, with standard actions, K-frames, and distance tiers (LOD) configured alongside. EmakiMobs can attach custom mobs to 3D models rendered by ModelEngine or BetterModel, driving standard actions, keyframe (K-frame) events, and distance-based LOD. Both model backends are optional dependencies: with neither installed, the model layer is silently disabled and mobs run in vanilla form

Prerequisites ​

  1. Install the ModelEngine (paid) or BetterModel (free, open source) plugin itself
  2. Place Blockbench-exported .bbmodel blueprints into that plugin's blueprints/ directory and wire up the resource pack containing the model textures
  3. Pick the backend in EmakiMobs' config.yml under model: (see below)
  4. Add a model: section to the mob definition YAML (see below)

Blueprint name resolution inside each backend is that backend's own business; EmakiMobs only queries by string name

Main model: section ​

FieldTypeRequiredDefaultAccepted valuesDescription
apistringNoautoauto / model_engine / better_model (aliases modelengine / bettermodel also accepted; case-insensitive)auto picks a backend (ModelEngine first, then BetterModel). An invalid value raises a precheck WARN and falls back to auto; when the selected backend is absent, models are disabled with a console note
view_distancedoubleNo48.0— (blocks)Model view range. NEAR renders at this range; at MID ModelEngine shrinks it to min(view_distance, mid) and BetterModel only spawns the model to players within that distance; at FAR ModelEngine zeroes the range and BetterModel pauses the tracker
lod.neardoubleNo16.0must be ≤ lod.midLOD lower bound (blocks). A distance ≤ this is NEAR (full range)
lod.middoubleNo32.0must be ≥ lod.near and ≤ lod.farLOD middle bound (blocks). Between near and mid is MID (degraded range); anything beyond mid is FAR (so mid–far is also FAR), which makes it the real hide boundary
lod.fardoubleNo48.0must be ≥ lod.midFar-side early-return bound: a nearest-player distance beyond it is treated as FAR immediately. Lowering it below lod.mid makes part of the MID tier unreachable
movement_check_interval_ticksintNo5≥ 1 (ticks)Period of the movement (idle/walk/run) and LOD check; the tier or state is pushed to the backend only when it changes
death_detach_delay_ticksintNo40≥ 0 (ticks)Ticks to wait after the death animation starts before detaching the model
walk_speed_thresholddoubleNo0.12— (blocks/tick)Horizontal speed at which the mob counts as walking
run_speed_thresholddoubleNo0.28— (blocks/tick)Horizontal speed at which the mob counts as running (checked before the walking threshold)
transitions.<action>_fade_ticksintNo5 for idle/walk/run, 0 for death, 2 for hurt, 3 for attack/skill/interact≥ 0 (ticks)Blend-in length delegated to the engine's native fading. <action> only accepts the eight fixed keys idle, walk, run, death, hurt, attack, skill, interact; other keys are never read, and negative values are treated as 0

Tier behaviour: NEAR renders at full range; MID degrades (ModelEngine shrinks the range to min(view_distance, mid), BetterModel only spawns the model to players within that distance); at FAR ModelEngine zeroes the view range and BetterModel pauses that model's tracker

Force BetterModel and tighten the mid tier to 24 blocks:

yaml
model:
  api: "better_model"
  lod:
    near: 12.0
    mid: 24.0
    far: 48.0

Mob definition model: section ​

yaml
model:
  blueprint: "bone_lord"        # required: model blueprint name
  api: "model_engine"           # optional: overrides the global model.api, selecting the backend for this mob
  scale: 1.2                    # optional: model scale, default 1.0
  animations:                   # optional: standard action → engine animation name
    idle: "breath"
    walk: "walk_slow"
    run: "run_fast"
    death: "die"
    hurt: "flinch"
    attack: "swing"
    skill: "cast"
    interact: "nod"
  keyframes:                    # optional: K-frame timelines for any animation
    attack:
      duration_ticks: 24        # timeline length in ticks
      loop: false               # whether playback loops
      priority: "action"        # ambient|utility|action|reactive|terminal
      conflict: "replace"       # replace|reject policy for equal priorities
      frames:
        - tick: 0
          action: "self | play_sound sound=minecraft:entity.wither.shoot"
        - tick: 10
          label: "impact"
          action: "self | spawn_particle particle=flame"
  lod:                          # optional: overrides the global model.lod, giving this mob its own tier bounds
    near: 12.0
    mid: 24.0
    far: 40.0
FieldTypeRequiredDefaultAccepted valuesDescription
blueprintstringYes (within this section)nonea blueprint name present in the backend's blueprints/A missing or blank value makes the section ignored with a load-time warning (console.model_missing_blueprint). If the backend cannot find the blueprint at runtime, console.model_blueprint_missing is logged and the attach is skipped
apistringNonone (inherits the global model.api)auto / model_engine / better_modelThe model backend for this mob, overriding the global config. An invalid value raises loader.model_api_invalid and falls back to the global config
scaledoubleNo1.0—Model scale multiplier, used both on attach and on a mob_set_model swap
animations.<action key>stringNonone (uses the built-in default animation name)an animation name present in the blueprintAction key → engine animation name. Both key and value are normalised (key lower-cased, value trimmed); blank values are ignored
keyframes.<action key>sectionNonone—K-frame timeline; the key is the action key (normalised to lower case). A non-standard action key (not one of idle... interact) is registered with utility priority and no looping
keyframes.<action key>.duration_ticksintNo20≥ 1Total timeline length in ticks. A value below 1 discards the whole timeline with a warning; a looping animation wraps at this value
keyframes.<action key>.loopbooleanNothe action's built-in value for a standard action (idle/walk/run are true, the rest false); false for a non-standard keytrue / falseWhether playback loops
keyframes.<action key>.prioritystringNothe action's built-in priority for a standard action; utility for a non-standard keyambient / utility / action / reactive / terminal (case-insensitive)Playback priority; an unrecognised value is treated as ambient
keyframes.<action key>.conflictstringNoreplacereplace / reject (case-insensitive)Equal-priority conflict policy; an unrecognised value is treated as replace
keyframes.<action key>.frameslistNo[]items are {tick, action, label} mappingsFrame list. Frames run in list order and are not sorted; a frame missing tick, or with a non-numeric tick, is dropped with a warning (loader.model_keyframe_missing_tick)
keyframes.<action key>.frames[].tickintYes (within a frame)none≥ 0 (ticks)Trigger tick; tick: 0 runs immediately when playback starts. It is not clamped to duration_ticks — a frame beyond the duration delays playback until that tick before continuing or finishing
keyframes.<action key>.frames[].actionstringNo""one CoreLib action pipeline lineFrame payload; an empty string only notifies listeners and runs nothing, and a compile failure logs a warning
keyframes.<action key>.frames[].labelstringNo""—Frame label, passed to animation listeners with the frame (diagnostics only)
lod.near / lod.mid / lod.fardoubleNo16.0 / 32.0 / 48.0—This mob's LOD tier bounds, overriding the global config.yml model.lod; when only some are written, the missing ones fall back to the defaults on the left (they do not inherit the global values)
  • The whole model: section is optional; without it the mob gets no model
  • model.api and model.lod take effect per mob: the backend bridge is resolved and cached by the api value, and LOD tiers are decided by this mob's own bounds. Without api the mob inherits the global model.api; without lod it inherits the global model.lod

Standard actions ​

ActionTriggered byDefault priorityLoops
idleafter attach / after a transient action finishes / standing stillambientyes
walkhorizontal speed ≥ walk_speed_thresholdambientyes
runhorizontal speed ≥ run_speed_thresholdambientyes
attacklanding a hit (on_damage_give)actionno
skillskill cast (via mob_play_animation)actionno
interactinteraction (via mob_play_animation)utilityno
hurttaking damage (on_damage_take)reactiveno
deathdeath (on_death), detach follows after the delayterminalno

Priority rules: a higher tier pre-empts the current playback; equal tiers resolve by the conflict policy (replace or reject); lower-tier requests are rejected. terminal (death) cannot be pre-empted. A standard action with no timeline uses the default duration (20 ticks) plus that action's built-in priority and looping setting

Keyframes (K-frames) ​

Timelines are driven by EmakiCoreLib's animation scheduler: when playback starts, tick: 0 frames run immediately and later frames fire at their exact tick on the entity's owner thread. A frame payload is one CoreLib action pipeline line, so any registered stage (spawn_particle, play_sound, attribute changes, state switches, ...) plus the whole expression/placeholder syntax is reusable as-is

Action stages ​

Any CoreLib action line (skills, loot actions, third-party plugins) can use:

text
self | mob_play_animation animation=spin
self | mob_stop_animation animation=spin
self | mob_set_model blueprint=bone_lord_phase2
StageParameterTypeRequiredDefaultDescription
mob_play_animationanimationstringYesnoneAn action key defined in the mob's model.animations / model.keyframes; missing or blank returns a configuration error
mob_stop_animationanimationstringYesnoneAs above; stops playback of the matching definition
mob_set_modelblueprintstringYesnoneThe new blueprint name; missing or blank returns a configuration error, and a blueprint the backend cannot find logs a warning and keeps the current model
  • All three require the target to resolve to a managed mob entity; a non-managed entity returns skipped without an error
  • mob_set_model hot-swaps the blueprint (reusing the definition's scale) and falls back to idle afterwards

Third-party extensions ​

Other plugins can register animation definitions, play them, and observe playback through EmakiCoreLibApi (see CoreLib animation system). EmakiMobs consumes the same registry, so third-party mob:<id>:<key> definitions are directly referenceable by mob_play_animation

Troubleshooting ​

SymptomCheck
No model on mobBackend plugin installed; model.api (global or per-mob) points to an installed backend; startup log shows 未找到可用的模型后端 (auto and neither backend available) or 模型后端 '<名称>' 未安装
Missing-blueprint warningIs console.model_blueprint_missing logged? Does the name match the file (without extension) in the backend's blueprints/? Did you /meg reload or restart the backend?
Animation does not playDoes the engine animation name exist in the blueprint? Are the animations: action keys spelled right?
K-frames never fireIs frames[].action a valid pipeline line (a compile failure logs Animation keyframe line did not compile)? Are frames[].tick values in ascending order (frames run in list order and are not sorted)? Is a tick larger than duration_ticks (it is not clamped)?
Model vanishes at rangeThis is LOD by design; the boundary is lod.mid, not lod.far. Raise lod.mid first, then lod.far (early-return bound) and view_distance (NEAR/MID tier range) as needed. If it only happens for one mob, check its model.lod overrides