From 7951d613c49d3d31f2183de1d80174c79f8e9833 Mon Sep 17 00:00:00 2001 From: extrawurst Date: Wed, 19 Mar 2025 20:23:48 +0100 Subject: [PATCH] custom camera rig * should not go into level geometry anymore --- Cargo.lock | 12 ----- Cargo.toml | 1 - src/aim.rs | 4 +- src/camera.rs | 101 +++++++++++++++++++++++++++++------------- src/main.rs | 27 ++++------- src/physics_layers.rs | 8 ++++ src/player.rs | 70 ++++++++++++----------------- 7 files changed, 117 insertions(+), 106 deletions(-) create mode 100644 src/physics_layers.rs diff --git a/Cargo.lock b/Cargo.lock index bfdbfe7..3aa5189 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -697,17 +697,6 @@ dependencies = [ "sysinfo", ] -[[package]] -name = "bevy_dolly" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68783929254625d3ffa54a6fed7ff7216abf90296eea5e3885cde419d5daead0" -dependencies = [ - "bevy", - "bevy_math", - "bevy_transform", -] - [[package]] name = "bevy_ecs" version = "0.15.3" @@ -2816,7 +2805,6 @@ dependencies = [ "bevy-tnua", "bevy-tnua-avian3d", "bevy_asset_loader", - "bevy_dolly", "bevy_sprite3d", "bevy_trenchbroom", "nil", diff --git a/Cargo.toml b/Cargo.toml index 6a7f71c..a7eba27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ nil = "0.14.0" bevy-inspector-egui = "0.29.1" bevy-tnua = "0.21.0" bevy-tnua-avian3d = "0.2.0" -bevy_dolly = { version = "0.0.5", default-features = false } bevy_asset_loader = "0.22.0" bevy_sprite3d = "4.0.0" rand = "0.8.5" diff --git a/src/aim.rs b/src/aim.rs index 03d3554..20de735 100644 --- a/src/aim.rs +++ b/src/aim.rs @@ -1,6 +1,6 @@ use crate::{ billboards::Billboard, - player::{Player, PlayerHead}, + player::{Player, PlayerRig}, tb_entities::EnemySpawn, }; use bevy::prelude::*; @@ -84,7 +84,7 @@ fn update( mut state: ResMut, query: Query<(Entity, &Transform), With>, player_pos: Query<&Transform, With>, - player_rot: Query<&Transform, With>, + player_rot: Query<&Transform, With>, ) { let Some(player_pos) = player_pos.iter().next().map(|t| t.translation) else { return; diff --git a/src/camera.rs b/src/camera.rs index 3df625e..1dbf722 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,38 +1,77 @@ +use avian3d::prelude::*; use bevy::prelude::*; -use bevy_dolly::prelude::*; -impl GameCameraRig { - pub fn new_with_arm(arm: Vec3) -> Self { - Self( - CameraRig::builder() - .with(Position::new(Vec3::ZERO)) - .with(Rotation::new(Quat::IDENTITY)) - .with(Smooth::new_position(1.25).predictive(true)) - .with(Smooth::new_rotation(0.5)) - .with(Arm::new(arm)) - .with( - LookAt::new(Vec3::ZERO + Vec3::Y) - .tracking_smoothness(0.2) - .tracking_predictive(true), - ) - .build(), - ) - } +use crate::physics_layers::GameLayer; - pub fn set_position_target(&mut self, target_position: Vec3, target_rotation: Quat) { - self.driver_mut::().position = target_position; - self.driver_mut::().rotation = target_rotation; - self.driver_mut::().target = target_position + Vec3::Y; +#[derive(Component, Reflect, Debug)] +pub struct CameraTarget; + +#[derive(Component, Reflect, Debug)] +pub struct CameraArmRotation; + +#[derive(Component, Reflect, Debug)] +pub struct MainCamera { + arm: Vec3, +} + +impl MainCamera { + fn new(arm: Vec3) -> Self { + Self { arm } } } -/// A custom camera rig which combines smoothed movement with a look-at driver. -#[derive(Component, Debug, Deref, DerefMut)] -pub struct GameCameraRig(CameraRig); - -// Turn the nested rig into a driver, so it can be used in another rig. -impl RigDriver for GameCameraRig { - fn update(&mut self, params: RigUpdateParams) -> Transform { - self.0.update(params.delta_time_seconds) - } +pub fn plugin(app: &mut App) { + app.add_systems(Startup, startup); + app.add_systems(Update, update); +} + +fn startup(mut commands: Commands) { + commands.spawn(( + Camera3d::default(), + MainCamera::new(Vec3::new(0., 1., -12.)), + )); +} + +fn update( + mut cam: Query< + (&MainCamera, &mut Transform), + (Without, Without), + >, + target: Query<&GlobalTransform, (With, Without)>, + arm_rotation: Query<&Transform, With>, + spatial_query: SpatialQuery, +) { + let Ok(target) = target.get_single().map(|t| t.translation()) else { + return; + }; + + let Ok(rotation) = arm_rotation.get_single() else { + return; + }; + + let Ok((camera, mut cam_transform)) = cam.get_single_mut() else { + return; + }; + + let target_transform = Transform::from_translation(target) + * Transform::from_rotation(rotation.rotation) + * Transform::from_translation(camera.arm); + + let ideal_cam_pos = target_transform.translation; + + let max_distance = camera.arm.length(); + let Ok(direction) = Dir3::new(ideal_cam_pos - target) else { + return; + }; + + let filter = SpatialQueryFilter::from_mask(LayerMask(GameLayer::Level.to_bits())); + let cam_pos = if let Some(first_hit) = + spatial_query.cast_ray(target, direction, max_distance, false, &filter) + { + target + (direction * first_hit.distance) + } else { + ideal_cam_pos + }; + + *cam_transform = Transform::from_translation(cam_pos).looking_at(target, Vec3::Y); } diff --git a/src/main.rs b/src/main.rs index 23b2549..924543e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ mod heads_ui; mod keys; mod movables; mod npc; +mod physics_layers; mod platforms; mod player; mod shooting; @@ -25,11 +26,10 @@ use bevy::core_pipeline::tonemapping::Tonemapping; use bevy::prelude::*; use bevy::render::view::ColorGrading; use bevy::scene::SceneInstanceReady; -use bevy_dolly::prelude::*; use bevy_tnua::prelude::TnuaControllerPlugin; use bevy_tnua_avian3d::TnuaAvian3dPlugin; use bevy_trenchbroom::prelude::*; -use camera::GameCameraRig; +use physics_layers::GameLayer; #[derive(Resource, Reflect, Debug)] #[reflect(Resource)] @@ -41,9 +41,6 @@ struct DebugVisuals { pub cam_follow: bool, } -#[derive(Component, Debug)] -pub struct MainCamera; - fn main() { let mut app = App::new(); @@ -73,8 +70,6 @@ fn main() { // app.add_plugins(PhysicsDebugPlugin::default()); // app.add_plugins(bevy_inspector_egui::quick::WorldInspectorPlugin::default()); - app.add_systems(Update, Dolly::::update_active); - app.add_plugins(alien::plugin); app.add_plugins(cash::plugin); app.add_plugins(player::plugin); @@ -91,6 +86,7 @@ fn main() { app.add_plugins(cutscene::plugin); app.add_plugins(controls::plugin); app.add_plugins(sounds::plugin); + app.add_plugins(camera::plugin); app.insert_resource(AmbientLight { color: Color::WHITE, @@ -99,7 +95,7 @@ fn main() { app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz"))); - app.add_systems(Startup, (setup_cam, write_trenchbroom_config, music)); + app.add_systems(Startup, (write_trenchbroom_config, music)); app.add_systems(PostStartup, setup_scene); app.add_systems( Update, @@ -109,19 +105,12 @@ fn main() { app.run(); } -fn setup_cam(mut commands: Commands) { - commands.spawn(( - Camera3d::default(), - MainCamera, - Rig::builder() - .with(GameCameraRig::new_with_arm(Vec3::new(0., 5., 14.))) - .build(), - )); -} - fn setup_scene(mut commands: Commands, asset_server: Res) { commands - .spawn(SceneRoot(asset_server.load("maps/map1.map#Scene"))) + .spawn(( + CollisionLayers::new(LayerMask(GameLayer::Level.to_bits()), LayerMask::ALL), + SceneRoot(asset_server.load("maps/map1.map#Scene")), + )) .observe(|_t: Trigger| { //TODO: use for state driven map loading info!("map loaded"); diff --git a/src/physics_layers.rs b/src/physics_layers.rs new file mode 100644 index 0000000..18aae86 --- /dev/null +++ b/src/physics_layers.rs @@ -0,0 +1,8 @@ +use avian3d::prelude::PhysicsLayer; + +#[derive(PhysicsLayer, Clone, Copy, Debug, Default)] +pub enum GameLayer { + #[default] + Level, + Player, +} diff --git a/src/player.rs b/src/player.rs index 496dbf7..1cf687a 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,10 +1,10 @@ use crate::{ - DebugVisuals, alien::{ALIEN_ASSET_PATH, Animations}, - camera::GameCameraRig, + camera::{CameraArmRotation, CameraTarget}, cash::{Cash, CashCollectEvent}, controls::Controls, heads_ui::HeadChanged, + physics_layers::GameLayer, tb_entities::SpawnPoint, }; use avian3d::prelude::*; @@ -12,10 +12,9 @@ use bevy::{ prelude::*, window::{CursorGrabMode, PrimaryWindow}, }; -use bevy_dolly::prelude::Rig; use bevy_tnua::{TnuaUserControlsSystemSet, prelude::*}; use bevy_tnua_avian3d::TnuaAvian3dSensorShape; -use std::{f32::consts::PI, time::Duration}; +use std::time::Duration; #[derive(Component, Default)] pub struct Player; @@ -24,7 +23,7 @@ pub struct Player; struct PlayerAnimations; #[derive(Component, Default)] -pub struct PlayerHead; +struct PlayerHead; #[derive(Component, Default)] pub struct PlayerRig; @@ -47,7 +46,6 @@ pub fn plugin(app: &mut App) { Update, ( spawn, - update_camera, collect_cash, toggle_animation, setup_animations_marker_for_player, @@ -86,30 +84,39 @@ fn spawn( .spawn(( Name::from("player"), Player, + CameraTarget, transform, TransformInterpolation, + TransformExtrapolation, Visibility::default(), RigidBody::Dynamic, Collider::capsule(1.2, 1.5), + CollisionLayers::new(LayerMask(GameLayer::Player.to_bits()), LayerMask::ALL), LockedAxes::ROTATION_LOCKED, TnuaController::default(), TnuaAvian3dSensorShape(Collider::cylinder(0.8, 0.0)), )) - .with_child(( - Name::from("head"), - PlayerHead, - Transform::from_translation(Vec3::new(0., -0.5, 0.)) - .with_rotation(Quat::from_rotation_y(std::f32::consts::PI)), - SceneRoot(mesh), - )) - .with_child(( - Name::from("body rig"), - PlayerRig, - Transform::from_translation(Vec3::new(0., -3., 0.)) - .with_rotation(Quat::from_rotation_y(std::f32::consts::PI)) - .with_scale(Vec3::splat(1.5)), - SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(ALIEN_ASSET_PATH))), - )); + .with_children(|parent| { + parent + .spawn(( + Name::from("body rig"), + PlayerRig, + CameraArmRotation, + Transform::from_translation(Vec3::new(0., -3., 0.)) + .with_rotation(Quat::from_rotation_y(std::f32::consts::PI)) + .with_scale(Vec3::splat(1.4)), + SceneRoot( + asset_server.load(GltfAssetLabel::Scene(0).from_asset(ALIEN_ASSET_PATH)), + ), + )) + .with_child(( + Name::from("head"), + PlayerHead, + Transform::from_translation(Vec3::new(0., 1.6, 0.)) + .with_scale(Vec3::splat(0.7)), + SceneRoot(mesh), + )); + }); commands.spawn(( AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")), @@ -122,7 +129,7 @@ fn spawn( fn rotate_view( mut controls: ResMut, // todo: Put the player head as a child of the rig to avoid this mess: - mut player: Query<&mut Transform, Or<(With, With)>>, + mut player: Query<&mut Transform, With>, ) { for mut tr in &mut player { tr.rotate_y(controls.keyboard_state.look_dir.x * -0.001); @@ -207,25 +214,6 @@ fn apply_controls( } } -fn update_camera( - player: Query<&GlobalTransform, With>, - mut rig: Single<&mut Rig>, - res: Res, -) { - let Some(player) = player.iter().next() else { - return; - }; - - if !res.cam_follow { - return; - } - - rig.driver_mut::().set_position_target( - player.translation(), - player.rotation() * Quat::from_rotation_y(PI), - ); -} - fn collect_cash( mut commands: Commands, mut collision_event_reader: EventReader,