Wiki 9Minecraft Wiki 9Minecraft

Math-Based Animations with Molang

Create smooth, dynamic procedural entity animations in Bedrock using Molang math functions.

5Sections
4Code examples
4Known errors

Math-based animations replace static Blockbench keyframes with Molang expressions that calculate bone transformations every frame. Instead of baking hundreds of keyframes for a simple walk cycle or breathing loop, you write compact mathematical formulas that react directly to an entity's movement speed, age, or state. You reach for math-based animations when you want smooth procedural motion, smaller file sizes, and animations that blend seamlessly with gameplay variables.

How Procedural Animations Work

In a standard keyframe animation, an animation file contains a dictionary of timestamps under a bone's rotation, position, or scale channels. The game interpolates between those fixed timestamps. In a math-based animation, you omit the timestamps entirely and provide a single three-element array of strings or numbers directly to the transformation channel.

The game evaluates these Molang strings every render frame. Because there is no fixed timeline, you must supply a continuously changing number to drive the motion. This is known as the time driver. The two most common drivers are `query.anim_time`, which counts up in seconds from the moment the animation starts playing, and `query.life_time`, which counts up from the moment the entity is loaded into the client world.

When you put a time driver inside a trigonometric function like `math.sin` or `math.cos`, the output smoothly oscillates between -1 and 1 as time passes. By multiplying the input and output of these functions, you control both the speed and the distance of the bone's movement.

RP/animations/floating_drone.animation.json
{
  "format_version": "1.8.0",
  "animations": {
    "animation.floating_drone.idle": {
      "loop": true,
      "bones": {
        "body": {
          "position": [
            0.0,
            "math.sin(query.life_time * 180) * 2.0",
            0.0
          ],
          "rotation": [
            "math.cos(query.life_time * 180) * 5.0",
            0.0,
            "math.sin(query.life_time * 90) * 3.0"
          ]
        }
      }
    }
  }
}

Core Molang Math Functions and Queries

A critical detail of Bedrock's Molang engine is that `math.sin` and `math.cos` take their input in degrees, not radians. A full wave cycle is 360 degrees. This means `math.sin(query.life_time * 360)` completes exactly one full back-and-forth cycle every second. If you want an idle breathing cycle to take four seconds, you divide 360 by 4, giving you a multiplier of 90.

Every wave expression follows the same four-part pattern: `Offset + math.sin(Time * Frequency + Phase) * Amplitude`. The Offset shifts the starting rest angle; Frequency controls how fast the wave cycles; Phase delays or advances the wave for specific bones so they do not all move in robotic unison; and Amplitude sets the maximum degrees of rotation or pixels of movement.

For animations that should only move when the entity walks, replace the time clock with `query.modified_distance_moved`. This query increments based on the actual distance the entity travels across the ground. When the entity stops walking, the query stops increasing, and the legs naturally freeze in place without needing an animation controller transition.

ExpressionWhat it doesNotes
math.sin(x)Returns the sine of x between -1 and 1Input x is measured in degrees (360 is one full cycle).
math.cos(x)Returns the cosine of x between -1 and 1Starts at 1 when x is 0; equals math.sin(x + 90).
math.clamp(val, min, max)Restricts a value between min and maxUseful for capping extreme rotations during high speeds.
math.lerp(a, b, t)Linearly interpolates from a to b by factor tFactor t goes from 0.0 (returns a) to 1.0 (returns b).
math.abs(x)Returns the positive value of xFolds the bottom half of a sine wave up for bouncing steps.
query.life_timeTime in seconds since the entity renderedBest for continuous idle animations; never resets.
query.anim_timeTime in seconds since current animation startedResets to 0 when the animation starts or restarts.
query.modified_distance_movedTotal distance moved, scaled by entity speedStandard driver for procedural walk and run cycles.
query.modified_move_speedCurrent normalized movement speed (0.0 to 1.0)Multiply your amplitude by this so legs settle when stopping.

Building a Procedural Walk Cycle

Walk cycles are the most common place to use math-based animations. For a standard quadruped or biped, opposite limbs need to swing in opposite directions. There are two clean ways to invert a wave for the opposite leg: either multiply the final amplitude by `-1`, or add `180` degrees to the phase inside the sine function. Using a cosine wave on the body's vertical position at double the leg frequency causes the torso to bob up and down each time a foot passes the center line.

If you drive a walk cycle purely with `query.modified_distance_moved`, the legs will stop mid-stride when the entity halts. To make the legs return smoothly to a neutral standing pose, multiply the entire expression by `query.modified_move_speed`. As the entity slows to a stop, `query.modified_move_speed` drops toward zero, scaling the leg rotation down to zero degrees.

RP/animations/forest_beetle.animation.json
{
  "format_version": "1.8.0",
  "animations": {
    "animation.forest_beetle.walk": {
      "loop": true,
      "bones": {
        "body": {
          "position": [
            0.0,
            "math.abs(math.cos(query.modified_distance_moved * 38)) * query.modified_move_speed * 1.5",
            0.0
          ],
          "rotation": [
            0.0,
            0.0,
            "math.sin(query.modified_distance_moved * 38) * query.modified_move_speed * 4.0"
          ]
        },
        "leg_front_left": {
          "rotation": [
            "math.sin(query.modified_distance_moved * 38) * 35.0 * query.modified_move_speed",
            0.0,
            0.0
          ]
        },
        "leg_front_right": {
          "rotation": [
            "-math.sin(query.modified_distance_moved * 38) * 35.0 * query.modified_move_speed",
            0.0,
            0.0
          ]
        },
        "leg_back_left": {
          "rotation": [
            "math.sin(query.modified_distance_moved * 38 + 90) * 35.0 * query.modified_move_speed",
            0.0,
            0.0
          ]
        },
        "leg_back_right": {
          "rotation": [
            "-math.sin(query.modified_distance_moved * 38 + 90) * 35.0 * query.modified_move_speed",
            0.0,
            0.0
          ]
        }
      }
    }
  }
}

Optimization with Client Entity Pre-Animation Scripts

When an entity has many bones using the exact same time and frequency calculations—such as a centipede with twenty legs or a dragon with a ten-segment tail—writing the full `query.modified_distance_moved` formula inside every bone channel wastes CPU time. The client has to look up the query and perform the multiplication dozens of times per frame.

You can calculate common values once per frame inside the client entity file (`RP/entity/`) using the `scripts.pre_animation` array. Any `variable.` or `temp.` value you assign there is immediately available to all animations running on that entity during that frame.

While you are inside the client entity definition, you can also attach a continuous math animation directly in the `scripts.animate` array without needing a separate animation controller file. You can even pass a blend weight expression right in the `scripts.animate` array if you want to turn the animation up or down based on a query.

RP/entity/forest_beetle.entity.json
{
  "format_version": "1.10.0",
  "minecraft:client_entity": {
    "description": {
      "identifier": "custom:forest_beetle",
      "materials": {
        "default": "entity_alphatest"
      },
      "textures": {
        "default": "textures/entity/forest_beetle"
      },
      "geometry": {
        "default": "geometry.forest_beetle"
      },
      "animations": {
        "walk": "animation.forest_beetle.walk",
        "look_at_target": "animation.common.look_at_target"
      },
      "scripts": {
        "pre_animation": [
          "variable.walk_wave = math.sin(query.modified_distance_moved * 38) * 35.0 * query.modified_move_speed;"
        ],
        "animate": [
          "walk",
          "look_at_target"
        ]
      },
      "render_controllers": [
        "controller.render.default"
      ]
    }
  }
}

Chaining Bone Motion for Tails and Tentacles

Math animations excel at overlapping action, such as waving tails, swimming fish, or floating tentacles. If you have a chain of parented bones (`tail_1` -> `tail_2` -> `tail_3`), applying the exact same sine wave to all three bones will make the tail curl into a stiff C-shape and uncurl all at once.

To create a realistic S-curve wave that travels down the length of the tail, subtract a phase offset (usually between 30 and 60 degrees) from each subsequent child bone down the chain. Because child bones inherit their parent's rotation in Bedrock geometry, even a small 10-degree amplitude per segment will add up to a sweeping 40-degree curve at the tip of a four-bone tail.

RP/animations/sea_serpent.animation.json
{
  "format_version": "1.8.0",
  "animations": {
    "animation.sea_serpent.swim": {
      "loop": true,
      "bones": {
        "tail_1": {
          "rotation": [0.0, "math.sin(query.life_time * 240) * 12.0", 0.0]
        },
        "tail_2": {
          "rotation": [0.0, "math.sin(query.life_time * 240 - 45) * 14.0", 0.0]
        },
        "tail_3": {
          "rotation": [0.0, "math.sin(query.life_time * 240 - 90) * 16.0", 0.0]
        },
        "tail_4": {
          "rotation": [0.0, "math.sin(query.life_time * 240 - 135) * 18.0", 0.0]
        }
      }
    }
  }
}

Which versions this applies to

Applies to Bedrock Edition 1.8.0 and higher for animation files, and 1.10.0 and higher for client entity definitions. All Molang math functions and movement queries covered here are stable and do not require any experimental toggles.

When it does not work

What you seeWhyThe fix
The bone barely twitches instead of swinging back and forth, even with a large amplitude.You passed `query.life_time` or `query.anim_time` into `math.sin()` assuming it uses radians, or without multiplying it by a frequency. Since `math.sin()` uses degrees, unmultiplied time only advances the wave by 1 degree per second, taking six minutes to finish a single loop.Multiply the time query inside the parentheses by a degree frequency, such as `math.sin(query.life_time * 180) * 30`, where 180 is the speed in degrees per second and 30 is the swing angle.
Content Log shows `[Molang][error]-Error: unhandled request for unknown variable 'variable.walk_wave'` and the bone stays frozen at 0.The animation references a custom `variable.` that has not been initialized yet, either because of a typo in `scripts.pre_animation` or because the entity file is missing the `pre_animation` block.Ensure the variable is defined in the entity's `scripts.pre_animation` array, or use the null-coalescing operator `(variable.walk_wave ?? 0)` inside the animation so it falls back cleanly if evaluated early.
The animation plays for a split second when the entity spawns and then freezes permanently.A math-based animation has no keyframe timestamps, so its calculated length is 0.0 seconds. Without `"loop": true`, the animation finishes on its very first frame.Add `"loop": true` to the animation definition in your `.animation.json` file so the game re-evaluates the Molang expressions every frame.
Content Log reports a JSON syntax error or ` Molang Error: unexpected token` when loading the resource pack.A Molang expression containing math operators (`*`, `+`, `-`) was written directly in the JSON array without quotation marks, or a parenthesis inside the string was left unclosed.Wrap every non-numeric Molang expression in double quotes inside the transformation array, and verify that every opening parenthesis `(` in your `math.` functions has a matching `)`.

Reference, straight from the game files

Read out of the game itself, so the ids match the version you are building for.