Skip to content
Snippets Groups Projects
camera.rs 4.29 KiB
Newer Older
Louis's avatar
Louis committed
use bevy::app::App;
use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::math::{vec3, Vec2, Vec3Swizzles};
Louis's avatar
Louis committed
use bevy::prelude::{
	Camera2dBundle, Commands, Component, CoreStage, Entity, EventReader, Input, Local, MouseButton,
	OrthographicProjection, Plugin, Projection, Query, Res, Transform, Windows, With, Without,
Louis's avatar
Louis committed
};
use bevy::render::camera::ScalingMode;
Louis's avatar
Louis committed
use iyes_loopless::prelude::{AppLooplessStateExt, ConditionSet};
Louis's avatar
Louis committed

use crate::system::flow::AppState;
use crate::system::load_config::virtual_size;
use crate::system::utilities::{f32_max, f32_min};
Louis's avatar
Louis committed
use crate::system::window::WindowManager;
use crate::world::MapQuery;
Louis's avatar
Louis committed

/// A flag component to indicate which entity should be followed by the camera
#[derive(Component)]
pub struct ChaseCam;
/// A flag component to indicate a camera that should be used for rendering world entities and sprites
#[derive(Component)]
pub struct GameCamera;

/// System that creates a default orthographic camera, with correct tags for querying
pub fn spawn_orthographic_camera(mut commands: Commands) {
	let (target_width, target_height) = virtual_size();
	commands.spawn((
		Camera2dBundle {
			projection: OrthographicProjection {
				left: -(target_width / 2.0),
				right: (target_width / 2.0),
				top: (target_height / 2.0),
				bottom: -(target_height / 2.0),
				scaling_mode: ScalingMode::Auto {
					min_width: target_width,
					min_height: target_height,
				},
				..Default::default()
			},
			..Default::default()
		},
		GameCamera,
	));
}

Louis's avatar
Louis committed
pub fn pan_camera(
	mut ev_motion: EventReader<MouseMotion>,
	mut ev_scroll: EventReader<MouseWheel>,
Louis's avatar
Louis committed
	input_mouse: Res<Input<MouseButton>>,
	mut query: Query<(&mut Transform, &OrthographicProjection), With<GameCamera>>,
Louis's avatar
Louis committed
	windows: Res<Windows>,
	mut last_mouse_pos: Local<Vec2>,
Louis's avatar
Louis committed
) {
	let mut pan = Vec2::ZERO;

	if let Some(mouse) = windows
		.get_primary()
		.and_then(|window| window.cursor_position())
	{
		let previous_mouse = std::mem::replace(&mut *last_mouse_pos, mouse);

		let mouse_diff = mouse - previous_mouse;
Louis's avatar
Louis committed

		if input_mouse.pressed(MouseButton::Right) {
			for ev in ev_motion.iter() {
				pan += ev.delta;
			}

			if mouse_diff != Vec2::ZERO && mouse_diff.x < 50.0 && mouse_diff.y < 50.0 {
				for (mut transform, projection) in &mut query {
					transform.translation -= mouse_diff.extend(0.0);
				}
			}
		}
Louis's avatar
Louis committed
/// System that takes the average location of all chase camera entities, and updates the location
/// of all world cameras to track the average location.
///
/// e.g. If a player entity is chased, and a mouse tracking entity is chased, the world cameras will
/// by updated to the midpoint between the player and the mouse
pub fn sync_chase_camera_location(
	mut commands: Commands,
	chased_query: Query<&Transform, (With<ChaseCam>, Without<GameCamera>)>,
	mut camera_query: Query<
		(Entity, &mut Transform, &OrthographicProjection),
		(With<GameCamera>, Without<ChaseCam>),
	>,
	map_query: MapQuery,
Louis's avatar
Louis committed
) {
Louis's avatar
Louis committed
	if chased_query.is_empty() {
		return;
	}

Louis's avatar
Louis committed
	let mut average_location = Vec2::new(0.0, 0.0);
	let mut count = 0;
	for location in chased_query.iter() {
		average_location += location.translation.xy();
		count += 1;
	}

	if count > 0 {
		average_location /= count as f32;
	}

	for (_, mut location, proj) in camera_query.iter_mut() {
		if let Some(bounds) = map_query.get_camera_bounds() {
			// log::info!("BOUNDS {:?}", bounds);

			let width = proj.right - proj.left;
			let height = proj.top - proj.bottom;

			let val_x = f32_max(
				bounds.get_min_x(width),
				f32_min(bounds.get_max_x(width), average_location.x),
			);
			let val_y = f32_max(
				bounds.get_min_y(height),
				f32_min(bounds.get_max_y(height), average_location.y),
			);

			location.translation = vec3(val_x, val_y, location.translation.z);
		} else {
			location.translation = average_location.extend(location.translation.z);
		}
Louis's avatar
Louis committed
	}
}

/// A marker struct for spawning and managing cameras. Cameras will be created on startup, and
/// will constantly have their positions synced
pub struct CameraManagementPlugin;

impl Plugin for CameraManagementPlugin {
	fn build(&self, app: &mut App) {
		app.add_enter_system(AppState::Preload, spawn_orthographic_camera)
Louis's avatar
Louis committed
			.add_system_to_stage(CoreStage::PreUpdate, sync_chase_camera_location)
			.add_system_set(
				ConditionSet::new()
					.run_in_state(AppState::InGame)
					.with_system(pan_camera)
					.into(),
			);
Louis's avatar
Louis committed
	}
}