use bevy::{ app::{App, Startup, Update}, asset::{AssetEvent, AssetServer, Assets}, ecs::{ //entity::Entity, //entity_disabling::Disabled, query::{With, Without}, system::{Commands, Query, Res, Single}, }, image::Image, log::warn, math::{Vec2, Vec3}, prelude::*, sprite::{Sprite, SpriteImageMode}, transform::components::Transform, window::{Window, WindowResized}, }; use crate::{ ecs::Me, ecs::{MainCamera, MAIN_LAYER, StarfieldBack, StarfieldFront, StarfieldMid}, }; pub const BACK_STARFIELD_SIZE: f32 = 256.0; pub const MID_STARFIELD_SIZE: f32 = 384.0; pub const FRONT_STARFIELD_SIZE: f32 = 512.0; #[derive(Component, Clone, Copy, Debug)] pub struct StarfieldSize(pub f32); pub fn starfield_plugin(app: &mut App) { app.add_systems(Startup, set_up_starfield) .add_systems(Update, fix_starfield) .add_systems(Update, resize_starfield) .add_systems(Update, update_starfield); } pub fn set_up_starfield( mut commands: Commands, asset_server: Res, window: Query<&Window>, ) { let starfield_handle = asset_server.load("textures/starfield.png"); let starfield_transp_handle = asset_server.load("textures/starfield_transp.png"); let window = window.iter().next().unwrap(); commands .spawn(Sprite { image: starfield_handle, custom_size: Some(window.size() + Vec2::splat(BACK_STARFIELD_SIZE)), image_mode: SpriteImageMode::Tiled { tile_x: true, tile_y: true, stretch_value: 1.0, }, ..Default::default() }) .insert(MAIN_LAYER) .insert(Transform::from_xyz(0.0, 0.0, 5.0)) .insert(Visibility::Inherited) .insert(StarfieldBack); commands .spawn(Sprite { image: starfield_transp_handle.clone(), custom_size: Some(window.size() + Vec2::splat(MID_STARFIELD_SIZE)), image_mode: SpriteImageMode::Tiled { tile_x: true, tile_y: true, stretch_value: 1.0, }, ..Default::default() }) .insert(MAIN_LAYER) .insert(Transform::from_xyz(0.0, 0.0, 4.5)) .insert(Visibility::Inherited) .insert(StarfieldMid); commands .spawn(Sprite { image: starfield_transp_handle, custom_size: Some(window.size() + Vec2::splat(FRONT_STARFIELD_SIZE)), image_mode: SpriteImageMode::Tiled { tile_x: true, tile_y: true, stretch_value: 1.0, }, ..Default::default() }) .insert(MAIN_LAYER) .insert(Transform::from_xyz(0.0, 0.0, 4.0)) .insert(Visibility::Inherited) .insert(StarfieldFront); } pub fn fix_starfield( mut starfield_back: Query< (Entity, &mut Sprite), ( With, Without, Without, ), >, mut starfield_mid: Query< (Entity, &mut Sprite), ( With, Without, Without, ), >, mut starfield_front: Query< (Entity, &mut Sprite), ( With, Without, Without, ), >, assets: Res>, mut asset_events: MessageReader>, mut commands: Commands, ) { for event in asset_events.read() { if let AssetEvent::Added { id } = event { let (entity_back, mut starfield_back) = starfield_back.single_mut().unwrap(); if *id == starfield_back.image.id() { let starfield_image = assets.get(*id).unwrap(); let size = BACK_STARFIELD_SIZE / (starfield_image.size().x as f32); starfield_back.image_mode = SpriteImageMode::Tiled { tile_x: true, tile_y: true, stretch_value: size, }; commands.entity(entity_back).insert(StarfieldSize(size)); } let (entity_mid, mut starfield_mid) = starfield_mid.single_mut().unwrap(); if *id == starfield_mid.image.id() { let starfield_image = assets.get(*id).unwrap(); let size = MID_STARFIELD_SIZE / (starfield_image.size().x as f32); starfield_mid.image_mode = SpriteImageMode::Tiled { tile_x: true, tile_y: true, stretch_value: size, }; commands.entity(entity_mid).insert(StarfieldSize(size)); } let (entity_front, mut starfield_front) = starfield_front.single_mut().unwrap(); if *id == starfield_front.image.id() { let starfield_image = assets.get(*id).unwrap(); let size = FRONT_STARFIELD_SIZE / (starfield_image.size().x as f32); starfield_front.image_mode = SpriteImageMode::Tiled { tile_x: true, tile_y: true, stretch_value: size, }; commands.entity(entity_front).insert(StarfieldSize(size)); } } } } pub fn resize_starfield( mut starfield_back: Query< &mut Sprite, ( With, Without, Without, ), >, mut starfield_mid: Query< &mut Sprite, ( With, Without, Without, ), >, mut starfield_front: Query< &mut Sprite, ( With, Without, Without, ), >, mut resize_event: MessageReader, camera: Single<&Projection, With>, ) { for event in resize_event.read() { let Ok(mut starfield_back) = starfield_back.single_mut() else { warn!("{:?}: no such entity!", stringify!(starfield_back)); return; }; let Ok(mut starfield_mid) = starfield_mid.single_mut() else { warn!("{:?}: no such entity!", stringify!(starfield_mid)); return; }; let Ok(mut starfield_front) = starfield_front.single_mut() else { warn!("{:?}: no such entity!", stringify!(starfield_front)); return; }; let Projection::Orthographic(projection) = *camera else { return }; /*if camera.scale.z > 10.0 { // arbitrary // TODO: find out how to disable sprites // done } else { // TODO: find out how to reenable them without toggling on every update :( }*/ starfield_back.custom_size = Some( Vec2::new(event.width, event.height) * projection.scale + Vec2::splat(BACK_STARFIELD_SIZE * 2.0), ); starfield_mid.custom_size = Some( Vec2::new(event.width, event.height) * projection.scale + Vec2::splat(MID_STARFIELD_SIZE * 2.0), ); starfield_front.custom_size = Some( Vec2::new(event.width, event.height) * projection.scale + Vec2::splat(FRONT_STARFIELD_SIZE * 2.0), ); } } macro_rules! fix_negative_field_translations { ($field:ident, $size:expr) => { if $field.translation.y < $size / 2.0 { $field.translation.y -= $size; } if $field.translation.x < $size / 2.0 { $field.translation.x -= $size; } }; } pub fn update_starfield( mut starfield_back: Query< &mut Transform, ( With, Without, Without, Without, ), >, mut starfield_mid: Query< &mut Transform, ( With, Without, Without, Without, ), >, mut starfield_front: Query< &mut Transform, ( With, Without, Without, Without, ), >, window: Single<&Window>, camera: Single< &Projection, ( With, Without, Without, Without, Without, ), >, player: Query<&Transform, (With, Without)>, ) { let Some(player) = player.iter().next() else { return; }; let Projection::Orthographic(projection) = *camera else { return }; let mut starfield_back_pos = starfield_back.single_mut().unwrap(); let mut starfield_mid_pos = starfield_mid.single_mut().unwrap(); let mut starfield_front_pos = starfield_front.single_mut().unwrap(); //starfield_pos.translation = (player.translation / STARFIELD_SIZE).round() * STARFIELD_SIZE; starfield_back_pos.translation = player.translation + (-player.translation / 3.0) % BACK_STARFIELD_SIZE + (Vec3::new(window.resolution.width(), -window.resolution.height(), 0.0) * projection.scale / 2.0) % BACK_STARFIELD_SIZE + Vec3::new(0.0, BACK_STARFIELD_SIZE, 0.0) - Vec3::new(0.0, 0.0, 5.0); starfield_mid_pos.translation = player.translation + (-player.translation / 2.5) % MID_STARFIELD_SIZE + (Vec3::new(window.resolution.width(), -window.resolution.height(), 0.0) * projection.scale / 2.0) % MID_STARFIELD_SIZE + Vec3::new(0.0, MID_STARFIELD_SIZE, 0.0) - Vec3::new(0.0, 0.0, 4.5); starfield_front_pos.translation = player.translation + (-player.translation / 2.0) % FRONT_STARFIELD_SIZE + (Vec3::new(window.resolution.width(), -window.resolution.height(), 0.0) * projection.scale / 2.0) % FRONT_STARFIELD_SIZE + Vec3::new(0.0, FRONT_STARFIELD_SIZE, 0.0) - Vec3::new(0.0, 0.0, 4.0); fix_negative_field_translations!(starfield_back_pos, BACK_STARFIELD_SIZE); fix_negative_field_translations!(starfield_mid_pos, MID_STARFIELD_SIZE); fix_negative_field_translations!(starfield_front_pos, FRONT_STARFIELD_SIZE); }