Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Bevy 0.14 Cursor Pitfalls: Complete Log

Environment: Bevy 0.14.2 + macOS Retina

Cursor system pitfalls encountered during development of the rust-bevy-mighty-rodent project, logged by discovery order.

Pitfall 1: System Cursor Won’t Hide

Symptom: Custom sprite cursor renders, but the macOS default cursor is still visible — two cursors overlap.

Root cause: Bevy 0.14’s CursorIcon enum has no None variant. You can’t hide the system cursor via Cursor::icon.

Fix: Set Cursor.visible = false:

#![allow(unused)]
fn main() {
cursor: Cursor {
    visible: false,
    icon: CursorIcon::Default,
    ..default()
},
}

Pitfall 2: Cursor Sprite Renders but Is Invisible

Symptom: The cursor sprite spawns (z=100) and logs confirm it follows the mouse, but it’s not visible in the game window.

Root cause: Bevy’s UI layer renders independently on top of all 2D sprites. A SpriteBundle at z=100 sits within the 2D sprite layer, but the UI layer (NodeBundle menus) covers everything.

Fix: Use ImageBundle (UI node) with ZIndex::Global(999):

#![allow(unused)]
fn main() {
commands.spawn((
    ImageBundle {
        style: Style {
            position_type: PositionType::Absolute,
            width: Val::Px(64.0),
            height: Val::Px(64.0),
            ..default()
        },
        image: UiImage::new(cursor_handle),
        z_index: ZIndex::Global(999),
        ..default()
    },
    CursorSprite,
));
}

Pitfall 3: CursorMoved Doesn’t Update UI Position

Fix: Update Style.left and Style.top from CursorMoved events.

Pitfall 4: macOS Retina Pixel Ratio

Symptom: Cursor position is off on Retina displays — the cursor reaches only half the screen.

Root cause: CursorMoved returns physical pixels, but UI Val::Px uses logical pixels. Divide by scale_factor:

#![allow(unused)]
fn main() {
let scale = window.scale_factor();
style.left = Val::Px(event.position.x / scale);
style.top = Val::Px(event.position.y / scale);
}

Pitfall 5: Cursor Disappears on Scene Transition

Fix: Use global entities (not scene-scoped) or re-spawn the cursor on scene entry.

Summary

PitfallSymptomRoot CauseFix
1Two cursorsCursorIcon has no NoneCursor.visible=false
2Sprite invisibleUI layer covers spritesImageBundle + ZIndex::Global
3Position stuckNot updating UI StyleUpdate Style.left/top
4Retina offsetPhysical vs logical pixelsDivide by scale_factor
5Lost on scene switchScene despawns entitiesRe-spawn or keep global