Wiki 9Minecraft Wiki 9Minecraft

Actionbar Compass Display Using Commands and Script API

Build a real-time HUD compass in the player actionbar using rotation queries or @minecraft/server.

5Sections
4Code examples
3Known errors

A compass display places a live directional indicator directly into the player's actionbar or title HUD, saving them from carrying a physical compass item in their hotbar. Mapmakers reach for this recipe in adventure maps, battle royale arenas, and exploration packs where screen real estate and inventory slots are tight. You can build a lightweight 8-direction version using pure commands and target selectors, or upgrade to a smooth scrolling tape compass with dynamic waypoint markers using the Script API.

How Yaw Rotation Works in Bedrock

Every directional display in Minecraft Bedrock relies on reading the player's horizontal head rotation, known as yaw. In Bedrock's coordinate system, yaw is measured in degrees ranging from -180.0 to 180.0. Understanding where the four cardinal directions fall on this circle is the foundation of any compass recipe.

South sits at exactly 0 degrees. As the player turns clockwise toward the West, the yaw value increases positively up to 90 degrees. Continuing clockwise toward North pushes the value up to 180 degrees. Because the scale wraps around at the northern meridian, turning slightly past North toward the East flips the value to -180 degrees. From there, continuing clockwise brings the player through East at -90 degrees and back to South at 0.

When you test for these angles in commands using the ry (rotation yaw maximum) and rym (rotation yaw minimum) target selector arguments, you must account for this wrap-around at North. Seven of the eight standard compass slices can be checked with a single continuous range where rym is less than or equal to ry. Due North straddles the 180 and -180 boundary, meaning a pure command setup must split the North check into two separate commands or rely on a script to normalize the angle first.

DirectionExact Center YawSelector Range (rym to ry)Coordinate Axis
South (S)0°rym=-22, ry=22Positive Z (+Z)
Southwest (SW)45°rym=23, ry=67-X / +Z
West (W)90°rym=68, ry=112Negative X (-X)
Northwest (NW)135°rym=113, ry=157-X / -Z
North (N)180° / -180°rym=158, ry=180 AND rym=-180, ry=-158Negative Z (-Z)
Northeast (NE)-135°rym=-157, ry=-113+X / -Z
East (E)-90°rym=-112, ry=-68Positive X (+X)
Southeast (SE)-45°rym=-67, ry=-23+X / +Z

Pure Command Recipe: 8-Point Actionbar Compass

If your add-on does not use scripts, you can drive an 8-point compass entirely from a ticking mcfunction file. The strategy is to run nine titleraw commands every tick—one for each of the seven contiguous directional wedges, and two to cover the split wedge at North. Each command uses @a[rym=..., ry=...] so that every player in the world sees the string matching their own camera angle.

Using titleraw instead of the plain title command lets you embed formatting codes and scoreboard values later if you want to append coordinates or a timer next to the compass. The example below uses color formatting codes (§c for red on North, §f for white on active directions, and §7 for gray on flanking directions) to simulate a three-slot viewport that shifts as the player turns.

To keep the compass responsive without flooding the command engine in multiplayer, register the function in BP/functions/tick.json. Because tick.json executes 20 times per second, the actionbar text will never fade out, and transitions between the 45-degree slices will feel immediate to the player.

BP/functions/compass_hud.mcfunction
# South (-22 to 22)
titleraw @a[rym=-22,ry=22] actionbar {"rawtext":[{"text":"§7[ SE | §f§lS§r§7 | SW ]"}]}

# Southwest (23 to 67)
titleraw @a[rym=23,ry=67] actionbar {"rawtext":[{"text":"§7[ S | §f§lSW§r§7 | W ]"}]}

# West (68 to 112)
titleraw @a[rym=68,ry=112] actionbar {"rawtext":[{"text":"§7[ SW | §f§lW§r§7 | NW ]"}]}

# Northwest (113 to 157)
titleraw @a[rym=113,ry=157] actionbar {"rawtext":[{"text":"§7[ W | §f§lNW§r§7 | §cN§7 ]"}]}

# North Split 1 (158 to 180)
titleraw @a[rym=158,ry=180] actionbar {"rawtext":[{"text":"§7[ NW | §c§lN§r§7 | NE ]"}]}

# North Split 2 (-180 to -158)
titleraw @a[rym=-180,ry=-158] actionbar {"rawtext":[{"text":"§7[ NW | §c§lN§r§7 | NE ]"}]}

# Northeast (-157 to -113)
titleraw @a[rym=-157,ry=-113] actionbar {"rawtext":[{"text":"§7[ §cN§7 | §f§lNE§r§7 | E ]"}]}

# East (-112 to -68)
titleraw @a[rym=-112,ry=-68] actionbar {"rawtext":[{"text":"§7[ NE | §f§lE§r§7 | SE ]"}]}

# Southeast (-67 to -23)
titleraw @a[rym=-67,ry=-23] actionbar {"rawtext":[{"text":"§7[ E | §f§lSE§r§7 | S ]"}]}

Registering the Function Loop

For the mcfunction file above to run automatically, reference its path (without the .mcfunction extension) inside BP/functions/tick.json. When the world loads, Bedrock runs every listed function once per game tick.

If you only want the compass to appear when a player holds a specific item or wears a custom helmet, add a tag filter such as tag=has_compass to every @a selector in compass_hud.mcfunction. You can then grant or revoke that tag from a separate controller function or script.

BP/functions/tick.json
{
  "values": [
    "compass_hud"
  ]
}

Script API Recipe: Smooth Scrolling Tape Compass

While the 9-command mcfunction is reliable, jumping in 45-degree increments feels stiff compared to modern games that use a horizontal scrolling tape at the top or bottom of the screen. Writing a 360-command mcfunction to cover every single degree is bad practice and hurts server tick performance. Instead, use the @minecraft/server Script API to read the exact float value from player.getRotation().y and slice a continuous text ribbon.

In the script below, we build a single character string representing a full 360-degree horizon, then repeat it three times so we can cleanly extract a substring window around the player's current yaw without worrying about array out-of-bounds errors at the 0/360 seam. We convert Bedrock's -180 to 180 yaw into a standard 0 to 360 bearing where North is 0, East is 90, South is 180, and West is 270.

Calling player.onScreenDisplay.setActionBar() updates the HUD directly from JavaScript without spawning command output log messages or requiring command blocks to be enabled in the world settings. Running the interval every 2 ticks (10 times per second) gives smooth motion while keeping CPU usage negligible.

BP/scripts/main.js
import { world, system } from "@minecraft/server";

// A 72-character ribbon where each character represents 5 degrees (72 * 5 = 360).
// Index 0 corresponds to North (0 degrees).
const COMPASS_RIBBON = "N · · · · NE· · · · E · · · · SE· · · · S · · · · SW· · · · W · · · · NW· · · · ";
const TRIPLE_RIBBON = COMPASS_RIBBON + COMPASS_RIBBON + COMPASS_RIBBON;
const VIEWPORT_HALF_WIDTH = 12;

system.runInterval(() => {
  for (const player of world.getAllPlayers()) {
    const rot = player.getRotation();
    
    // Convert Bedrock yaw (-180 at North, 0 at South) to 0-360 compass bearing (0 at North, 90 at East)
    let bearing = (rot.y + 180) % 360;
    if (bearing < 0) {
      bearing += 360;
    }

    // Map the 0-360 bearing to our 72-character ribbon
    const centerIndex = Math.round(bearing / 5) % 72;
    const offsetIndex = centerIndex + 72;

    const leftSlice = TRIPLE_RIBBON.substring(offsetIndex - VIEWPORT_HALF_WIDTH, offsetIndex);
    const centerChar = TRIPLE_RIBBON.substring(offsetIndex, offsetIndex + 1);
    const rightSlice = TRIPLE_RIBBON.substring(offsetIndex + 1, offsetIndex + 1 + VIEWPORT_HALF_WIDTH);

    const formattedDegree = Math.floor(bearing).toString().padStart(3, "0");
    const hudText = `§8[ §7${leftSlice}§e§l${centerChar}§r§7${rightSlice} §8] §f${formattedDegree}°`;

    player.onScreenDisplay.setActionBar(hudText);
  }
}, 2);

Adding Dynamic Waypoint Markers to the Tape

Once your compass is driven by a script, you can superimpose objective markers—such as a quest target, a bed, or a capture point—directly onto the scrolling ribbon. To calculate the angle from the player to a target coordinate, subtract the player's X and Z coordinates from the target's X and Z coordinates, then pass those deltas into Math.atan2().

Because Minecraft's Z axis points South and X axis points East, computing Math.atan2(-dx, dz) in radians and converting to degrees gives a value that matches Bedrock's native -180 to 180 yaw directly. Once you convert both the player's yaw and the target angle into 0-to-72 ribbon indices, you can replace the dot character at the waypoint's index with a colored symbol before slicing the string.

This technique also lets you store per-player waypoint coordinates using player.setDynamicProperty(), meaning every player in a multiplayer session can see their own personal quest marker on their compass ribbon simultaneously.

BP/scripts/waypoint_compass.js
import { world, system } from "@minecraft/server";

const BASE_RIBBON = "N · · · · NE· · · · E · · · · SE· · · · S · · · · SW· · · · W · · · · NW· · · · ";
const TARGET_POS = { x: 0, z: 0 };

function yawToBearing(yaw) {
  let b = (yaw + 180) % 360;
  return b < 0 ? b + 360 : b;
}

system.runInterval(() => {
  for (const player of world.getAllPlayers()) {
    const loc = player.location;
    const rot = player.getRotation();

    // Calculate yaw from player to target (0, 0)
    const dx = TARGET_POS.x - loc.x;
    const dz = TARGET_POS.z - loc.z;
    const targetYaw = Math.atan2(-dx, dz) * (180 / Math.PI);

    const playerBearing = yawToBearing(rot.y);
    const targetBearing = yawToBearing(targetYaw);

    const targetIndex = Math.round(targetBearing / 5) % 72;

    // Inject a diamond marker into a copy of the ribbon at the target's bearing
    const ribbonChars = BASE_RIBBON.split("");
    ribbonChars[targetIndex] = "♦";
    const customRibbon = ribbonChars.join("");
    const triple = customRibbon + customRibbon + customRibbon;

    const centerIdx = (Math.round(playerBearing / 5) % 72) + 72;
    const view = triple.substring(centerIdx - 12, centerIdx + 13);

    const dist = Math.round(Math.hypot(dx, dz));
    player.onScreenDisplay.setActionBar(`§7< ${view} > §a♦ ${dist}m`);
  }
}, 2);

Which versions this applies to

The pure command and mcfunction recipe works in all Bedrock versions from 1.16.0 onward with no experimental toggles required. The Script API recipe uses stable APIs (world.getAllPlayers, getRotation, onScreenDisplay.setActionBar, system.runInterval) available in @minecraft/server 1.4.0 and newer (Bedrock 1.20.10+) without needing Beta APIs enabled.

When it does not work

What you seeWhyThe fix
Compass stops updating when the player looks due North, or flickers an old direction.You used a single selector like @a[rym=158,ry=-158] for North. In Bedrock selectors, rym (minimum) cannot be numerically larger than ry (maximum); the selector simply matches zero entities.Split the North check into two separate commands: one for positive angles (@a[rym=158,ry=180]) and one for negative angles (@a[rym=-180,ry=-158]).
Syntax error: Unexpected ":" at "actionbar >>{"rawtext"<<" in the function log.You used the regular /title command instead of /titleraw while passing a JSON rawtext object, or you have unescaped quotes inside your JSON string.Change the command from title to titleraw whenever you pass {"rawtext":[...]}, as the standard title command expects plain unquoted text.
The actionbar compass jitter-shifts left and right as the ribbon scrolls in the Script API version.Minecraft's default font is proportional, meaning characters like 'W' and 'M' are wider in pixels than '.' or 'I', and bold formatting (§l) adds extra pixel width to the center character.Avoid toggling bold (§l) on single characters inside a scrolling substring, and use middle dots (·) or dashes (-) which have closer pixel widths to uppercase letters than standard periods.

Reference, straight from the game files

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