use crate::client::Me; use crate::config::planet::Planet; use crate::ecs::MainCamera; use bevy::prelude::*; use bevy::window::PrimaryWindow; pub fn indicators_plugin(app: &mut App) { app.add_systems(PreUpdate, (add_indicators, update_indicators)) .add_systems(PostUpdate, update_indicators_position); } #[derive(Component)] struct PlanetIndicator; #[derive(Component)] struct HasIndicator(Entity); fn add_indicators( planets_wo_indicators: Query<(Entity, &Planet), Without>, player: Query>, asset_server: Res, mut commands: Commands, ) { let Ok(me) = player.single() else { return; }; for (planet, planet_data) in &planets_wo_indicators { let Some(indicator_url) = &planet_data.indicator_sprite else { continue; }; let mut sprite = Sprite::from_image(asset_server.load(indicator_url)); sprite.custom_size = Some(Vec2::new(25.0, 25.0)); let indicator = commands .spawn(( ChildOf(me), PlanetIndicator, sprite, Transform::from_xyz(0.0, 0.0, 0.0), )) .id(); commands.entity(planet).insert(HasIndicator(indicator)); } } fn update_indicators( changed_planets_w_indicators: Query<(&Planet, &HasIndicator), Changed>, asset_server: Res, mut commands: Commands, ) { for (planet_data, indicator) in changed_planets_w_indicators.iter() { let Some(indicator_sprite) = &planet_data.indicator_sprite else { continue; }; let mut sprite = Sprite::from_image(asset_server.load(indicator_sprite)); sprite.custom_size = Some(Vec2::new(50.0, 50.0)); commands .entity(indicator.0) .remove::() .insert(sprite); } } fn update_indicators_position( planets_w_indicator: Query<(&Transform, &HasIndicator), Without>, player: Query<&Transform, (With, Without)>, mut indicators: Query< (&mut Transform, &mut Sprite), ( With, Without, Without, Without, ), >, window: Query<&Window, With>, camera: Single<&Transform, (With, Without)>, ) { let Ok(player_position) = player.single() else { return; }; let Ok(window) = window.single() else { return }; for (planet_position, indicator_id) in &planets_w_indicator { let mut offset = planet_position.translation - player_position.translation; let sprite_size = 32.0 * camera.scale.z; let half_window_height = window.height() * camera.scale.z / 2.0 - (sprite_size / 2.0); let half_window_width = window.width() * camera.scale.z / 2.0 - (sprite_size / 2.0); // scale both parts to fit x, then same for y if offset.x.abs() > half_window_width { let scale_factor = offset.x.clamp(-half_window_width, half_window_width) / offset.x; offset *= scale_factor; } if offset.y.abs() > half_window_height { let scale_factor = offset.y.clamp(-half_window_height, half_window_height) / offset.y; offset *= scale_factor; } let Ok((mut this_indicator, mut this_sprite)) = indicators.get_mut(indicator_id.0) else { continue; }; this_sprite.custom_size = Some(Vec2::splat(sprite_size)); let inv_rot = player_position.rotation.inverse(); this_indicator.translation = inv_rot.mul_vec3(Vec3::new(offset.x, offset.y, 0.0)); this_indicator.rotation = inv_rot; } }