use avian2d::{math::*, prelude::*}; use bevy::prelude::*; pub struct CharacterControllerPlugin; impl Plugin for CharacterControllerPlugin { fn build(&self, app: &mut App) { app.add_message::() .add_systems(Update, ((keyboard_input, gamepad_input), movement).chain()) .add_systems( // Run collision handling after collision detection. // // NOTE: The collision implementation here is very basic and a bit buggy. // A collide-and-slide algorithm would likely work better. PhysicsSchedule, kinematic_controller_collisions.in_set(NarrowPhaseSystems::Last), ); } } /// 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); /// 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, } impl CharacterControllerBundle { pub fn new(collider: Collider) -> Self { Self { character_controller: CharacterController, body: RigidBody::Kinematic, collider, speed: MaxSpeed(100.), acceleration: MaxAcceleration(100.), } } } /// Sends [`MovementAction`] events based on keyboard input. fn keyboard_input( mut movement_writer: MessageWriter, keyboard_input: Res>, ) { 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, 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