How Entity Texture Animation Works
In Bedrock Edition, an entity's visual appearance is bound together by its client entity file and one or more render controllers. Because resource packs only parse `flipbook_textures.json` for block atlas textures, entity textures are loaded as single static images. To make an entity texture appear animated, you have two distinct approaches inside the render controller: cycling through an array of separate PNG files, or shifting the UV coordinates across a single sprite sheet.
Both methods rely on Molang, Bedrock's lightweight expression language. By inserting time-based queries such as `query.life_time` or `query.anim_time` into the render controller, the game evaluates a new texture index or UV offset every frame. Array switching is best for simple state changes or low-frame-count loops like blinking, while UV offsetting is ideal for continuous scrolling effects or tall vertical sprite sheets.
Method 1: Frame-by-Frame Texture Arrays
The most straightforward way to animate an entity texture is to save each frame as a separate PNG file and register all of them inside the client entity definition (`RP/entity/`). Each texture is given a short alias in the `textures` object. The client entity file also tells the entity which render controller to use.
When setting up your textures, keep the dimensions identical across all frames so the entity's geometry UVs continue to line up properly as the images swap.
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "custom:golem_drone",
"materials": {
"default": "entity_alphatest"
},
"textures": {
"frame_0": "textures/entity/golem_drone/drone_0",
"frame_1": "textures/entity/golem_drone/drone_1",
"frame_2": "textures/entity/golem_drone/drone_2",
"frame_3": "textures/entity/golem_drone/drone_3"
},
"geometry": {
"default": "geometry.golem_drone"
},
"render_controllers": [
"controller.render.golem_drone"
]
}
}
}Building the Array Render Controller
Inside the render controller, you group those texture aliases into a texture array under the `arrays` block. You can name the array anything that starts with the `Array.` prefix. In the `textures` list below it, you index into that array using a Molang expression.
Because `query.life_time` returns a floating-point number in seconds, multiplying it by your desired frames per second (FPS) increases the index at a steady rate. In Bedrock's render controllers, floating-point results used as array indices are truncated to integers, and indices that exceed the array length automatically wrap around using modulo arithmetic. However, using `math.floor()` or `math.mod()` explicitly makes your intent clearer and avoids edge-case precision bugs.
{
"format_version": "1.8.0",
"render_controllers": {
"controller.render.golem_drone": {
"arrays": {
"textures": {
"Array.skins": [
"Texture.frame_0",
"Texture.frame_1",
"Texture.frame_2",
"Texture.frame_3"
]
}
},
"geometry": "Geometry.default",
"materials": [
{
"*": "Material.default"
}
],
"textures": [
"Array.skins[math.mod(math.floor(query.life_time * 8), 4)]"
]
}
}
}Method 2: UV Scrolling and Sprite Sheets
If your animation has many frames, loading dozens of separate texture files becomes tedious. Instead, you can stack your frames vertically or horizontally into a single image file and use the `uv_anim` object inside the render controller to shift the UV reading window.
The `uv_anim` block accepts two two-element arrays: `offset` and `scale`, representing the `[U, V]` axes (horizontal and vertical, respectively). These values operate in normalized UV space from `0.0` to `1.0`, regardless of the actual pixel resolution of your texture.
To play a 4-frame vertical strip at 10 frames per second, you set the vertical scale (`V`) to `0.25` (which is `1 / 4` of the image height) and step the vertical offset in increments of `0.25`. For a smooth scrolling texture—like the charged creeper armor or a conveyor belt—you leave the scale at `1.0` and feed a continuous time value into the offset.
| Field | What it does | Notes |
|---|---|---|
| uv_anim.offset | Shifts the starting UV coordinates [U, V] across the texture. | Accepts floats or Molang strings. 1.0 equals one full texture width or height. |
| uv_anim.scale | Multiplies the size of the UV map [U, V] before offsetting. | Use 1 / frame_count to isolate a single frame on a sprite strip. |
| arrays.textures | Defines a named list of Texture.* references. | Array names must begin with the 'Array.' prefix. |
| query.life_time | Returns the time in seconds since the entity loaded on the client. | Standard clock for continuous loops; resets when the entity leaves render distance. |
{
"format_version": "1.8.0",
"render_controllers": {
"controller.render.energy_core_stepped": {
"geometry": "Geometry.default",
"materials": [
{
"*": "Material.default"
}
],
"textures": [
"Texture.default"
],
"uv_anim": {
"offset": [
0.0,
"math.mod(math.floor(query.life_time * 10), 4) / 4.0"
],
"scale": [
1.0,
0.25
]
}
}
}
}Driving Texture Frames from Animations
Relying on `query.life_time` works well for ambient loops, but sometimes you need a texture change to sync up with a specific attack or state—such as an eye flashing red right before a laser fires. For this, you can set a Molang variable inside a regular resource pack animation (`RP/animations/`) or animation controller, and read that variable inside the render controller.
Initialize your variable (for example, `variable.eye_frame = 0;`) inside the `initialize` array under `scripts` in the client entity file so the render controller never evaluates an uninitialized value. Then, keyframe or assign `variable.eye_frame` in your animation file, and use `Array.skins[variable.eye_frame]` in the render controller's `textures` list.
Which versions this applies to
Applies to Minecraft Bedrock 1.8.0 and later for render controllers, and 1.10.0 and later for modern client entity definitions. All features covered here are stable and do not require any experimental toggles.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| The entity renders with a pink and black checkerboard texture on specific frames of the animation. | One of the `Texture.*` aliases listed in your render controller's `Array` does not match a key in the client entity's `textures` block, or the file path in the client entity points to a PNG that does not exist. | Check the Content Log for `[RenderController]` warnings about missing textures, verify every alias in `Array.skins` matches a key in the `.entity.json` file, and confirm the PNG files are saved in the exact `RP/textures/...` folder. |
| Using `uv_anim` causes the entire entity texture to stretch, squash, or display adjacent body parts on the wrong cubes. | When you change `uv_anim.scale` to `1 / N` for a vertical sprite sheet, your model's UVs must be mapped to the full width and height of a single frame, and each frame on the image must be a complete texture layout stacked vertically. If you only stack a small patch of the texture, the global UV scale will distort the rest of the model. | Duplicate the entire entity texture canvas vertically for each frame so the total image height is `base_height * frame_count`, or isolate the animated cubes into a separate geometry/material and use a second render controller for the animated part. |
| Content Log shows an error like `Error: unhandled request for unknown variable 'Array.skins'` and the entity turns invisible or untextured. | The array name in the render controller is missing the mandatory `Array.` prefix, or it was placed under `materials` or `geometries` instead of `arrays.textures`. | Ensure the array is defined inside `"arrays": { "textures": { "Array.name": [...] } }` and that both the definition and the reference in `"textures"` use the exact same `Array.` prefix with a capital A. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
