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, STARGUIDE_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<AssetServer>,
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<StarfieldBack>,
Without<StarfieldMid>,
Without<StarfieldFront>,
),
>,
mut starfield_mid: Query<
(Entity, &mut Sprite),
(
With<StarfieldMid>,
Without<StarfieldBack>,
Without<StarfieldFront>,
),
>,
mut starfield_front: Query<
(Entity, &mut Sprite),
(
With<StarfieldFront>,
Without<StarfieldBack>,
Without<StarfieldMid>,
),
>,
assets: Res<Assets<Image>>,
mut asset_events: MessageReader<AssetEvent<Image>>,
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<StarfieldBack>,
Without<StarfieldMid>,
Without<StarfieldFront>,
),
>,
mut starfield_mid: Query<
&mut Sprite,
(
With<StarfieldMid>,
Without<StarfieldBack>,
Without<StarfieldFront>,
),
>,
mut starfield_front: Query<
&mut Sprite,
(
With<StarfieldFront>,
Without<StarfieldBack>,
Without<StarfieldMid>,
),
>,
mut resize_event: MessageReader<WindowResized>,
camera: Single<&Transform, With<MainCamera>>,
) {
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;
};
/*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) * camera.scale.z
+ Vec2::splat(BACK_STARFIELD_SIZE * 2.0),
);
starfield_mid.custom_size = Some(
Vec2::new(event.width, event.height) * camera.scale.z
+ Vec2::splat(MID_STARFIELD_SIZE * 2.0),
);
starfield_front.custom_size = Some(
Vec2::new(event.width, event.height) * camera.scale.z
+ 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<StarfieldBack>,
Without<Me>,
Without<StarfieldMid>,
Without<StarfieldFront>,
),
>,
mut starfield_mid: Query<
&mut Transform,
(
With<StarfieldMid>,
Without<Me>,
Without<StarfieldBack>,
Without<StarfieldFront>,
),
>,
mut starfield_front: Query<
&mut Transform,
(
With<StarfieldFront>,
Without<Me>,
Without<StarfieldBack>,
Without<StarfieldMid>,
),
>,
window: Single<&Window>,
camera: Single<
&Transform,
(
With<MainCamera>,
Without<Me>,
Without<StarfieldFront>,
Without<StarfieldMid>,
Without<StarfieldBack>,
),
>,
player: Query<&Transform, (With<Me>, Without<StarfieldFront>)>,
) {
let Some(player) = player.iter().next() 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) * camera.scale.z
/ 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) * camera.scale.z
/ 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) * camera.scale.z
/ 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);
}