moonlit/src/game/camera.rs

267 lines
8.9 KiB
Rust

use crate::util::{move_towards_vec3, vec3_lerp};
use bevy::input::mouse::{MouseScrollUnit, MouseWheel};
use bevy::prelude::*;
use bevy::render::camera::ScalingMode;
use bevy::{input::mouse::MouseMotion, transform::TransformSystem};
use bevy_ecs_ldtk::prelude::*;
use super::darkness::{PointLight2D, ShadowMesh, SpotLight2D};
pub struct GameCameraPlugin;
impl Plugin for GameCameraPlugin {
fn build(&self, app: &mut App) {
app.configure_set(
PostUpdate,
CameraSystem.before(TransformSystem::TransformPropagate),
)
.register_type::<CameraFollow>()
.register_type::<GameCamera>()
.add_systems(Startup, camera_setup)
.add_systems(Update, control_light)
.add_systems(
PostUpdate,
(follow_detach_system, follow_system, free_system).in_set(CameraSystem),
)
.add_systems(
PostUpdate,
room_restraint
.after(CameraSystem)
.before(TransformSystem::TransformPropagate),
);
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, SystemSet)]
pub struct CameraSystem;
#[derive(Clone, Copy, PartialEq, Reflect)]
pub enum FollowMovement {
Instant,
Linear(f32),
Smooth(f32),
}
impl Default for FollowMovement {
fn default() -> Self {
Self::Instant
}
}
#[derive(Default, Reflect, PartialEq)]
pub enum CameraMode {
#[default]
Free,
Follow,
}
#[derive(Default, Component, Reflect)]
#[reflect(Component)]
pub struct GameCamera {
pub mode: CameraMode,
}
#[derive(Default, Component, Reflect)]
#[reflect(Component)]
pub struct CameraFollow {
pub target: Option<Entity>,
pub movement: FollowMovement,
}
#[derive(Default, Component)]
pub struct CameraRoomRestraint;
fn camera_setup(mut commands: Commands) {
commands.spawn((
Name::new("Game Camera"),
Camera2dBundle {
projection: OrthographicProjection {
scaling_mode: ScalingMode::AutoMax {
max_width: 256.0,
max_height: 160.0,
},
..default()
},
camera_2d: Camera2d {
clear_color: bevy::core_pipeline::clear_color::ClearColorConfig::Custom(
Color::rgb(0.0, 0.0, 0.0),
),
},
transform: Transform::from_xyz(128.0, -73.0, 999.9),
..default()
},
GameCamera::default(),
Visibility::default(),
ComputedVisibility::default(),
CameraRoomRestraint,
PointLight2D { radius: 30.0 },
ShadowMesh::default(),
));
}
fn follow_detach_system(
mouse_input: Res<Input<MouseButton>>,
mut mouse_events: EventReader<MouseMotion>,
mut camera_query: Query<&mut GameCamera>,
) {
let raw_mouse_motion: Vec2 = mouse_events.iter().map(|e| e.delta).sum();
for mut camera in camera_query.iter_mut() {
if camera.mode != CameraMode::Free
&& mouse_input.pressed(MouseButton::Middle)
&& raw_mouse_motion != Vec2::ZERO
{
camera.mode = CameraMode::Free
}
}
}
fn free_system(
mut free_query: Query<(&mut Transform, &GameCamera, &OrthographicProjection)>,
mut mouse_events: EventReader<MouseMotion>,
mouse_input: Res<Input<MouseButton>>,
key_input: Res<Input<KeyCode>>,
time: Res<Time>,
) {
let raw_mouse_motion: Vec2 = mouse_events.iter().map(|e| e.delta).sum();
for (mut transform, camera, projection) in free_query.iter_mut() {
if camera.mode == CameraMode::Free {
let mut motion = Vec2::ZERO;
if mouse_input.pressed(MouseButton::Middle) {
motion += raw_mouse_motion * projection.scale * Vec2::new(-1.0, 1.0);
}
motion.x += match (key_input.pressed(KeyCode::A), key_input.pressed(KeyCode::D)) {
(true, false) => -1.0,
(false, true) => 1.0,
(_, _) => 0.0,
} * projection.scale
* time.delta_seconds()
* 100.0;
motion.y += match (key_input.pressed(KeyCode::S), key_input.pressed(KeyCode::W)) {
(true, false) => -1.0,
(false, true) => 1.0,
(_, _) => 0.0,
} * projection.scale
* time.delta_seconds()
* 100.0;
if motion != Vec2::ZERO {
input_movement(&mut transform, motion)
}
}
}
}
fn follow_system(
time: Res<Time>,
follow_query: Query<(Entity, &CameraFollow, Option<&GameCamera>)>,
mut transform_query: Query<&mut Transform>,
) {
for (entity, follow, camera) in follow_query.iter() {
if camera.map_or(true, |cam| cam.mode == CameraMode::Follow) {
if let Some(target_entity) = follow.target {
let target = transform_query
.get(target_entity)
.ok()
.map_or(Vec3::ZERO, |transform| transform.translation);
let target = Vec3 { z: 999.9, ..target };
if let Ok(mut camera_transform) = transform_query.get_mut(entity) {
follow_movement(
&mut camera_transform,
&follow.movement,
target,
time.delta_seconds(),
)
}
}
}
}
}
fn room_restraint(
mut camera_query: Query<(&mut Transform, &OrthographicProjection), With<CameraRoomRestraint>>,
level_query: Query<(&GlobalTransform, &Handle<LdtkLevel>), Without<OrthographicProjection>>,
level_selection: Res<LevelSelection>,
ldtk_levels: Res<Assets<LdtkLevel>>,
) {
for (mut camera_transform, projection) in camera_query.iter_mut() {
for (level_transform, level_handle) in &level_query {
if let Some(ldtk_level) = ldtk_levels.get(level_handle) {
let level = &ldtk_level.level;
if level_selection.is_match(&0, level) {
let top = camera_transform.translation.y + projection.area.max.y;
let bottom = camera_transform.translation.y + projection.area.min.y;
let left = camera_transform.translation.x + projection.area.min.x;
let right = camera_transform.translation.x + projection.area.max.x;
let top_limit = level_transform.translation().y + level.px_hei as f32;
let bottom_limit = level_transform.translation().y;
let left_limit = level_transform.translation().x;
let right_limit = level_transform.translation().x + level.px_wid as f32;
let top_move = (top_limit - top).min(0.0);
let bottom_move = (bottom_limit - bottom).max(0.0);
let left_move = (left_limit - left).max(0.0);
let right_move = (right_limit - right).min(0.0);
// Move camera back to room
camera_transform.translation.x += left_move + right_move;
camera_transform.translation.y += top_move + bottom_move;
// Center camera on room if room is too small
if top - bottom > level.px_hei as f32 {
camera_transform.translation.y = (top_limit + bottom_limit) / 2.0;
}
if right - left > level.px_wid as f32 {
camera_transform.translation.x = (right_limit + left_limit) / 2.0;
}
}
}
}
}
}
fn follow_movement(
transform: &mut Transform,
movement: &FollowMovement,
target: Vec3,
delta_time: f32,
) {
match movement {
FollowMovement::Instant => {
transform.translation = target;
}
FollowMovement::Linear(speed) => {
transform.translation =
move_towards_vec3(transform.translation, target, speed * delta_time);
}
FollowMovement::Smooth(speed) => {
transform.translation =
vec3_lerp(transform.translation, target, (speed * delta_time).min(1.0));
}
}
}
fn input_movement(transform: &mut Transform, movement: Vec2) {
transform.translation += movement.extend(0.0);
}
fn control_light(
mut mouse_scroll_events: EventReader<MouseWheel>,
mut point_light_query: Query<&mut PointLight2D>,
mut spot_light_query: Query<&mut SpotLight2D>,
) {
let mut scrolling = 0.0;
for event in mouse_scroll_events.iter() {
match event.unit {
MouseScrollUnit::Line => scrolling += event.y * 0.05,
MouseScrollUnit::Pixel => scrolling += event.y * 0.025,
}
}
for mut point_light in point_light_query.iter_mut() {
point_light.radius *= 1.0 + scrolling;
}
for mut spot_light in spot_light_query.iter_mut() {
spot_light.radius *= 1.0 + scrolling;
}
}