use std::ops::Deref; use std::time::Duration; use bevy::{ color::palettes::css as csscolors, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, math::Vec3Swizzles, prelude::*, window::PrimaryWindow, }; use bevy_spatial::{AutomaticUpdate, SpatialStructure}; use bevy_spatial::{SpatialAccess, kdtree::KDTree2}; // marker for entities tracked by the KDTree #[derive(Component, Default)] struct NearestNeighbourComponent; // marker for the "cursor" entity #[derive(Component)] struct Cursor; fn main() { App::new() .add_plugins(DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { present_mode: bevy::window::PresentMode::AutoNoVsync, ..default() }), ..default() })) // Add the plugin, which takes the tracked component as a generic. .add_plugins( AutomaticUpdate::::new() .with_spatial_ds(SpatialStructure::KDTree2) .with_frequency(Duration::from_millis(1)), ) .add_plugins(LogDiagnosticsPlugin::default()) .add_plugins(FrameTimeDiagnosticsPlugin::default()) .insert_resource(Mouse2D { pos: Vec2::ZERO }) .add_systems(Startup, setup) .add_systems( Update, ( update_mouse_pos, ( mouse, color, reset_color.before(color), collide_wall, movement, ), ) .chain(), ) .run(); } // type alias for easier usage later type NNTree = KDTree2; fn setup(mut commands: Commands) { commands.spawn(Camera2d); commands.spawn(( Cursor, Sprite { color: Color::srgb(0.0, 0.0, 1.0), custom_size: Some(Vec2::new(10.0, 10.0)), ..default() }, Transform { translation: Vec3::ZERO, ..default() }, )); let sprite = Sprite { color: csscolors::ORANGE_RED.into(), custom_size: Some(Vec2::new(6.0, 6.0)), ..default() }; for x in -100..100 { for y in -100..100 { commands.spawn(( NearestNeighbourComponent, sprite.clone(), Transform { translation: Vec3::new((x * 4) as f32, (y * 4) as f32, 0.0), ..default() }, )); } } } #[derive(Copy, Clone, Resource)] struct Mouse2D { pos: Vec2, } fn update_mouse_pos( window: Single<&Window, With>, camera: Single<(&Camera, &GlobalTransform)>, mut mouse: ResMut, ) { let (cam, cam_t) = camera.deref(); if let Some(w_pos) = window.cursor_position() { if let Ok(pos) = cam.viewport_to_world_2d(cam_t, w_pos) { mouse.pos = pos; } } } fn mouse( mut commands: Commands, mouse: Res, treeaccess: Res, mut transform: Single<&mut Transform, With>, ms_buttons: Res>, ) { let use_mouse = ms_buttons.pressed(MouseButton::Left); if let Some((_pos, entity)) = treeaccess.nearest_neighbour(mouse.pos) { transform.translation = mouse.pos.extend(0.0); // I don't really know what this is here for if use_mouse { commands.entity(entity.unwrap()).despawn(); } } } fn color( treeaccess: Res, mouse: Res, mut query: Query<&mut Sprite, With>, ) { for (_, entity) in treeaccess.within_distance(mouse.pos, 50.0) { if let Ok(mut sprite) = query.get_mut(entity.unwrap()) { sprite.color = Color::BLACK; } } } fn reset_color(mut query: Query<&mut Sprite, With>) { for mut sprite in &mut query { sprite.color = csscolors::ORANGE_RED.into(); } } fn movement(mut query: Query<&mut Transform, With>) { for mut pos in &mut query { let goal = pos.translation - Vec3::ZERO; pos.translation += goal.normalize_or_zero(); } } fn collide_wall( window: Single<&Window, With>, mut query: Query<&mut Transform, With>, ) { let w = window.width() / 2.0; let h = window.height() / 2.0; for mut pos in &mut query { let [x, y] = pos.translation.xy().to_array(); if y < -h || x < -w || y > h || x > w { pos.translation = pos.translation.normalize_or_zero(); } } }