use crate::entities::motion::Velocity; use crate::entities::{BoxSize, CollisionGroup, Player}; use crate::system::{Background, DawnBringerPalette, BACKGROUND_HEIGHT, BACKGROUND_WIDTH}; use crate::utilities::translate_rect; use bevy::ecs::system::SystemParam; use bevy::math::Vec3Swizzles; use bevy::prelude::{ AssetServer, Bundle, Changed, Commands, Component, DespawnRecursiveExt, Entity, OrthographicProjection, Query, Rect, Res, Sprite, SpriteBundle, Transform, Vec2, }; use bevy_prototype_lyon::draw::Stroke; use bevy_prototype_lyon::prelude::{Fill, GeometryBuilder, ShapeBundle}; use bevy_prototype_lyon::shapes; static Z_PLAYER: f32 = 300.0; static Z_ITEMS: f32 = 200.0; static Z_ENEMY: f32 = 250.0; static Z_BACKGROUND: f32 = 50.0; #[derive(Copy, Clone, Debug, Component)] pub struct DespawnOffScreen { left: bool, right: bool, top: bool, bottom: bool, } impl Default for DespawnOffScreen { fn default() -> Self { Self { left: true, right: true, top: true, bottom: true, } } } impl DespawnOffScreen { pub fn all() -> Self { Self::default() } pub fn right() -> Self { Self { left: false, right: true, top: false, bottom: false, } } pub fn left() -> Self { Self { left: true, right: false, top: false, bottom: false, } } pub fn entity_should_despawn(&self, entity_pos: Vec2, camera_bounds: Rect) -> bool { self.left && entity_pos.x < camera_bounds.min.x || self.top && entity_pos.y > camera_bounds.max.y || self.right && entity_pos.x > camera_bounds.max.x || self.bottom && entity_pos.y < camera_bounds.min.y } } #[derive(SystemParam)] pub struct EntitySpawner<'w, 's> { commands: Commands<'w, 's>, assets: Res<'w, AssetServer>, } impl<'w, 's> EntitySpawner<'w, 's> { pub fn spawn_player_at(&mut self, position: Vec2) -> Entity { self.commands .spawn(( SpriteBundle { texture: self.assets.load("sprites/ship.png"), transform: Transform::from_translation(position.extend(Z_PLAYER)), ..Default::default() }, Velocity::ZERO, Player, )) .id() } pub fn spawn_background_at(&mut self, position: Vec2) -> Entity { self.commands .spawn(( SpriteBundle { texture: self.assets.load("sprites/background.png"), transform: Transform::from_translation(position.extend(Z_BACKGROUND)), ..Default::default() }, Velocity::from(Vec2::new(-75.0, 0.0)), Background, BoxSize::from(Vec2::new(BACKGROUND_WIDTH, BACKGROUND_HEIGHT)), )) .id() } pub fn spawn_alien_at(&mut self, position: Vec2) -> Entity { self.commands .spawn(( SpriteBundle { texture: self.assets.load("sprites/alien_ship.png"), transform: Transform::from_translation(position.extend(Z_ENEMY)), sprite: Sprite { flip_x: true, ..Default::default() }, ..Default::default() }, BoxSize::from(Vec2::new(50.0, 25.0)), CollisionGroup::Enemy, )) .id() } pub fn spawn_projectile( &mut self, position: Vec2, velocity_direction: Vec2, rest: impl Bundle, ) -> Entity { let projectile_shape = shapes::RoundedPolygon { radius: 2.0, closed: true, points: vec![ Vec2::new(-7.0, -2.0), Vec2::new(-7.0, 2.0), Vec2::new(7.0, 2.0), Vec2::new(7.0, -2.0), ], }; self.commands .spawn(( ShapeBundle { path: GeometryBuilder::build_as(&projectile_shape), transform: Transform::from_translation(position.extend(Z_ITEMS)), ..Default::default() }, BoxSize::from(Vec2::new(10.0, 10.0)), Fill::color(DawnBringerPalette::MID_BLUE), Stroke::new(DawnBringerPalette::MIDNIGHT_BLUE, 2.0), Velocity::from(Vec2::new(100.0, 0.0) * velocity_direction), rest, )) .id() } } pub fn apply_despawn_boundaries( mut commands: Commands, camera_query: Query<(&OrthographicProjection, &Transform)>, bounds_query: Query<(Entity, &DespawnOffScreen, &Transform), Changed<Transform>>, ) { let (camera_ortho, camera_position) = match camera_query.get_single() { Ok(value) => value, Err(_) => return, }; let viewport = translate_rect(camera_ortho.area, camera_position.translation.xy()); for (entity, boundary, transform) in &bounds_query { if boundary.entity_should_despawn(transform.translation.xy(), viewport) { commands.entity(entity).despawn_recursive(); } } }