Files
time_travel/src/avian.rs
T
2026-06-03 15:49:13 -05:00

136 lines
4.4 KiB
Rust

use avian2d::{math::*, prelude::*};
use bevy::prelude::*;
pub struct CharacterControllerPlugin;
impl Plugin for CharacterControllerPlugin {
fn build(&self, app: &mut App) {
app.add_message::<MovementAction>()
.add_systems(Update, ((keyboard_input, gamepad_input), movement).chain());
}
}
/// A [`Message`] written for a movement input action.
#[derive(Message, Default, Copy, Clone)]
pub struct MovementAction(Vec2);
/// A marker component indicating that an entity is using a character controller.
#[derive(Component, Default, Copy, Clone)]
pub struct CharacterController;
/// The max speed for a CharacterController.
#[derive(Component, Default, Copy, Clone)]
pub struct MaxSpeed(Scalar);
/// The max acceleration per second for a CharacterController.
#[derive(Component, Default, Copy, Clone)]
pub struct MaxAcceleration(Scalar);
/// Whether inputs are enabled for a CharacterController.
#[derive(Component, Default, Copy, Clone)]
pub struct InputEnabled(pub bool);
/// A bundle that contains the components needed for a basic
/// kinematic character controller.
#[derive(Bundle)]
pub struct CharacterControllerBundle {
character_controller: CharacterController,
body: RigidBody,
collider: Collider,
speed: MaxSpeed,
acceleration: MaxAcceleration,
enabled: InputEnabled,
}
impl CharacterControllerBundle {
pub fn new(collider: Collider) -> Self {
Self {
character_controller: CharacterController,
body: RigidBody::Dynamic,
collider,
speed: MaxSpeed(300.),
acceleration: MaxAcceleration(50000.),
enabled: InputEnabled(true),
}
}
}
/// Sends [`MovementAction`] events based on keyboard input.
fn keyboard_input(
mut movement_writer: MessageWriter<MovementAction>,
keyboard_input: Res<ButtonInput<KeyCode>>,
) {
let left = keyboard_input.any_pressed([KeyCode::KeyA, KeyCode::ArrowLeft]);
let right = keyboard_input.any_pressed([KeyCode::KeyD, KeyCode::ArrowRight]);
let up = keyboard_input.any_pressed([KeyCode::KeyW, KeyCode::ArrowUp]);
let down = keyboard_input.any_pressed([KeyCode::KeyS, KeyCode::ArrowDown]);
let x = right as i8 - left as i8;
let y = up as i8 - down as i8;
let dir = Vec2::new(x as f32, y as f32);
if let Some(dir) = dir.try_normalize() {
movement_writer.write(MovementAction(dir));
}
}
/// Sends [`MovementAction`] events based on gamepad input.
fn gamepad_input(mut movement_writer: MessageWriter<MovementAction>, gamepads: Query<&Gamepad>) {
for gamepad in gamepads.iter() {
if let (Some(x), Some(y)) = (
gamepad.get(GamepadAxis::LeftStickX),
gamepad.get(GamepadAxis::LeftStickY),
) {
let mut dir = Vec2::new(x, y);
let len = dir.length();
if len == 0. {
continue;
}
if len > 1. {
dir = dir.normalize();
}
movement_writer.write(MovementAction(dir));
}
}
}
/// Responds to [`MovementAction`] events and moves character controllers accordingly.
fn movement(
time: Res<Time>,
mut movement_reader: MessageReader<MovementAction>,
mut controllers: Query<(&MaxSpeed, &MaxAcceleration, &mut LinearVelocity, &InputEnabled)>,
) {
// Precision is adjusted so that the example works with
// both the `f32` and `f64` features. Otherwise you don't need this.
let delta_time = time.delta_secs_f64().adjust_precision();
for (max_speed, max_acceleration, mut linear_velocity, enabled) in &mut controllers {
if enabled.0 {
// let len = movement_reader.len();
// while len > 1 {
// warn!("Extra movement message. Ignoring");
// println!("{}", len);
// let _ = movement_reader.read().take(len - 1).count();
// }
let target = movement_reader
.read()
.last()
.map(|ma| ma.0)
.unwrap_or_default()
* max_speed.0;
let mut delta = target - **linear_velocity;
let delta_len = delta.length();
let max_acceleration = max_acceleration.0 * delta_time;
if delta_len > max_acceleration {
delta = delta.normalize() * max_acceleration;
}
**linear_velocity += delta;
} else {
**linear_velocity = Vec2::ZERO;
}
}
}