Fly controller (#26)

This commit is contained in:
extrawurst
2025-04-12 17:16:41 +02:00
committed by GitHub
parent dab4ea0656
commit 9b3765c41d
10 changed files with 550 additions and 325 deletions

View File

@@ -0,0 +1,178 @@
use super::{ControllerSet, Controls, controller_common::MovementSpeedFactor};
use crate::{GameState, player::PlayerBodyMesh};
use avian3d::{math::*, prelude::*};
use bevy::{ecs::query::Has, prelude::*};
use super::controller_common::{
CharacterController, ControllerGravity, Grounded, JumpImpulse, MaxSlopeAngle, MovementSpeed,
PlayerMovement, reset_upon_switch,
};
pub struct CharacterControllerPlugin;
impl Plugin for CharacterControllerPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(
reset_upon_switch,
set_movement_flag,
update_grounded,
apply_gravity,
rotate_view_keyboard,
rotate_view_gamepad, // todo consider merging these two
movement,
)
.chain()
.in_set(ControllerSet::ApplyControlsRun)
.run_if(in_state(GameState::Playing)), // todo check if we can make this less viral
);
}
}
/// Updates the [`Grounded`] status for character controllers.
fn update_grounded(
mut commands: Commands,
mut query: Query<
(Entity, &ShapeHits, &Rotation, Option<&MaxSlopeAngle>),
With<CharacterController>,
>,
) {
for (entity, hits, rotation, max_slope_angle) in &mut query {
// The character is grounded if the shape caster has a hit with a normal
// that isn't too steep.
let is_grounded = hits.iter().any(|hit| {
if let Some(angle) = max_slope_angle {
(rotation * -hit.normal2).angle_between(Vector::Y).abs() <= angle.0
} else {
true
}
});
if is_grounded {
debug!("grounded");
commands.entity(entity).insert(Grounded);
} else {
debug!("not grounded");
commands.entity(entity).remove::<Grounded>();
}
}
}
/// Sets the movement flag, which is an indicator for the rig animation and the braking system.
fn set_movement_flag(mut player_movement: ResMut<PlayerMovement>, controls: Res<Controls>) {
let mut direction = controls.keyboard_state.move_dir;
let deadzone = 0.2;
if let Some(gamepad) = controls.gamepad_state {
direction += gamepad.move_dir;
}
if player_movement.any_direction {
if direction.length_squared() < deadzone {
player_movement.any_direction = false;
}
} else if direction.length_squared() > deadzone {
player_movement.any_direction = true;
}
}
fn rotate_view_gamepad(
controls: Res<Controls>,
mut player: Query<&mut Transform, With<PlayerBodyMesh>>,
) {
let Some(gamepad) = controls.gamepad_state else {
return;
};
if gamepad.view_mode {
return;
}
for mut tr in &mut player {
tr.rotate_y(gamepad.look_dir.x * -0.001);
}
}
fn rotate_view_keyboard(
mut controls: ResMut<Controls>,
mut player: Query<&mut Transform, With<PlayerBodyMesh>>,
) {
if controls.keyboard_state.view_mode {
return;
}
for mut tr in &mut player {
tr.rotate_y(controls.keyboard_state.look_dir.x * -0.001);
}
controls.keyboard_state.look_dir = Vec2::ZERO;
}
/// Responds to [`MovementAction`] events and moves character controllers accordingly.
fn movement(
controls: Res<Controls>,
mut controllers: Query<(
&MovementSpeed,
&MovementSpeedFactor,
&JumpImpulse,
&mut LinearVelocity,
Has<Grounded>,
)>,
rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
mut jump_used: Local<bool>,
) {
let mut direction = controls.keyboard_state.move_dir;
let mut jump_requested = controls.keyboard_state.jump;
if let Some(gamepad) = controls.gamepad_state {
direction += gamepad.move_dir;
jump_requested |= gamepad.jump;
}
direction = direction.normalize_or_zero();
for (movement_speed, factor, jump_impulse, mut linear_velocity, is_grounded) in &mut controllers
{
let mut direction = direction.extend(0.0).xzy();
if let Some(ref rig_transform) = rig_transform_q {
direction =
(rig_transform.forward() * direction.z) + (rig_transform.right() * direction.x);
}
linear_velocity.x = -direction.x * movement_speed.0 * factor.0;
linear_velocity.z = -direction.z * movement_speed.0 * factor.0;
if is_grounded && jump_requested && !*jump_used {
linear_velocity.y = jump_impulse.0;
debug!("jump");
*jump_used = true;
}
if !controls.keyboard_state.jump
&& !controls
.gamepad_state
.map(|state| state.jump)
.unwrap_or_default()
{
*jump_used = false;
}
}
}
/// Applies [`ControllerGravity`] to character controllers.
fn apply_gravity(
time: Res<Time>,
mut controllers: Query<(&ControllerGravity, &mut LinearVelocity, Option<&Grounded>)>,
) {
let delta_time = time.delta_secs();
for (gravity, mut linear_velocity, grounded) in &mut controllers {
if grounded.is_none() {
linear_velocity.0 += gravity.0 * delta_time;
}
}
}