Skip to content
Snippets Groups Projects
spawning.rs 4.22 KiB
Newer Older
use crate::entities::motion::Velocity;
Louis's avatar
Louis committed
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;
Louis's avatar
Louis committed
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()
	}
Louis's avatar
Louis committed

	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)),
Louis's avatar
Louis committed
			))
			.id()
	}
Louis's avatar
Louis committed

	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();
		}
	}