From 5d2c1d6cad665dedfa4a95860b38bca03d3eb4e2 Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Wed, 29 Oct 2025 15:28:40 +0100 Subject: [PATCH 1/2] wip: use shapecaster component instead of spatialquery for ground check --- assets/blender_src/fun_shooter.blend | 4 +- assets/blender_src/fun_shooter.blend1 | 4 +- assets/maps/medium_plastic/scene.gltf | 138 +++++--------------------- src/character_controller/mod.rs | 73 ++++++++------ src/enemy/spawn/mod.rs | 23 ++++- src/game_flow/game_mode.rs | 5 +- src/game_flow/systems.rs | 7 +- src/nav_mesh_pathfinding/mod.rs | 60 ++++++++++- src/player/camera/systems.rs | 6 +- 9 files changed, 164 insertions(+), 156 deletions(-) diff --git a/assets/blender_src/fun_shooter.blend b/assets/blender_src/fun_shooter.blend index f5b2d62..e1c68e8 100644 --- a/assets/blender_src/fun_shooter.blend +++ b/assets/blender_src/fun_shooter.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51894f369013b8f936ec4a5d407e9674f70b33ab6881b3f438c7bae165ae3bc4 -size 109799149 +oid sha256:4649f7c5d3f610e40af46e86d5afab896fb84844bb0078831446ea5df701a664 +size 109791897 diff --git a/assets/blender_src/fun_shooter.blend1 b/assets/blender_src/fun_shooter.blend1 index 0d3b466..f5b2d62 100644 --- a/assets/blender_src/fun_shooter.blend1 +++ b/assets/blender_src/fun_shooter.blend1 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:794adc3aa328ac508bba89c4a02a95732c83528f67243eb6a8474c67899da195 -size 109793554 +oid sha256:51894f369013b8f936ec4a5d407e9674f70b33ab6881b3f438c7bae165ae3bc4 +size 109799149 diff --git a/assets/maps/medium_plastic/scene.gltf b/assets/maps/medium_plastic/scene.gltf index 9c338cc..cc79cd8 100644 --- a/assets/maps/medium_plastic/scene.gltf +++ b/assets/maps/medium_plastic/scene.gltf @@ -10,16 +10,16 @@ "nodes":[ 528, 529, - 530, - 531, - 532, - 533, - 534 + 530 ] }, { "name":"Small_Map", "nodes":[ + 821, + 822, + 823, + 824, 825, 826, 827, @@ -35,11 +35,7 @@ 837, 838, 839, - 840, - 841, - 842, - 843, - 844 + 840 ] } ], @@ -2935,26 +2931,6 @@ 1.4299999475479126 ] }, - { - "extras":{ - "skein":[ - { - "fun_shooter::enemy::spawn::EnemySpawnLocation":{} - } - ] - }, - "name":"Empty", - "scale":[ - 1.4299999475479126, - 1.4299999475479126, - 1.4299999475479126 - ], - "translation":[ - -7.055305480957031, - 0.7016563415527344, - 7.515159606933594 - ] - }, { "extras":{ "skein":[ @@ -2975,26 +2951,6 @@ 20.508115768432617 ] }, - { - "extras":{ - "skein":[ - { - "fun_shooter::enemy::spawn::EnemySpawnLocation":{} - } - ] - }, - "name":"Empty.002", - "scale":[ - 1.4299999475479126, - 1.4299999475479126, - 1.4299999475479126 - ], - "translation":[ - 4.780683517456055, - 1.5932782888412476, - 6.4729461669921875 - ] - }, { "extras":{ "skein":[ @@ -3015,46 +2971,6 @@ -11.650971412658691 ] }, - { - "extras":{ - "skein":[ - { - "fun_shooter::enemy::spawn::EnemySpawnLocation":{} - } - ] - }, - "name":"Empty.004", - "scale":[ - 1.4299999475479126, - 1.4299999475479126, - 1.4299999475479126 - ], - "translation":[ - 3.5724313259124756, - 1.4190740585327148, - -13.245203018188477 - ] - }, - { - "extras":{ - "skein":[ - { - "fun_shooter::enemy::spawn::EnemySpawnLocation":{} - } - ] - }, - "name":"Empty.005", - "scale":[ - 1.4299999475479126, - 1.4299999475479126, - 1.4299999475479126 - ], - "translation":[ - -3.921736240386963, - 0.7016563415527344, - 7.515159606933594 - ] - }, { "extras":{ "skein":[ @@ -4571,7 +4487,7 @@ }, { "children":[ - 819 + 815 ], "name":"Map.001" }, @@ -4615,6 +4531,10 @@ }, { "children":[ + 531, + 532, + 533, + 534, 535, 536, 537, @@ -4895,15 +4815,11 @@ 812, 813, 814, - 815, 816, 817, 818, - 820, - 821, - 822, - 823, - 824 + 819, + 820 ], "extras":{ "skein":[] @@ -5159,7 +5075,7 @@ { "sampler":0, "target":{ - "node":802, + "node":798, "path":"rotation" } } @@ -5178,7 +5094,7 @@ { "sampler":0, "target":{ - "node":803, + "node":799, "path":"rotation" } } @@ -5197,7 +5113,7 @@ { "sampler":0, "target":{ - "node":804, + "node":800, "path":"rotation" } } @@ -5216,7 +5132,7 @@ { "sampler":0, "target":{ - "node":805, + "node":801, "path":"rotation" } } @@ -5235,7 +5151,7 @@ { "sampler":0, "target":{ - "node":806, + "node":802, "path":"rotation" } } @@ -5254,7 +5170,7 @@ { "sampler":0, "target":{ - "node":807, + "node":803, "path":"rotation" } } @@ -5273,7 +5189,7 @@ { "sampler":0, "target":{ - "node":808, + "node":804, "path":"rotation" } } @@ -5292,7 +5208,7 @@ { "sampler":0, "target":{ - "node":809, + "node":805, "path":"rotation" } } @@ -5311,7 +5227,7 @@ { "sampler":0, "target":{ - "node":810, + "node":806, "path":"rotation" } } @@ -5330,7 +5246,7 @@ { "sampler":0, "target":{ - "node":811, + "node":807, "path":"rotation" } } @@ -5349,7 +5265,7 @@ { "sampler":0, "target":{ - "node":812, + "node":808, "path":"rotation" } } @@ -5368,7 +5284,7 @@ { "sampler":0, "target":{ - "node":813, + "node":809, "path":"rotation" } } @@ -5387,7 +5303,7 @@ { "sampler":0, "target":{ - "node":814, + "node":810, "path":"rotation" } } @@ -5406,7 +5322,7 @@ { "sampler":0, "target":{ - "node":815, + "node":811, "path":"rotation" } } diff --git a/src/character_controller/mod.rs b/src/character_controller/mod.rs index f9e5651..c960cd4 100644 --- a/src/character_controller/mod.rs +++ b/src/character_controller/mod.rs @@ -1,8 +1,9 @@ -use avian3d::prelude::*; +use avian3d::{math::Quaternion, prelude::*}; use bevy::prelude::*; use crate::{ GRAVITY, + enemy::Enemy, game_flow::states::InGameState, player::{Player, camera::components::ViewModelCamera}, }; @@ -37,34 +38,42 @@ pub enum MovementAction { Jump, } -/// Contains all needed components for a character that should be controlled by the player #[derive(Bundle)] pub struct CharacterControllerBundle { - velocity: LinearVelocity, rigid_body: RigidBody, collider: Collider, - grounded: Grounded, locked_axes: LockedAxes, movement_state: MovementState, colliding_entities: CollidingEntities, + grounded: Grounded, + ground_caster: ShapeCaster, } impl Default for CharacterControllerBundle { fn default() -> Self { Self { - velocity: LinearVelocity::ZERO, rigid_body: RigidBody::Kinematic, collider: Collider::capsule( CHARACTER_CAPSULE_RADIUS, CHARACTER_CAPSULE_LENGTH, ), - grounded: Grounded::default(), locked_axes: LockedAxes::new() .lock_rotation_x() .lock_rotation_y() .lock_rotation_z(), - movement_state: MovementState(MovementStateEnum::Idle), colliding_entities: CollidingEntities::default(), + movement_state: MovementState(MovementStateEnum::Idle), + grounded: Grounded(true), + ground_caster: ShapeCaster::new( + Collider::capsule( + CHARACTER_CAPSULE_RADIUS, + CHARACTER_CAPSULE_LENGTH, + ), + Vec3::ZERO, + Quaternion::default(), + Dir3::NEG_Y, + ) + .with_max_distance(0.2), } } } @@ -90,6 +99,7 @@ impl Plugin for CharacterControllerPlugin { apply_gravity_over_time, handle_keyboard_input_for_player, handle_movement_actions_for_player, + debug_remove_grounded_from_enemies, ) .run_if(in_state(InGameState::Playing)), ) @@ -242,7 +252,7 @@ fn handle_movement_actions_for_player( let player_y = player_transform.translation.y; let difference_y = hit_down_y - player_y; if difference_y.abs() < 0.3 { - debug!("Snapping player to slope"); + info!("Snapping player to slope"); player_velocity.y = difference_y / time.delta_secs(); } @@ -271,33 +281,28 @@ fn handle_movement_actions_for_player( } } +/// Updates the [`Grounded`] status for character controllers. fn update_on_ground( - query: Query<(&Transform, Entity, &mut LinearVelocity, &mut Grounded)>, - spatial_query: SpatialQuery, + mut query: Query<( + &ShapeHits, + &Rotation, + &mut Grounded, + &mut LinearVelocity, + )>, ) { - for (transform, entity, mut velocity, mut grounded) in query { - let on_ground = spatial_query - .cast_shape( - &Collider::capsule( - CHARACTER_CAPSULE_RADIUS, - CHARACTER_CAPSULE_LENGTH, - ), - transform.translation, - transform.rotation, - Dir3::NEG_Y, - &ShapeCastConfig { - max_distance: 0.1, - ..default() - }, - &SpatialQueryFilter::default().with_excluded_entities([entity]), - ) - .is_some(); + for (hits, rotation, mut grounded, mut velocity) in &mut query { + // The character is grounded if the shape caster has a hit with a normal + // that isn't too steep. + let on_ground = hits.iter().any(|hit| { + (rotation * -hit.normal2).angle_between(Vec3::Y).abs() + <= MAX_SLOPE_ANGLE + }); if grounded.0 != on_ground { grounded.0 = on_ground; } if on_ground && velocity.y <= 0.0 { - velocity.y = 0.0; + velocity.y = 0.0 } } } @@ -313,3 +318,15 @@ fn apply_gravity_over_time( } } } + +fn debug_remove_grounded_from_enemies( + mut commands: Commands, + keyboard_input: Res>, + enemy_query: Query>, +) { + if keyboard_input.just_pressed(KeyCode::KeyU) { + for enemy in enemy_query { + commands.entity(enemy).remove::(); + } + } +} diff --git a/src/enemy/spawn/mod.rs b/src/enemy/spawn/mod.rs index 577808b..c864186 100644 --- a/src/enemy/spawn/mod.rs +++ b/src/enemy/spawn/mod.rs @@ -8,7 +8,10 @@ use crate::{ }, nav_mesh_pathfinding::{ArchipelagoRef, ENEMY_AGENT_RADIUS}, }; -use avian3d::{math::PI, prelude::*}; +use avian3d::{ + math::{PI, Quaternion}, + prelude::*, +}; use bevy::prelude::*; use bevy_landmass::{ Agent, Agent3dBundle, AgentSettings, AgentTarget3d, ArchipelagoRef3d, @@ -145,6 +148,16 @@ fn handle_spawn_enemies_at_enemy_spawn_locations_message( Visibility::Visible, LinearVelocity::ZERO, CollidingEntities::default(), + ShapeCaster::new( + Collider::capsule( + CHARACTER_CAPSULE_RADIUS, + CHARACTER_CAPSULE_LENGTH, + ), + Vec3::ZERO, + Quaternion::default(), + Dir3::NEG_Y, + ) + .with_max_distance(0.2), )) .with_child(( Transform { @@ -172,13 +185,13 @@ fn handle_spawn_enemies_at_enemy_spawn_locations_message( archipelago_ref.0, ), settings: AgentSettings { - desired_speed: 2.0, - max_speed: 2.0, - radius: ENEMY_AGENT_RADIUS, + desired_speed: WALK_VELOCITY, + max_speed: RUN_VELOCITY, + radius: ENEMY_AGENT_RADIUS + 0.1, }, }, AgentTarget3d::None, - Transform::from_xyz(0.0, -0.6, 0.0), + Transform::from_xyz(0.0, -0.8, 0.0), AgentEnemyEntityPointer(enemy_entity), )); } diff --git a/src/game_flow/game_mode.rs b/src/game_flow/game_mode.rs index 149797e..43a649a 100644 --- a/src/game_flow/game_mode.rs +++ b/src/game_flow/game_mode.rs @@ -70,7 +70,10 @@ fn handle_start_game_mode_event( current_game_mode: Res>, ) { for _ in message_reader.read() { - info!("Got start game mode message"); + info!( + "Got start game mode message, updating states to reflect changes \ + and spawning enemies and players." + ); next_app_state.set(AppState::InGame); next_main_menu_state.set(MainMenuState::None); next_in_game_state.set(InGameState::Playing); diff --git a/src/game_flow/systems.rs b/src/game_flow/systems.rs index 1b36bf0..fa42d50 100644 --- a/src/game_flow/systems.rs +++ b/src/game_flow/systems.rs @@ -150,7 +150,10 @@ pub fn check_navmesh_ready( pub fn on_game_loading_state_nav_mesh_ready( mut start_game_mode_message_writer: MessageWriter, ) { - info!("Okay everything is ready now, write SpawnGameMode message!"); + info!( + "Entered nav_mesh_ready loading state, everything is ready now. \ + Writing StartGameModeMessage" + ); start_game_mode_message_writer.write(StartGameModeMessage); } @@ -158,6 +161,6 @@ pub fn handle_playing_state_enter( mut commands: Commands, main_menu_camera: Single>, ) { - info!("got start game mode event, despawning main menu camera"); + info!("Entered playing state, despawning main menu camera"); commands.entity(*main_menu_camera).despawn(); } diff --git a/src/nav_mesh_pathfinding/mod.rs b/src/nav_mesh_pathfinding/mod.rs index a074cbe..28dba49 100644 --- a/src/nav_mesh_pathfinding/mod.rs +++ b/src/nav_mesh_pathfinding/mod.rs @@ -1,12 +1,17 @@ use avian_rerecast::AvianBackendPlugin; +use avian3d::prelude::*; use bevy::prelude::*; -use bevy_landmass::{debug::Landmass3dDebugPlugin, prelude::*}; +use bevy_landmass::{Agent3d, debug::Landmass3dDebugPlugin, prelude::*}; use bevy_rerecast::{debug::DetailNavmeshGizmo, prelude::*}; use landmass_rerecast::{ Island3dBundle, LandmassRerecastPlugin, NavMeshHandle3d, }; -use crate::game_flow::states::GameLoadingState; +use crate::{ + character_controller::MAX_SLOPE_ANGLE, + enemy::{Enemy, spawn::AgentEnemyEntityPointer}, + game_flow::states::GameLoadingState, +}; pub const ENEMY_AGENT_RADIUS: f32 = 0.3; @@ -23,7 +28,7 @@ impl Plugin for NavMeshPathfindingPlugin { OnEnter(GameLoadingState::CollidersReady), generate_navmesh_when_map_colliders_ready, ); - app.add_systems(Update, update_agent_velocity); + app.add_systems(Update, (update_agent_velocity, snap_agent_to_floor)); } } @@ -79,7 +84,54 @@ fn update_agent_velocity( for (mut agent_velocity, desired_velocity, agent_state) in agent_query.iter_mut() { - debug!("Agent state: {:?}", agent_state); + info!("Agent state: {:?}", agent_state,); agent_velocity.velocity = desired_velocity.velocity(); } } + +fn snap_agent_to_floor( + query: Query< + (Entity, &Transform, &mut LinearVelocity, &ShapeHits), + With, + >, + time: Res