267 lines
8.9 KiB
Rust
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;
|
|
}
|
|
}
|