diff --git a/crates/hedz_reloaded/src/abilities/arrow.rs b/crates/hedz_reloaded/src/abilities/arrow.rs index d389797..f8ca81a 100644 --- a/crates/hedz_reloaded/src/abilities/arrow.rs +++ b/crates/hedz_reloaded/src/abilities/arrow.rs @@ -1,8 +1,11 @@ use super::TriggerArrow; use crate::{ - GameState, billboards::Billboard, global_observer, heads_database::HeadsDatabase, - hitpoints::Hit, loading_assets::GameAssets, physics_layers::GameLayer, - utils::sprite_3d_animation::AnimationTimer, + GameState, global_observer, + heads_database::HeadsDatabase, + hitpoints::Hit, + loading_assets::GameAssets, + physics_layers::GameLayer, + utils::{Billboard, sprite_3d_animation::AnimationTimer}, }; use avian3d::prelude::*; use bevy::{light::NotShadowCaster, prelude::*}; diff --git a/crates/hedz_reloaded/src/abilities/gun.rs b/crates/hedz_reloaded/src/abilities/gun.rs index 881df1c..1735f73 100644 --- a/crates/hedz_reloaded/src/abilities/gun.rs +++ b/crates/hedz_reloaded/src/abilities/gun.rs @@ -1,8 +1,14 @@ use super::TriggerGun; use crate::{ - GameState, abilities::ProjectileId, billboards::Billboard, global_observer, - heads_database::HeadsDatabase, hitpoints::Hit, loading_assets::GameAssets, - physics_layers::GameLayer, tb_entities::EnemySpawn, utils::sprite_3d_animation::AnimationTimer, + GameState, + abilities::ProjectileId, + global_observer, + heads_database::HeadsDatabase, + hitpoints::Hit, + loading_assets::GameAssets, + physics_layers::GameLayer, + tb_entities::EnemySpawn, + utils::{Billboard, sprite_3d_animation::AnimationTimer}, }; use avian3d::prelude::*; use bevy::{light::NotShadowCaster, prelude::*}; diff --git a/crates/hedz_reloaded/src/abilities/mod.rs b/crates/hedz_reloaded/src/abilities/mod.rs index 97c837f..c36f56f 100644 --- a/crates/hedz_reloaded/src/abilities/mod.rs +++ b/crates/hedz_reloaded/src/abilities/mod.rs @@ -17,7 +17,7 @@ use crate::{ physics_layers::GameLayer, player::Player, protocol::PlaySound, - utils::{billboards::Billboard, explosions::Explosion, sprite_3d_animation::AnimationTimer}, + utils::{Billboard, explosions::Explosion, sprite_3d_animation::AnimationTimer}, }; use bevy::{light::NotShadowCaster, prelude::*}; use bevy_replicon::prelude::{SendMode, ServerTriggerExt, Signature, ToClients}; diff --git a/crates/hedz_reloaded/src/camera.rs b/crates/hedz_reloaded/src/camera.rs index 4e34d71..2246f53 100644 --- a/crates/hedz_reloaded/src/camera.rs +++ b/crates/hedz_reloaded/src/camera.rs @@ -1,19 +1,3 @@ -use crate::GameState; -#[cfg(feature = "client")] -use crate::control::Inputs; -use crate::control::ViewMode; -#[cfg(feature = "client")] -use crate::physics_layers::GameLayer; -#[cfg(feature = "client")] -use crate::player::LocalPlayer; -#[cfg(feature = "client")] -use crate::{control::LookDirMovement, loading_assets::UIAssets}; -#[cfg(feature = "client")] -use avian3d::prelude::SpatialQuery; -#[cfg(feature = "client")] -use avian3d::prelude::{ - Collider, LayerMask, PhysicsLayer as _, ShapeCastConfig, SpatialQueryFilter, -}; use bevy::prelude::*; use serde::{Deserialize, Serialize}; @@ -22,183 +6,3 @@ pub struct CameraTarget; #[derive(Component, Reflect, Debug, Serialize, Deserialize, PartialEq)] pub struct CameraArmRotation; - -/// Requested camera rotation based on various input sources (keyboard, gamepad) -#[derive(Component, Reflect, Debug, Default, Deref, DerefMut)] -#[reflect(Component)] -pub struct CameraRotationInput(pub Vec2); - -#[derive(Resource, Reflect, Debug, Default)] -#[reflect(Resource)] -pub struct CameraState { - pub cutscene: bool, - pub view_mode: ViewMode, -} - -#[derive(Component, Reflect, Debug, Default)] -struct CameraUi; - -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct MainCamera { - pub enabled: bool, - dir: Dir3, - distance: f32, - target_offset: Vec3, -} - -impl MainCamera { - fn new(arm: Vec3) -> Self { - let (dir, distance) = Dir3::new_and_length(arm).expect("invalid arm length"); - Self { - enabled: true, - dir, - distance, - target_offset: Vec3::new(0., 2., 0.), - } - } -} - -pub fn plugin(app: &mut App) { - app.register_type::(); - app.register_type::(); - app.register_type::(); - - app.init_resource::(); - app.add_systems(OnEnter(GameState::Playing), startup); - #[cfg(feature = "client")] - app.add_systems( - PostUpdate, - (update, update_ui, update_look_around, rotate_view).run_if(in_state(GameState::Playing)), - ); -} - -fn startup(mut commands: Commands) { - commands.spawn(( - Camera3d::default(), - MainCamera::new(Vec3::new(0., 1.8, 15.)), - CameraRotationInput::default(), - )); -} - -#[cfg(feature = "client")] -fn update_look_around( - inputs: Single<&Inputs, With>, - mut cam_state: ResMut, -) { - let view_mode = inputs.view_mode; - - if view_mode != cam_state.view_mode { - cam_state.view_mode = view_mode; - } -} - -#[cfg(feature = "client")] -fn update_ui( - mut commands: Commands, - cam_state: Res, - assets: Res, - query: Query>, -) { - if cam_state.is_changed() { - let show_free_cam_ui = cam_state.view_mode.is_free() || cam_state.cutscene; - - if show_free_cam_ui { - commands.spawn(( - CameraUi, - Node { - margin: UiRect::top(Val::Px(20.)) - .with_left(Val::Auto) - .with_right(Val::Auto), - justify_content: JustifyContent::Center, - ..default() - }, - children![( - Node { - display: Display::Block, - position_type: PositionType::Absolute, - ..default() - }, - ImageNode::new(assets.camera.clone()), - )], - )); - } else { - for entity in query.iter() { - commands.entity(entity).despawn(); - } - } - } -} - -#[cfg(feature = "client")] -fn update( - cam: Single< - (&MainCamera, &mut Transform, &CameraRotationInput), - (Without, Without), - >, - target_q: Single< - (&Transform, &Children), - ( - With, - With, - Without, - ), - >, - arm_rotation: Query<&Transform, With>, - spatial_query: SpatialQuery, - cam_state: Res, -) { - if cam_state.cutscene { - return; - } - - let (camera, mut cam_transform, cam_rotation_input) = cam.into_inner(); - - let (target_q, children) = target_q.into_inner(); - - let arm_tf = children - .iter() - .find_map(|child| arm_rotation.get(child).ok()) - .unwrap(); - - if !camera.enabled { - return; - } - - let target = target_q.translation + camera.target_offset; - - let direction = arm_tf.rotation * Quat::from_rotation_y(cam_rotation_input.x) * camera.dir; - - let max_distance = camera.distance; - - let filter = SpatialQueryFilter::from_mask(LayerMask(GameLayer::Level.to_bits())); - let cam_pos = if let Some(first_hit) = spatial_query.cast_shape( - &Collider::sphere(0.5), - target, - Quat::IDENTITY, - direction, - &ShapeCastConfig::from_max_distance(max_distance), - &filter, - ) { - let distance = first_hit.distance; - target + (direction * distance) - } else { - target + (direction * camera.distance) - }; - - *cam_transform = Transform::from_translation(cam_pos).looking_at(target, Vec3::Y); -} - -#[cfg(feature = "client")] -fn rotate_view( - inputs: Single<&Inputs, With>, - look_dir: Res, - mut cam: Single<&mut CameraRotationInput>, -) { - if !inputs.view_mode.is_free() { - cam.x = 0.0; - return; - } - - cam.0 += look_dir.0 * -0.001; -} diff --git a/crates/hedz_reloaded/src/client/aim/marker.rs b/crates/hedz_reloaded/src/client/aim/marker.rs index d792044..85bcd74 100644 --- a/crates/hedz_reloaded/src/client/aim/marker.rs +++ b/crates/hedz_reloaded/src/client/aim/marker.rs @@ -1,6 +1,5 @@ use crate::{ - GameState, aim::MarkerEvent, global_observer, loading_assets::UIAssets, - utils::billboards::Billboard, + GameState, aim::MarkerEvent, global_observer, loading_assets::UIAssets, utils::Billboard, }; use bevy::prelude::*; use bevy_sprite3d::Sprite3d; diff --git a/crates/hedz_reloaded/src/client/camera.rs b/crates/hedz_reloaded/src/client/camera.rs new file mode 100644 index 0000000..0b3bf71 --- /dev/null +++ b/crates/hedz_reloaded/src/client/camera.rs @@ -0,0 +1,193 @@ +use crate::{ + GameState, + camera::{CameraArmRotation, CameraTarget}, + control::{Inputs, LookDirMovement, ViewMode}, + loading_assets::UIAssets, + physics_layers::GameLayer, + player::LocalPlayer, +}; +use avian3d::prelude::SpatialQuery; +use avian3d::prelude::{ + Collider, LayerMask, PhysicsLayer as _, ShapeCastConfig, SpatialQueryFilter, +}; +use bevy::prelude::*; + +/// Requested camera rotation based on various input sources (keyboard, gamepad) +#[derive(Component, Reflect, Debug, Default, Deref, DerefMut)] +#[reflect(Component)] +pub struct CameraRotationInput(pub Vec2); + +#[derive(Resource, Reflect, Debug, Default)] +#[reflect(Resource)] +pub struct CameraState { + pub cutscene: bool, + pub view_mode: ViewMode, +} + +#[derive(Component, Reflect, Debug, Default)] +struct CameraUi; + +#[derive(Component, Reflect, Debug)] +#[reflect(Component)] +pub struct MainCamera { + pub enabled: bool, + dir: Dir3, + distance: f32, + target_offset: Vec3, +} + +impl MainCamera { + fn new(arm: Vec3) -> Self { + let (dir, distance) = Dir3::new_and_length(arm).expect("invalid arm length"); + Self { + enabled: true, + dir, + distance, + target_offset: Vec3::new(0., 2., 0.), + } + } +} + +pub fn plugin(app: &mut App) { + app.register_type::(); + app.register_type::(); + app.register_type::(); + + app.init_resource::(); + + app.add_systems(OnEnter(GameState::Playing), startup); + + app.add_systems( + PostUpdate, + (update, update_ui, update_look_around, rotate_view).run_if(in_state(GameState::Playing)), + ); +} + +fn startup(mut commands: Commands) { + commands.spawn(( + Camera3d::default(), + MainCamera::new(Vec3::new(0., 1.8, 15.)), + CameraRotationInput::default(), + )); +} + +#[cfg(feature = "client")] +fn update_look_around( + inputs: Single<&Inputs, With>, + mut cam_state: ResMut, +) { + let view_mode = inputs.view_mode; + + if view_mode != cam_state.view_mode { + cam_state.view_mode = view_mode; + } +} + +#[cfg(feature = "client")] +fn update_ui( + mut commands: Commands, + cam_state: Res, + assets: Res, + query: Query>, +) { + if cam_state.is_changed() { + let show_free_cam_ui = cam_state.view_mode.is_free() || cam_state.cutscene; + + if show_free_cam_ui { + commands.spawn(( + CameraUi, + Node { + margin: UiRect::top(Val::Px(20.)) + .with_left(Val::Auto) + .with_right(Val::Auto), + justify_content: JustifyContent::Center, + ..default() + }, + children![( + Node { + display: Display::Block, + position_type: PositionType::Absolute, + ..default() + }, + ImageNode::new(assets.camera.clone()), + )], + )); + } else { + for entity in query.iter() { + commands.entity(entity).despawn(); + } + } + } +} + +fn update( + cam: Single< + (&MainCamera, &mut Transform, &CameraRotationInput), + (Without, Without), + >, + target_q: Single< + (&Transform, &Children), + ( + With, + With, + Without, + ), + >, + arm_rotation: Query<&Transform, With>, + spatial_query: SpatialQuery, + cam_state: Res, +) { + if cam_state.cutscene { + return; + } + + let (camera, mut cam_transform, cam_rotation_input) = cam.into_inner(); + + let (target_q, children) = target_q.into_inner(); + + let arm_tf = children + .iter() + .find_map(|child| arm_rotation.get(child).ok()) + .unwrap(); + + if !camera.enabled { + return; + } + + let target = target_q.translation + camera.target_offset; + + let direction = arm_tf.rotation * Quat::from_rotation_y(cam_rotation_input.x) * camera.dir; + + let max_distance = camera.distance; + + let filter = SpatialQueryFilter::from_mask(LayerMask(GameLayer::Level.to_bits())); + let cam_pos = if let Some(first_hit) = spatial_query.cast_shape( + &Collider::sphere(0.5), + target, + Quat::IDENTITY, + direction, + &ShapeCastConfig::from_max_distance(max_distance), + &filter, + ) { + let distance = first_hit.distance; + target + (direction * distance) + } else { + target + (direction * camera.distance) + }; + + *cam_transform = Transform::from_translation(cam_pos).looking_at(target, Vec3::Y); +} + +#[cfg(feature = "client")] +fn rotate_view( + inputs: Single<&Inputs, With>, + look_dir: Res, + mut cam: Single<&mut CameraRotationInput>, +) { + if !inputs.view_mode.is_free() { + cam.x = 0.0; + return; + } + + cam.0 += look_dir.0 * -0.001; +} diff --git a/crates/hedz_reloaded/src/client/control/controller_flying.rs b/crates/hedz_reloaded/src/client/control/controller_flying.rs index b93f91b..8f339a1 100644 --- a/crates/hedz_reloaded/src/client/control/controller_flying.rs +++ b/crates/hedz_reloaded/src/client/control/controller_flying.rs @@ -26,13 +26,13 @@ fn rotate_rig( return; } - let (local_player_childer, selected_controller) = *local_player; + let (local_player_children, selected_controller) = *local_player; if !matches!(selected_controller, SelectedController::Flying) { return; } - local_player_childer.iter().find(|&child| { + local_player_children.iter().find(|&child| { if let Ok(mut rig_transform) = player_mesh.get_mut(child) { let look_dir = look_dir.0; diff --git a/crates/hedz_reloaded/src/client/cutscene.rs b/crates/hedz_reloaded/src/client/cutscene.rs new file mode 100644 index 0000000..769333d --- /dev/null +++ b/crates/hedz_reloaded/src/client/cutscene.rs @@ -0,0 +1,104 @@ +use crate::{ + GameState, + client::camera::{CameraState, MainCamera}, + cutscene::StartCutscene, + global_observer, + tb_entities::{CameraTarget, CutsceneCamera, CutsceneCameraMovementEnd}, +}; +use bevy::prelude::*; +use bevy_trenchbroom::prelude::*; + +#[derive(Resource, Debug, Default)] +enum CutsceneState { + #[default] + None, + Playing { + timer: Timer, + camera_start: Transform, + camera_end: Transform, + }, +} + +pub fn plugin(app: &mut App) { + app.init_resource::(); + app.add_systems(Update, update.run_if(in_state(GameState::Playing))); + + global_observer!(app, on_start_cutscene); +} + +fn on_start_cutscene( + trigger: On, + mut cam_state: ResMut, + mut cutscene_state: ResMut, + cutscenes: Query<(&Transform, &CutsceneCamera, &Target), Without>, + cutscene_movement: Query< + (&Transform, &CutsceneCameraMovementEnd, &Target), + Without, + >, + cam_target: Query<(&Transform, &CameraTarget), Without>, +) { + let cutscene = trigger.event().0.clone(); + + cam_state.cutscene = true; + + // asumes `name` and `targetname` are equal + let Some((t, _, target)) = cutscenes + .iter() + .find(|(_, cutscene_camera, _)| cutscene == cutscene_camera.name) + else { + return; + }; + + let move_end = cutscene_movement + .iter() + .find(|(_, _, target)| cutscene == target.target.clone().unwrap_or_default()) + .map(|(t, _, _)| *t) + .unwrap_or_else(|| *t); + + let Some((target, _)) = cam_target.iter().find(|(_, camera_target)| { + camera_target.targetname == target.target.clone().unwrap_or_default() + }) else { + return; + }; + + *cutscene_state = CutsceneState::Playing { + timer: Timer::from_seconds(2.0, TimerMode::Once), + camera_start: t.looking_at(target.translation, Vec3::Y), + camera_end: move_end.looking_at(target.translation, Vec3::Y), + }; +} + +fn update( + mut cam_state: ResMut, + mut cutscene_state: ResMut, + mut cam: Query<&mut Transform, With>, + time: Res