use bevy::prelude::*; use bevy::input::mouse::MouseMotion; use bevy::window::PrimaryWindow; use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings}; use bevy::core_pipeline::tonemapping::Tonemapping; use bevy::pbr::CascadeShadowConfigBuilder; use bevy::transform::TransformSystem; use bevy::math::{DVec3, DQuat}; use bevy_xpbd_3d::prelude::*; use std::f32::consts::PI; use crate::{actor, audio, hud, settings}; pub struct CameraPlugin; impl Plugin for CameraPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup_camera); app.add_systems(Update, handle_input); app.add_systems(Update, manage_player_actor.after(handle_input)); app.add_systems(PostUpdate, sync_camera_to_player .after(PhysicsSet::Sync) .after(apply_input_to_player) .before(TransformSystem::TransformPropagate)); app.add_systems(Update, update_fov); app.add_systems(PostUpdate, apply_input_to_player .after(PhysicsSet::Sync) .before(TransformSystem::TransformPropagate)); } } pub fn setup_camera( mut commands: Commands, ) { // Add player commands.spawn(( Camera3dBundle { camera: Camera { hdr: true, // HDR is required for bloom clear_color: ClearColorConfig::Custom(Color::BLACK), ..default() }, tonemapping: Tonemapping::TonyMcMapface, transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }, BloomSettings { composite_mode: BloomCompositeMode::EnergyConserving, ..default() }, )); // Add Light from the Sun commands.spawn(DirectionalLightBundle { directional_light: DirectionalLight { illuminance: 1000.0, shadows_enabled: false, ..default() }, transform: Transform::from_rotation(Quat::from_rotation_y(PI/2.0)), cascade_shadow_config: CascadeShadowConfigBuilder { first_cascade_far_bound: 7.0, maximum_distance: 25.0, ..default() } .into(), ..default() }); } pub fn sync_camera_to_player( settings: Res, mut q_camera: Query<&mut Transform, (With, Without)>, q_playercam: Query<(&actor::Actor, &Transform), (With, Without)>, ) { if q_camera.is_empty() || q_playercam.is_empty() { return; } let mut camera_transform = q_camera.get_single_mut().unwrap(); let (actor, player_transform) = q_playercam.get_single().unwrap(); // Rotation camera_transform.rotation = player_transform.rotation * Quat::from_array([0.0, -1.0, 0.0, 0.0]); // Translation if settings.third_person { camera_transform.translation = player_transform.translation + camera_transform.rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0)); } else { camera_transform.translation = player_transform.translation; } } pub fn update_fov( q_player: Query<&actor::LifeForm, With>, mouse_input: Res>, mut settings: ResMut, mut q_camera: Query<&mut Projection, With>, ) { if let (Ok(lifeform), Ok(mut projection)) = (q_player.get_single(), q_camera.get_single_mut()) { let fov: f32; if settings.hud_active && mouse_input.pressed(settings.key_zoom) { fov = settings.zoom_fov_radians; if !settings.is_zooming { settings.is_zooming = true; } } else { fov = (lifeform.adrenaline.powf(3.0) * 25.0 + 45.0).to_radians(); if settings.is_zooming { settings.is_zooming = false; } }; *projection = Projection::Perspective(PerspectiveProjection { fov: fov, ..default() }); } } pub fn handle_input( keyboard_input: Res>, mut settings: ResMut, ) { if keyboard_input.just_pressed(settings.key_camera) { settings.third_person ^= true; } } fn manage_player_actor( settings: Res, mut q_playercam: Query<&mut Visibility, With>, mut q_hiddenplayer: Query<(&mut Visibility, &mut Position, &mut Rotation, &mut LinearVelocity, &mut AngularVelocity), (With, Without)>, q_ride: Query<(&Transform, &Position, &Rotation, &LinearVelocity, &AngularVelocity), (With, Without)>, ) { for mut vis in &mut q_playercam { if settings.third_person { *vis = Visibility::Inherited; } else { *vis = Visibility::Hidden; } } for (mut vis, mut pos, mut rot, mut v, mut angv) in &mut q_hiddenplayer { // If we are riding a vehicle, place the player at the position where // it would be after exiting the vehicle. // I would rather place it in the center of the vehicle, but at the time // of writing, I couldn't set the position/rotation of the player *during* // exiting the vehicle, so I'm doing it here instead, as a workaround. *vis = Visibility::Hidden; if let Ok((ride_trans, ride_pos, ride_rot, ride_v, ride_angv)) = q_ride.get_single() { pos.0 = ride_pos.0 + DVec3::from(ride_trans.rotation * Vec3::new(0.0, 0.0, ride_trans.scale.z * 2.0)); rot.0 = ride_rot.0 * DQuat::from_array([-1.0, 0.0, 0.0, 0.0]); *v = ride_v.clone(); *angv = ride_angv.clone(); } } } #[allow(clippy::too_many_arguments)] pub fn apply_input_to_player( time: Res