2024-03-16 20:00:40 +00:00
|
|
|
use bevy::prelude::*;
|
|
|
|
use bevy::input::mouse::MouseMotion;
|
2024-03-16 23:24:47 +00:00
|
|
|
use bevy::window::PrimaryWindow;
|
2024-03-29 18:41:46 +00:00
|
|
|
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
|
2024-03-30 14:50:36 +00:00
|
|
|
use bevy::core_pipeline::tonemapping::Tonemapping;
|
2024-03-29 15:33:12 +00:00
|
|
|
use bevy_xpbd_3d::prelude::*;
|
2024-03-18 03:39:26 +00:00
|
|
|
use std::f32::consts::*;
|
2024-03-21 17:45:43 +00:00
|
|
|
use crate::{settings, audio, actor};
|
2024-03-16 20:00:40 +00:00
|
|
|
|
|
|
|
pub struct CameraControllerPlugin;
|
|
|
|
|
|
|
|
impl Plugin for CameraControllerPlugin {
|
|
|
|
fn build(&self, app: &mut App) {
|
2024-03-29 18:41:46 +00:00
|
|
|
app.add_systems(Startup, setup_camera);
|
|
|
|
app.add_systems(Update, handle_input);
|
|
|
|
app.add_systems(Update, manage_player_actor);
|
2024-03-30 15:18:49 +00:00
|
|
|
app.add_systems(Update, sync_camera_to_player);
|
2024-03-16 20:00:40 +00:00
|
|
|
app.add_systems(Update, run_camera_controller);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-18 03:39:26 +00:00
|
|
|
// Based on Valorant's default sensitivity, not entirely sure why it is exactly 1.0 / 180.0,
|
|
|
|
// but I'm guessing it is a misunderstanding between degrees/radians and then sticking with
|
|
|
|
// it because it felt nice.
|
2024-03-18 02:06:41 +00:00
|
|
|
pub const RADIANS_PER_DOT: f32 = 1.0 / 180.0;
|
2024-03-16 20:00:40 +00:00
|
|
|
|
|
|
|
#[derive(Component)]
|
|
|
|
pub struct CameraController {
|
|
|
|
pub enabled: bool,
|
|
|
|
pub initialized: bool,
|
|
|
|
pub sensitivity: f32,
|
|
|
|
pub pitch: f32,
|
|
|
|
pub yaw: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for CameraController {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
enabled: true,
|
|
|
|
initialized: false,
|
2024-03-18 02:06:41 +00:00
|
|
|
sensitivity: 0.5,
|
2024-03-18 02:13:44 +00:00
|
|
|
pitch: 1.0, // pitch=0/yaw=0 -> face sun
|
|
|
|
yaw: 0.3,
|
2024-03-16 20:00:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-29 18:41:46 +00:00
|
|
|
fn setup_camera(
|
|
|
|
mut commands: Commands,
|
|
|
|
) {
|
|
|
|
// Add player
|
|
|
|
commands.spawn((
|
|
|
|
Camera3dBundle {
|
|
|
|
camera: Camera {
|
|
|
|
hdr: true, // HDR is required for bloom
|
|
|
|
..default()
|
|
|
|
},
|
2024-03-30 14:50:36 +00:00
|
|
|
tonemapping: Tonemapping::TonyMcMapface,
|
2024-03-29 18:41:46 +00:00
|
|
|
transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
CameraController::default(),
|
|
|
|
BloomSettings {
|
|
|
|
composite_mode: BloomCompositeMode::EnergyConserving,
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2024-03-30 15:18:49 +00:00
|
|
|
pub fn sync_camera_to_player(
|
|
|
|
settings: Res<settings::Settings>,
|
|
|
|
mut q_camera: Query<&mut Transform, With<CameraController>>,
|
|
|
|
q_playercam: Query<(&actor::Actor, &Transform), (With<actor::PlayerCamera>, Without<actor::PlayerDrivesThis>, Without<CameraController>)>,
|
|
|
|
) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-29 18:41:46 +00:00
|
|
|
pub fn handle_input(
|
|
|
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
|
|
|
mut settings: ResMut<settings::Settings>,
|
|
|
|
) {
|
|
|
|
if keyboard_input.just_pressed(settings.key_camera) {
|
|
|
|
settings.third_person ^= true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn manage_player_actor(
|
|
|
|
settings: Res<settings::Settings>,
|
2024-03-30 14:37:51 +00:00
|
|
|
mut q_playercam: Query<&mut Visibility, With<actor::PlayerCamera>>,
|
|
|
|
mut q_hiddenplayer: Query<&mut Visibility, (With<actor::Player>, Without<actor::PlayerCamera>)>,
|
2024-03-29 18:41:46 +00:00
|
|
|
) {
|
|
|
|
for mut vis in &mut q_playercam {
|
|
|
|
if settings.third_person {
|
|
|
|
*vis = Visibility::Inherited;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
*vis = Visibility::Hidden;
|
|
|
|
}
|
|
|
|
}
|
2024-03-30 14:37:51 +00:00
|
|
|
for mut vis in &mut q_hiddenplayer {
|
|
|
|
*vis = Visibility::Hidden;
|
|
|
|
}
|
2024-03-29 18:41:46 +00:00
|
|
|
}
|
|
|
|
|
2024-03-16 20:00:40 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn run_camera_controller(
|
|
|
|
time: Res<Time>,
|
2024-03-18 03:10:08 +00:00
|
|
|
settings: Res<settings::Settings>,
|
2024-03-16 23:24:47 +00:00
|
|
|
mut windows: Query<&mut Window, With<PrimaryWindow>>,
|
2024-03-16 20:00:40 +00:00
|
|
|
mut mouse_events: EventReader<MouseMotion>,
|
|
|
|
key_input: Res<ButtonInput<KeyCode>>,
|
2024-03-16 23:41:06 +00:00
|
|
|
thruster_sound_controller: Query<&AudioSink, With<audio::ComponentThrusterSound>>,
|
2024-03-28 13:10:10 +00:00
|
|
|
rocket_sound_controller: Query<&AudioSink, With<audio::ComponentRocketSound>>,
|
2024-03-29 03:36:20 +00:00
|
|
|
ion_sound_controller: Query<&AudioSink, With<audio::ComponentIonSound>>,
|
2024-03-28 23:01:17 +00:00
|
|
|
mut q_engine: Query<&mut actor::Engine, With<actor::PlayerDrivesThis>>,
|
2024-03-30 15:18:49 +00:00
|
|
|
mut q_camera: Query<(&mut CameraController, &mut Projection)>,
|
2024-03-29 18:41:46 +00:00
|
|
|
q_player: Query<&actor::LifeForm, With<actor::Player>>,
|
|
|
|
mut q_playercam: Query<(
|
2024-03-28 23:01:17 +00:00
|
|
|
&mut Transform,
|
2024-03-29 15:33:12 +00:00
|
|
|
&mut actor::Engine,
|
|
|
|
&mut AngularVelocity,
|
|
|
|
&mut LinearVelocity,
|
2024-03-29 18:41:46 +00:00
|
|
|
), (With<actor::PlayerCamera>, Without<actor::PlayerDrivesThis>, Without<CameraController>)>,
|
2024-03-16 20:00:40 +00:00
|
|
|
) {
|
|
|
|
let dt = time.delta_seconds();
|
2024-03-16 23:41:06 +00:00
|
|
|
let mut play_thruster_sound = false;
|
2024-03-16 20:00:40 +00:00
|
|
|
|
2024-03-16 23:24:47 +00:00
|
|
|
let window_result = windows.get_single_mut();
|
|
|
|
let mut focused = true;
|
|
|
|
if window_result.is_ok() {
|
|
|
|
focused = window_result.unwrap().focused;
|
|
|
|
}
|
|
|
|
|
2024-03-29 18:41:46 +00:00
|
|
|
if let (
|
2024-03-30 15:18:49 +00:00
|
|
|
Ok((mut controller, mut projection)),
|
2024-03-29 18:41:46 +00:00
|
|
|
Ok(lifeform),
|
2024-03-30 15:18:49 +00:00
|
|
|
Ok((mut player_transform, player_engine, mut angularvelocity, mut v)),
|
2024-03-29 18:41:46 +00:00
|
|
|
) = (q_camera.get_single_mut(), q_player.get_single(), q_playercam.get_single_mut()) {
|
2024-03-16 20:00:40 +00:00
|
|
|
if !controller.enabled {
|
|
|
|
mouse_events.clear();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-29 15:58:42 +00:00
|
|
|
if angularvelocity.length_squared() > 0.0001 {
|
|
|
|
angularvelocity.x *= 0.98;
|
|
|
|
angularvelocity.y *= 0.98;
|
|
|
|
angularvelocity.z *= 0.98;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
angularvelocity.0 = Vec3::splat(0.0);
|
|
|
|
}
|
2024-03-29 15:33:12 +00:00
|
|
|
|
2024-03-16 20:00:40 +00:00
|
|
|
// Handle key input
|
|
|
|
let mut axis_input = Vec3::ZERO;
|
2024-03-16 23:24:47 +00:00
|
|
|
if focused {
|
2024-03-18 03:39:26 +00:00
|
|
|
if key_input.pressed(settings.key_forward) {
|
2024-03-29 18:41:46 +00:00
|
|
|
axis_input.z += 1.2;
|
2024-03-16 23:24:47 +00:00
|
|
|
}
|
2024-03-18 03:39:26 +00:00
|
|
|
if key_input.pressed(settings.key_back) {
|
2024-03-29 18:41:46 +00:00
|
|
|
axis_input.z -= 1.2;
|
2024-03-16 23:24:47 +00:00
|
|
|
}
|
2024-03-18 03:39:26 +00:00
|
|
|
if key_input.pressed(settings.key_right) {
|
2024-03-29 18:41:46 +00:00
|
|
|
axis_input.x -= 1.2;
|
2024-03-16 23:24:47 +00:00
|
|
|
}
|
2024-03-18 03:39:26 +00:00
|
|
|
if key_input.pressed(settings.key_left) {
|
2024-03-29 18:41:46 +00:00
|
|
|
axis_input.x += 1.2;
|
2024-03-16 23:24:47 +00:00
|
|
|
}
|
2024-03-18 03:39:26 +00:00
|
|
|
if key_input.pressed(settings.key_up) {
|
2024-03-29 01:49:16 +00:00
|
|
|
axis_input.y += 1.2;
|
2024-03-16 23:24:47 +00:00
|
|
|
}
|
2024-03-18 03:39:26 +00:00
|
|
|
if key_input.pressed(settings.key_down) {
|
2024-03-29 01:49:16 +00:00
|
|
|
axis_input.y -= 1.2;
|
2024-03-16 23:24:47 +00:00
|
|
|
}
|
2024-03-29 01:40:55 +00:00
|
|
|
if key_input.pressed(settings.key_stop) {
|
2024-03-29 15:33:12 +00:00
|
|
|
let stop_direction = -v.normalize();
|
2024-03-29 01:40:55 +00:00
|
|
|
if stop_direction.length_squared() > 0.3 {
|
2024-03-29 18:41:46 +00:00
|
|
|
axis_input += 1.0 * (player_transform.rotation.inverse() * stop_direction);
|
2024-03-29 01:40:55 +00:00
|
|
|
}
|
2024-03-21 17:45:43 +00:00
|
|
|
}
|
|
|
|
}
|
2024-03-29 01:41:05 +00:00
|
|
|
// In typical games we would normalize the input vector so that diagonal movement is as
|
|
|
|
// fast as forward or sideways movement. But here, we merely clamp each direction to an
|
|
|
|
// absolute maximum of 1, since every thruster can be used separately. If the forward
|
|
|
|
// thrusters and the leftward thrusters are active at the same time, then of course the
|
|
|
|
// total diagonal acceleration is faster than the forward acceleration alone.
|
|
|
|
axis_input = axis_input.clamp(Vec3::splat(-1.0), Vec3::splat(1.0));
|
2024-03-17 13:39:42 +00:00
|
|
|
|
2024-03-28 23:01:17 +00:00
|
|
|
let mut engine = if let Ok(engine) = q_engine.get_single_mut() { engine } else { player_engine };
|
|
|
|
|
2024-03-16 20:00:40 +00:00
|
|
|
// Apply movement update
|
2024-03-29 01:40:55 +00:00
|
|
|
let forward_factor = engine.current_warmup * (if axis_input.z < 0.0 {
|
2024-03-28 12:26:14 +00:00
|
|
|
engine.thrust_forward
|
|
|
|
} else {
|
|
|
|
engine.thrust_back
|
|
|
|
});
|
2024-03-29 00:40:58 +00:00
|
|
|
let right_factor = engine.thrust_sideways * engine.current_warmup;
|
|
|
|
let up_factor = engine.thrust_sideways * engine.current_warmup;
|
2024-03-29 00:55:23 +00:00
|
|
|
let factor = Vec3::new(right_factor, up_factor, forward_factor);
|
2024-03-29 00:40:58 +00:00
|
|
|
|
2024-03-29 01:40:55 +00:00
|
|
|
if axis_input.length_squared() > 0.003 {
|
2024-03-29 18:41:46 +00:00
|
|
|
let acceleration_global = player_transform.rotation * (axis_input * factor);
|
2024-03-29 01:56:48 +00:00
|
|
|
let mut acceleration_total = actor::ENGINE_SPEED_FACTOR * dt * acceleration_global;
|
|
|
|
let threshold = 1e-5;
|
|
|
|
if key_input.pressed(settings.key_stop) {
|
|
|
|
for i in 0..3 {
|
2024-03-29 15:33:12 +00:00
|
|
|
if v[i].abs() < threshold {
|
|
|
|
v[i] = 0.0;
|
2024-03-29 01:56:48 +00:00
|
|
|
}
|
2024-03-29 15:33:12 +00:00
|
|
|
else if v[i].signum() != (v[i] + acceleration_total[i]).signum() {
|
2024-03-29 02:31:15 +00:00
|
|
|
// Overshoot
|
2024-03-29 15:33:12 +00:00
|
|
|
v[i] = 0.0;
|
2024-03-29 01:56:48 +00:00
|
|
|
acceleration_total[i] = 0.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-03-29 15:33:12 +00:00
|
|
|
v.0 += acceleration_total;
|
2024-03-29 01:21:28 +00:00
|
|
|
engine.current_warmup = (engine.current_warmup + dt / engine.warmup_seconds).clamp(0.0, 1.0);
|
|
|
|
play_thruster_sound = !settings.mute_sfx;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
engine.current_warmup = (engine.current_warmup - dt / engine.warmup_seconds).clamp(0.0, 1.0);
|
|
|
|
}
|
2024-03-21 17:45:43 +00:00
|
|
|
|
2024-03-16 20:00:40 +00:00
|
|
|
// Handle mouse input
|
|
|
|
let mut mouse_delta = Vec2::ZERO;
|
2024-03-18 03:39:26 +00:00
|
|
|
for mouse_event in mouse_events.read() {
|
|
|
|
mouse_delta += mouse_event.delta;
|
2024-03-16 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if mouse_delta != Vec2::ZERO {
|
|
|
|
// Apply look update
|
|
|
|
controller.pitch = (controller.pitch
|
2024-03-29 18:41:46 +00:00
|
|
|
+ mouse_delta.y * RADIANS_PER_DOT * controller.sensitivity)
|
2024-03-16 20:00:40 +00:00
|
|
|
.clamp(-PI / 2., PI / 2.);
|
2024-03-29 18:41:46 +00:00
|
|
|
controller.yaw += mouse_delta.x * RADIANS_PER_DOT * controller.sensitivity;
|
2024-03-30 14:37:51 +00:00
|
|
|
player_transform.rotation = Quat::from_euler(EulerRot::ZYX, 0.0, -controller.yaw, controller.pitch).normalize();
|
2024-03-16 20:00:40 +00:00
|
|
|
}
|
2024-03-16 23:41:06 +00:00
|
|
|
|
2024-03-28 22:38:24 +00:00
|
|
|
let fov = (lifeform.adrenaline * lifeform.adrenaline * lifeform.adrenaline * 45.0 + 45.0).to_radians();
|
|
|
|
*projection = Projection::Perspective(PerspectiveProjection { fov: fov, ..default() });
|
|
|
|
|
2024-03-16 23:41:06 +00:00
|
|
|
if let Ok(sink) = thruster_sound_controller.get_single() {
|
2024-03-28 13:10:10 +00:00
|
|
|
if play_thruster_sound && engine.engine_type == actor::EngineType::Monopropellant {
|
|
|
|
sink.play()
|
|
|
|
} else {
|
|
|
|
sink.pause()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Ok(sink) = rocket_sound_controller.get_single() {
|
|
|
|
if play_thruster_sound && engine.engine_type == actor::EngineType::Rocket {
|
2024-03-16 23:41:06 +00:00
|
|
|
sink.play()
|
|
|
|
} else {
|
|
|
|
sink.pause()
|
|
|
|
}
|
|
|
|
}
|
2024-03-29 03:36:20 +00:00
|
|
|
if let Ok(sink) = ion_sound_controller.get_single() {
|
|
|
|
if play_thruster_sound && engine.engine_type == actor::EngineType::Ion {
|
|
|
|
sink.play()
|
|
|
|
} else {
|
|
|
|
sink.pause()
|
|
|
|
}
|
|
|
|
}
|
2024-03-16 20:00:40 +00:00
|
|
|
}
|
|
|
|
}
|