Overview of Detection Approaches
Unlike other editions of Minecraft that provide built-in scoreboard criteria for distance walked or sneaked, Minecraft Bedrock commands do not have native movement scoreboards. In a purely command-block-driven world, creators are forced to rely on block footprints—checking if the block at `~ ~-1 ~` is `minecraft:air` to guess if a player is jumping, or checking for `minecraft:water` to guess if they are swimming. These spatial checks are fragile. A player standing on the edge of a slab appears to be in the air, and a player standing in shallow water is not necessarily in a swimming posture.
When building a Behavior Pack, you have two exact, reliable methods to feed movement states into your command systems. The first is attaching a Behavior Pack animation controller to `minecraft:player`. Behavior Pack animation controllers evaluate Molang expressions every tick on the server and can execute slash commands or apply entity tags directly when a movement query turns true. The second method is the Script API (`@minecraft/server`), which exposes boolean properties and velocity vectors directly on the `Player` class in JavaScript, allowing you to apply tags or trigger commands without rewriting `BP/entities/player.json`.
For both methods, the best practice for command recipes is the tag-bridge pattern. Instead of placing all of your game logic inside the controller or script, have the detector simply add and remove tags like `is_sneaking` or `is_sprinting`. Your `.mcfunction` files or repeating command blocks can then target `@a[tag=is_sneaking]` cleanly, keeping your command recipes modular and easy to debug.
| Method | Requires player.json | Best Used For |
|---|---|---|
| Coordinate / Block Checks (`execute if block`) | No | Quick spatial checks like detecting if a player is off the ground or inside liquid without modifying player files. |
| BP Animation Controllers + Molang | Yes | State-based detection (sneaking, sprinting, swimming, jumping) that triggers `.mcfunction` files or manages tags directly. |
| Script API (`@minecraft/server`) | No | High-compatibility packs that cannot override `player.json`, or mechanics that need exact velocity vectors (`getVelocity()`). |
Molang Queries for Movement
Molang provides a suite of `query.` functions (which can be shortened to `q.` in modern format versions) that read the actor's internal movement flags directly from the game engine. When evaluated inside a Behavior Pack animation controller or entity definition, these queries run on the server side and reflect the authoritative state of the player.
Most movement queries return `1.0` when the player is performing the action and `0.0` when they are not. A few queries, such as `query.modified_move_speed`, return a continuous float value between `0.0` and `1.0` representing how fast the entity is moving horizontally relative to its current speed attribute. Combining boolean queries with speed queries lets you distinguish between a player who is merely holding the sneak key while standing still and a player who is actively creeping forward.
One important quirk involves `query.is_jumping`. In Bedrock, this query evaluates to `1.0` for the entire duration the player is holding the jump input while airborne, not just the single tick they leave the ground. If you want to trigger a command only once per jump, you must combine it with `query.is_on_ground` or structure your animation controller states so the command only fires on entry and waits for the player to touch the ground before resetting.
| Molang Query | Return Type | What it Detects |
|---|---|---|
| `query.is_sneaking` | Boolean (0.0 or 1.0) | True while the player is in the sneaking / crouching posture. |
| `query.is_sprinting` | Boolean (0.0 or 1.0) | True while the player is actively sprinting. |
| `query.is_swimming` | Boolean (0.0 or 1.0) | True while the player is in the horizontal swimming state in water. |
| `query.is_gliding` | Boolean (0.0 or 1.0) | True while the player is gliding with an elytra. |
| `query.is_jumping` | Boolean (0.0 or 1.0) | True while the player is holding the jump input / ascending from a jump. |
| `query.is_on_ground` | Boolean (0.0 or 1.0) | True when the entity's bounding box is resting on a solid collision box. |
| `query.is_in_water` | Boolean (0.0 or 1.0) | True when any part of the entity is submerged in water (even if not swimming). |
| `query.is_moving` | Boolean (0.0 or 1.0) | True when the entity has active movement velocity on the horizontal plane. |
| `query.modified_move_speed` | Float (0.0 to 1.0) | Normalized horizontal movement speed; useful for distinguishing walking from standing still. |
Setting Up a Movement Animation Controller
A Behavior Pack animation controller lives in `BP/animation_controllers/` and uses a state machine structure. Unlike Resource Pack animation controllers, which only play visual animations or sounds, Behavior Pack controllers can execute slash commands, entity events (`@s event_name`), and Molang expressions inside their `on_entry` and `on_exit` arrays.
When tracking multiple movement modes at once—for example, sneaking and sprinting simultaneously, or jumping while sprinting—you should define separate controllers inside the same file rather than stuffing every movement type into a single state machine. A state machine can only occupy one state at a time. If sneaking and jumping share a controller, a player who jumps while sneaking would transition out of the sneaking state. By separating each detector into its own controller (`controller.animation.player.detect_sneak`, `controller.animation.player.detect_sprint`, and so on), each state machine runs independently in parallel.
In the example below, each controller starts in a `default` state and transitions to an `active` state when its respective Molang query returns true. On entering `active`, it applies a descriptive tag to `@s`. When the query returns false (`!query.is_sneaking`), it transitions back to `default` and removes the tag via `on_exit`. For jumping, we include a third state (`airborne`) that waits for `query.is_on_ground` before resetting, ensuring the `just_jumped` function only fires once per leap from solid ground.
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.player.detect_sneak": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"sneaking": "query.is_sneaking"
}
]
},
"sneaking": {
"on_entry": [
"/tag @s add move_sneaking"
],
"on_exit": [
"/tag @s remove move_sneaking"
],
"transitions": [
{
"default": "!query.is_sneaking"
}
]
}
}
},
"controller.animation.player.detect_sprint": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"sprinting": "query.is_sprinting"
}
]
},
"sprinting": {
"on_entry": [
"/tag @s add move_sprinting"
],
"on_exit": [
"/tag @s remove move_sprinting"
],
"transitions": [
{
"default": "!query.is_sprinting"
}
]
}
}
},
"controller.animation.player.detect_swim": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"swimming": "query.is_swimming"
}
]
},
"swimming": {
"on_entry": [
"/tag @s add move_swimming"
],
"on_exit": [
"/tag @s remove move_swimming"
],
"transitions": [
{
"default": "!query.is_swimming"
}
]
}
}
},
"controller.animation.player.detect_jump": {
"initial_state": "grounded",
"states": {
"grounded": {
"transitions": [
{
"jumped": "query.is_jumping && !query.is_on_ground"
}
]
},
"jumped": {
"on_entry": [
"/tag @s add move_airborne",
"/function movement/on_jump_start"
],
"transitions": [
{
"grounded": "query.is_on_ground"
}
],
"on_exit": [
"/tag @s remove move_airborne"
]
}
}
}
}
}Linking Controllers in player.json
Creating the animation controller file does nothing until an entity definition references and runs it. To run these controllers on players, you must copy the vanilla `player.json` file into your Behavior Pack at `BP/entities/player.json` and register the controllers inside the `description` block.
Inside `description`, the `animations` object maps a short alias name (like `detect_sneak`) to the full controller identifier (`controller.animation.player.detect_sneak`). Then, inside the `scripts.animate` array, you list each short alias so the engine evaluates them every tick the player exists in the world.
Only the `description` section of `BP/entities/player.json` is shown in the snippet below to highlight the wiring; in a real pack, this file must also contain the full vanilla `component_groups`, `components`, and `events` blocks so players do not lose their health, inventory, or ability to interact with the world.
{
"format_version": "1.21.0",
"minecraft:entity": {
"description": {
"identifier": "minecraft:player",
"is_spawnable": false,
"is_summonable": false,
"is_experimental": false,
"animations": {
"detect_sneak": "controller.animation.player.detect_sneak",
"detect_sprint": "controller.animation.player.detect_sprint",
"detect_swim": "controller.animation.player.detect_swim",
"detect_jump": "controller.animation.player.detect_jump"
},
"scripts": {
"animate": [
"detect_sneak",
"detect_sprint",
"detect_swim",
"detect_jump"
]
}
},
"components": {
"minecraft:type_family": {
"family": [
"player"
]
}
}
}
}Detecting Movement with the Script API
Overriding `BP/entities/player.json` has a major drawback: if a player loads two different add-ons that both modify `player.json`, only the pack highest in the load order will have its `player.json` applied. To build a movement detector that is 100% compatible with other add-ons, you can use the `@minecraft/server` Script API instead of animation controllers.
The `Player` class in `@minecraft/server` exposes read-only boolean properties that mirror the Molang queries: `isSneaking`, `isSprinting`, `isSwimming`, `isJumping`, `isGliding`, `isOnGround`, `isInWater`, and `isFalling`. In addition, `player.getVelocity()` returns an `{ x, y, z }` vector representing the entity's current movement in blocks per tick, which allows you to calculate exact horizontal speed or vertical fall speed.
By running a `system.runInterval` loop every tick, you can inspect each player's movement properties and manage tags with `addTag()` and `removeTag()`. Because adding a tag the player already has or removing a tag they lack is unnecessary work, checking `hasTag()` in a helper function keeps the script clean and responsive. Once this script is active, your `.mcfunction` files can use the exact same `@a[tag=move_sneaking]` selectors as they would with the animation controller approach.
import { world, system } from "@minecraft/server";
function syncTag(player, tag, condition) {
const has = player.hasTag(tag);
if (condition && !has) {
player.addTag(tag);
} else if (!condition && has) {
player.removeTag(tag);
}
}
system.runInterval(() => {
for (const player of world.getAllPlayers()) {
syncTag(player, "move_sneaking", player.isSneaking);
syncTag(player, "move_sprinting", player.isSprinting);
syncTag(player, "move_swimming", player.isSwimming);
syncTag(player, "move_gliding", player.isGliding);
syncTag(player, "move_jumping", player.isJumping);
syncTag(player, "move_grounded", player.isOnGround);
const v = player.getVelocity();
const horizontalSpeed = Math.hypot(v.x, v.z);
syncTag(player, "move_walking", player.isOnGround && horizontalSpeed > 0.01 && !player.isSprinting && !player.isSneaking);
syncTag(player, "move_falling", !player.isOnGround && v.y < -0.08);
}
}, 1);Using Movement Tags in Function Recipes
Once either the animation controller or the script is maintaining movement tags on your players, writing command recipes in `.mcfunction` files becomes straightforward. You can combine `tag=` target selector arguments with `scores=`, `hasitem=`, or spatial checks (`x`, `y`, `z`, `r`) to build complex mechanics.
Remember that tags in target selectors only allow one positive `tag=` argument per selector bracket unless you chain multiple `execute if entity @s[tag=...]` subcommands. You can, however, include multiple negated tags (`tag=!move_sprinting,tag=!move_sneaking`) inside a single selector bracket. The function below demonstrates a tick recipe that drains stamina on a scoreboard while sprinting, regenerates it when standing still on the ground, grants invisibility while sneaking in tall grass, and boosts swimming speed when holding a prismarine shard.
# Drain 1 stamina per tick while sprinting
scoreboard players remove @a[tag=move_sprinting,scores={stamina=1..}] stamina 1
# Apply slowness if stamina hits zero while trying to sprint
effect @a[tag=move_sprinting,scores={stamina=..0}] slowness 2 3 true
# Regenerate stamina while grounded and neither sprinting nor jumping
scoreboard players add @a[tag=move_grounded,tag=!move_sprinting,tag=!move_jumping,scores={stamina=..99}] stamina 1
# Stealth mechanic: turn invisible while sneaking inside short or tall grass
execute as @a[tag=move_sneaking] at @s if block ~ ~ ~ short_grass run effect @s invisibility 1 0 true
execute as @a[tag=move_sneaking] at @s if block ~ ~ ~ tall_grass run effect @s invisibility 1 0 true
# Dolphin boost while actively swimming with a prismarine shard in offhand
effect @a[tag=move_swimming,hasitem={item=prismarine_shard,location=slot.weapon.offhand}] conduit_power 2 0 trueWhich versions this applies to
Applies to Minecraft Bedrock 1.19.50 and newer (using the modern execute syntax). Both Behavior Pack animation controllers (format_version 1.10.0) and the @minecraft/server Script API movement properties (isSneaking, isSprinting, isSwimming, isJumping, isGliding, isOnGround, getVelocity) are stable in current 1.20.x and 1.21.x releases and require no experimental toggles.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| Commands inside the animation controller never execute, even though the JSON validates and the pack loads without errors. | The animation controller was accidentally placed in the Resource Pack (`RP/animation_controllers/`) instead of the Behavior Pack (`BP/animation_controllers/`), or its short name was added to `description.animations` in `BP/entities/player.json` without also being listed in `description.scripts.animate`. | Move the controller file to `BP/animation_controllers/` and ensure every alias defined in `description.animations` is also present inside the `description.scripts.animate` array of `BP/entities/player.json`. |
| A player disconnects while sprinting or sneaking, and upon rejoining, their movement tag (`move_sprinting` or `move_sneaking`) stays permanently stuck on them until they sprint or sneak again. | Entity tags are saved to the world database when a player logs out, but Behavior Pack animation controllers always reset to their `initial_state` (`default`) when the player entity loads back in. Because the controller starts in `default`, the `on_exit` list of the `sprinting` state never runs to clean up the saved tag. | Add `"/tag @s remove move_sprinting"` to the `on_entry` array of the `default` state as well as the `on_exit` array of the active state. That way, whenever a player joins the world and enters `default`, any leftover movement tags are stripped immediately. |
| Content Log shows: `[Animation][error]-controller.animation.player.detect_sneak | Failed to parse expression` or transitions never fire. | A Molang query name was misspelled (for example, writing `query.is_crouching` instead of `query.is_sneaking`), or you placed a Resource Pack client-only query inside a Behavior Pack controller. | Turn on Content Log GUI in Creator Settings to see the exact state and expression that failed, and verify that all queries match valid server-side Molang identifiers such as `query.is_sneaking`, `query.is_sprinting`, `query.is_swimming`, and `query.is_on_ground`. |
| When using two add-ons together, movement detection completely stops working for one of the packs. | Both Behavior Packs contain `BP/entities/player.json`. Bedrock does not merge entity JSON files across packs; the pack higher in the world's Behavior Pack list completely overrides `player.json` from the lower pack. | Either merge the `animations` and `scripts.animate` entries from both packs into a single `BP/entities/player.json`, or switch the movement detector to the `@minecraft/server` Script API approach, which does not require modifying `player.json`. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
