//! Illustrates how to use a custom cursor image with a texture atlas and //! animation. use std::time::Duration; use bevy::{ prelude::*, winit::cursor::{CursorIcon, CustomCursor, CustomCursorImage}, }; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems( Startup, (setup_cursor_icon, setup_camera, setup_instructions), ) .add_systems( Update, ( execute_animation, toggle_texture_atlas, toggle_flip_x, toggle_flip_y, cycle_rect, ), ) .run(); } fn setup_cursor_icon( mut commands: Commands, asset_server: Res, mut texture_atlas_layouts: ResMut>, window: Single>, ) { let layout = TextureAtlasLayout::from_grid(UVec2::splat(64), 20, 10, Some(UVec2::splat(5)), None); let texture_atlas_layout = texture_atlas_layouts.add(layout); let animation_config = AnimationConfig::new(0, 199, 1, 4); commands.entity(*window).insert(( CursorIcon::Custom(CustomCursor::Image(CustomCursorImage { // Image to use as the cursor. handle: asset_server .load("cursors/kenney_crosshairPack/Tilesheet/crosshairs_tilesheet_white.png"), // Optional texture atlas allows you to pick a section of the image // and animate it. texture_atlas: Some(TextureAtlas { layout: texture_atlas_layout.clone(), index: animation_config.first_sprite_index, }), flip_x: false, flip_y: false, // Optional section of the image to use as the cursor. rect: None, // The hotspot is the point in the cursor image that will be // positioned at the mouse cursor's position. hotspot: (0, 0), })), animation_config, )); } fn setup_camera(mut commands: Commands) { commands.spawn(Camera3d::default()); } fn setup_instructions(mut commands: Commands) { commands.spawn(( Text::new( "Press T to toggle the cursor's `texture_atlas`.\n Press X to toggle the cursor's `flip_x` setting.\n Press Y to toggle the cursor's `flip_y` setting.\n Press C to cycle through the sections of the cursor's image using `rect`.", ), Node { position_type: PositionType::Absolute, bottom: Val::Px(12.0), left: Val::Px(12.0), ..default() }, )); } #[derive(Component)] struct AnimationConfig { first_sprite_index: usize, last_sprite_index: usize, increment: usize, fps: u8, frame_timer: Timer, } impl AnimationConfig { fn new(first: usize, last: usize, increment: usize, fps: u8) -> Self { Self { first_sprite_index: first, last_sprite_index: last, increment, fps, frame_timer: Self::timer_from_fps(fps), } } fn timer_from_fps(fps: u8) -> Timer { Timer::new(Duration::from_secs_f32(1.0 / (fps as f32)), TimerMode::Once) } } /// This system loops through all the sprites in the [`CursorIcon`]'s /// [`TextureAtlas`], from [`AnimationConfig`]'s `first_sprite_index` to /// `last_sprite_index`. fn execute_animation(time: Res