<anchor>

<anchor> pins a UI subtree to a 3D entity. Every frame it projects the entity's world position through the UI camera and centers itself on that point, so labels, nameplates and health bars follow objects as they and the camera move. It stays an ordinary screen-space node: flat, styled like <node>, and fully interactive.

Usage

React needs the entity's id, Entity::to_bits(), from the Bevy side. Here a request asks for it:

TSX

import { useEffect, useState } from "react";
import { bevy } from "./bevy";

function PlayerLabel() {
  const [player, setPlayer] = useState<bigint | null>(null);
  useEffect(() => {
    bevy.player.entity().then(setPlayer);
  }, []);
  if (player === null) return null;
  return (
    <anchor entity={player} offset={[0, 2, 0]}>
      <text>Player 1</text>
    </anchor>
  );
}

Rust

use bevy::prelude::*;
use bevy_react::prelude::*;

#[derive(Component)]
struct Player;

#[react_request(name = "player.entity", response = u64)]
struct PlayerEntity;

fn player_entity(
    req: On<Request<PlayerEntity>>,
    player: Query<Entity, With<Player>>,
) {
    match player.single() {
        Ok(entity) => req.respond(entity.to_bits()),
        Err(_) => req.respond_err("no player"),
    }
}

// app.add_react_request_handler(player_entity);

An event works as well, for example one listing every spawned unit with its entity.to_bits() (see Bevy to React).

<anchor> comes from the anchor cargo feature, which is on by default and adds AnchorPlugin to ReactPlugins. Without it, an <anchor> mounts as a plain node and reports a featureMissing warning (see Cargo features).

Attributes

Attribute Type Default Effect
entity number or bigint required The entity to follow, as Entity::to_bits()
offset [x, y, z] [0, 0, 0] World-space offset added to the entity's position
scale AnchorScaling none Scale the overlay with camera distance

All three are plain props: change them from React state and the anchor follows the new values.

Positioning

  • The anchor point is the entity's GlobalTransform translation plus offset, in world units. Use offset to lift a label above a model's origin.
  • The point is projected through the camera marked IsDefaultUiCamera, or, if none is marked, the first camera found. Mark your main camera when the app has several, including <portal> cameras and <surface> cameras.
  • The overlay is centered on the point using its own laid-out size, and is positioned after animations and transitions run, so it tracks the entity in the same frame. Moving it never re-runs layout.
  • The overlay is hidden while the entity doesn't exist (not spawned yet, despawned, or an invalid entity value) and while the point is behind the camera or beyond its far plane. A point off to the side of the screen keeps the overlay positioned off-screen. A new anchor is also hidden until its first layout, so it never flashes at the wrong place.

Distance scaling

scale takes { min, max, factor, baseDistance } and scales the overlay around its center by clamp(1 + factor * (baseDistance / distance - 1), min, max), where distance is from the camera to the anchor point, in world units. The overlay is at scale 1 when the camera is baseDistance away, grows as the camera comes closer and shrinks as it moves away. A factor of 0 disables scaling, 1 is true perspective (half the size at twice the distance), and 2 scales twice as fast. Without scale, the overlay keeps its size.

<anchor
  entity={player}
  offset={[0, 2, 0]}
  scale={{ min: 0.4, max: 2, factor: 1, baseDistance: 20 }}
>
  <text>Player 1</text>
</anchor>

A non-finite field disables scaling with a log warning; a min greater than max is swapped.

Layout and styling

  • <anchor> takes everything <node> does: style, hoverStyle, pressStyle, focusStyle, pointer handlers, onWheel and the scroll props. Its children lay out inside it as usual, and it sizes to its content.
  • It doesn't take part in its React parent's layout: it takes no space there, isn't clipped by the parent's overflow and doesn't extend its scroll range. Write it anywhere in the tree; unmounting the parent removes it.
  • Anchors live in their own UI root at the window's top-left, drawn beneath the app's other UI roots. Give an <anchor> a globalZIndex to draw it above them (see Z-index).
  • The anchor owns its position: positionType, left and top are overridden, and so are translateX, translateY, scale, scaleX and scaleY in its transform style, static, transitioned or animated. rotate works. Put other transforms on a child.
  • Clicks and hover work as on any node.

Limits

  • No occlusion: an overlay stays visible when its entity is hidden behind other geometry.
  • Only the position is followed. The overlay doesn't rotate or skew with the entity.
  • Overlays follow one camera and appear only on the main screen, not in portals or surfaces.
  • entity crosses as a JS number, exact below 2^53, which covers realistic Bevy entity ids.

Dozens of colored cubes in a 3D scene, each with a numbered React badge anchored above it that tracks its cube as the camera moves.

See <anchor> in the element reference.