more avian workd
This commit is contained in:
parent
42651aa4be
commit
f80b7e380a
172
src/avian.rs
172
src/avian.rs
@ -6,15 +6,7 @@ pub struct CharacterControllerPlugin;
|
|||||||
impl Plugin for CharacterControllerPlugin {
|
impl Plugin for CharacterControllerPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_message::<MovementAction>()
|
app.add_message::<MovementAction>()
|
||||||
.add_systems(
|
.add_systems(Update, ((keyboard_input, gamepad_input), movement).chain())
|
||||||
Update,
|
|
||||||
(
|
|
||||||
(keyboard_input, gamepad_input),
|
|
||||||
movement,
|
|
||||||
apply_movement_damping,
|
|
||||||
)
|
|
||||||
.chain(),
|
|
||||||
)
|
|
||||||
.add_systems(
|
.add_systems(
|
||||||
// Run collision handling after collision detection.
|
// Run collision handling after collision detection.
|
||||||
//
|
//
|
||||||
@ -27,31 +19,20 @@ impl Plugin for CharacterControllerPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A [`Message`] written for a movement input action.
|
/// A [`Message`] written for a movement input action.
|
||||||
#[derive(Message)]
|
#[derive(Message, Default, Copy, Clone)]
|
||||||
pub struct MovementAction(Vec2);
|
pub struct MovementAction(Vec2);
|
||||||
|
|
||||||
/// A marker component indicating that an entity is using a character controller.
|
/// A marker component indicating that an entity is using a character controller.
|
||||||
#[derive(Component)]
|
#[derive(Component, Default, Copy, Clone)]
|
||||||
pub struct CharacterController;
|
pub struct CharacterController;
|
||||||
|
|
||||||
/// A marker component indicating that an entity is on the ground.
|
/// The max speed for a CharacterController.
|
||||||
#[derive(Component)]
|
#[derive(Component, Default, Copy, Clone)]
|
||||||
#[component(storage = "SparseSet")]
|
pub struct MaxSpeed(Scalar);
|
||||||
pub struct Grounded;
|
|
||||||
|
|
||||||
/// The acceleration used for character movement.
|
/// The max acceleration per second for a CharacterController.
|
||||||
#[derive(Component)]
|
#[derive(Component, Default, Copy, Clone)]
|
||||||
pub struct MovementAcceleration(Scalar);
|
pub struct MaxAcceleration(Scalar);
|
||||||
|
|
||||||
/// The damping factor used for slowing down movement.
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct MovementDampingFactor(Scalar);
|
|
||||||
|
|
||||||
/// The maximum angle a slope can have for a character controller
|
|
||||||
/// to be able to climb and jump. If the slope is steeper than this angle,
|
|
||||||
/// the character will slide down.
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct MaxSlopeAngle(Scalar);
|
|
||||||
|
|
||||||
/// A bundle that contains the components needed for a basic
|
/// A bundle that contains the components needed for a basic
|
||||||
/// kinematic character controller.
|
/// kinematic character controller.
|
||||||
@ -60,59 +41,20 @@ pub struct CharacterControllerBundle {
|
|||||||
character_controller: CharacterController,
|
character_controller: CharacterController,
|
||||||
body: RigidBody,
|
body: RigidBody,
|
||||||
collider: Collider,
|
collider: Collider,
|
||||||
ground_caster: ShapeCaster,
|
speed: MaxSpeed,
|
||||||
movement: MovementBundle,
|
acceleration: MaxAcceleration,
|
||||||
}
|
|
||||||
|
|
||||||
/// A bundle that contains components for character movement.
|
|
||||||
#[derive(Bundle)]
|
|
||||||
pub struct MovementBundle {
|
|
||||||
acceleration: MovementAcceleration,
|
|
||||||
damping: MovementDampingFactor,
|
|
||||||
max_slope_angle: MaxSlopeAngle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MovementBundle {
|
|
||||||
pub const fn new(acceleration: Scalar, damping: Scalar, max_slope_angle: Scalar) -> Self {
|
|
||||||
Self {
|
|
||||||
acceleration: MovementAcceleration(acceleration),
|
|
||||||
damping: MovementDampingFactor(damping),
|
|
||||||
max_slope_angle: MaxSlopeAngle(max_slope_angle),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for MovementBundle {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new(30.0, 0.99, PI * 0.45)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharacterControllerBundle {
|
impl CharacterControllerBundle {
|
||||||
pub fn new(collider: Collider) -> Self {
|
pub fn new(collider: Collider) -> Self {
|
||||||
// Create shape caster as a slightly smaller version of collider
|
|
||||||
let mut caster_shape = collider.clone();
|
|
||||||
caster_shape.set_scale(Vector::ONE * 0.99, 10);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
character_controller: CharacterController,
|
character_controller: CharacterController,
|
||||||
body: RigidBody::Kinematic,
|
body: RigidBody::Kinematic,
|
||||||
collider,
|
collider,
|
||||||
ground_caster: ShapeCaster::new(caster_shape, Vector::ZERO, 0.0, Dir2::NEG_Y)
|
speed: MaxSpeed(10.),
|
||||||
.with_max_distance(10.0),
|
acceleration: MaxAcceleration(10.),
|
||||||
movement: MovementBundle::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_movement(
|
|
||||||
mut self,
|
|
||||||
acceleration: Scalar,
|
|
||||||
damping: Scalar,
|
|
||||||
max_slope_angle: Scalar,
|
|
||||||
) -> Self {
|
|
||||||
self.movement = MovementBundle::new(acceleration, damping, max_slope_angle);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends [`MovementAction`] events based on keyboard input.
|
/// Sends [`MovementAction`] events based on keyboard input.
|
||||||
@ -159,43 +101,31 @@ fn gamepad_input(mut movement_writer: MessageWriter<MovementAction>, gamepads: Q
|
|||||||
fn movement(
|
fn movement(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut movement_reader: MessageReader<MovementAction>,
|
mut movement_reader: MessageReader<MovementAction>,
|
||||||
mut controllers: Query<(
|
mut controllers: Query<(&MaxSpeed, &MaxAcceleration, &mut LinearVelocity)>,
|
||||||
&MovementAcceleration,
|
|
||||||
&MovementDampingFactor,
|
|
||||||
&mut LinearVelocity,
|
|
||||||
)>,
|
|
||||||
) {
|
) {
|
||||||
// Precision is adjusted so that the example works with
|
// Precision is adjusted so that the example works with
|
||||||
// both the `f32` and `f64` features. Otherwise you don't need this.
|
// both the `f32` and `f64` features. Otherwise you don't need this.
|
||||||
let delta_time = time.delta_secs_f64().adjust_precision();
|
let delta_time = time.delta_secs_f64().adjust_precision();
|
||||||
|
|
||||||
for (movement_acceleration, dampening, mut linear_velocity) in &mut controllers {
|
for (max_speed, max_acceleration, mut linear_velocity) in &mut controllers {
|
||||||
if movement_reader.is_empty() {
|
while movement_reader.len() > 1 {
|
||||||
linear_velocity.x *= 1.0 / (1.0 + damping_factor.0 * delta_time);
|
movement_reader.read();
|
||||||
}
|
}
|
||||||
for event in movement_reader.read() {
|
|
||||||
match event {
|
|
||||||
MovementAction::Move(direction) => {
|
|
||||||
linear_velocity.x += *direction * movement_acceleration.0 * delta_time;
|
|
||||||
}
|
|
||||||
MovementAction::Jump => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Slows down movement in the X direction.
|
let target = movement_reader
|
||||||
fn apply_movement_damping(
|
.read()
|
||||||
time: Res<Time>,
|
.next()
|
||||||
mut query: Query<(&MovementDampingFactor, &mut LinearVelocity)>,
|
.map(|ma| ma.0)
|
||||||
) {
|
.unwrap_or_default()
|
||||||
// Precision is adjusted so that the example works with
|
* max_speed.0;
|
||||||
// both the `f32` and `f64` features. Otherwise you don't need this.
|
|
||||||
let delta_time = time.delta_secs_f64().adjust_precision();
|
|
||||||
|
|
||||||
for (damping_factor, mut linear_velocity) in &mut query {
|
let mut delta = target - **linear_velocity;
|
||||||
// We could use `LinearDamping`, but we don't want to dampen movement along the Y axis
|
let delta_len = delta.length();
|
||||||
linear_velocity.x *= 1.0 / (1.0 + damping_factor.0 * delta_time);
|
let max_acceleration = max_acceleration.0 * delta_time;
|
||||||
|
if delta_len > max_acceleration {
|
||||||
|
delta = delta.normalize() * max_acceleration;
|
||||||
|
}
|
||||||
|
**linear_velocity = delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +142,7 @@ fn kinematic_controller_collisions(
|
|||||||
bodies: Query<&RigidBody>,
|
bodies: Query<&RigidBody>,
|
||||||
collider_rbs: Query<&ColliderOf, Without<Sensor>>,
|
collider_rbs: Query<&ColliderOf, Without<Sensor>>,
|
||||||
mut character_controllers: Query<
|
mut character_controllers: Query<
|
||||||
(&mut Position, &mut LinearVelocity, Option<&MaxSlopeAngle>),
|
(&mut Position, &mut LinearVelocity),
|
||||||
(With<RigidBody>, With<CharacterController>),
|
(With<RigidBody>, With<CharacterController>),
|
||||||
>,
|
>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
@ -233,7 +163,7 @@ fn kinematic_controller_collisions(
|
|||||||
let character_rb: RigidBody;
|
let character_rb: RigidBody;
|
||||||
let is_other_dynamic: bool;
|
let is_other_dynamic: bool;
|
||||||
|
|
||||||
let (mut position, mut linear_velocity, max_slope_angle) =
|
let (mut position, mut linear_velocity) =
|
||||||
if let Ok(character) = character_controllers.get_mut(rb1) {
|
if let Ok(character) = character_controllers.get_mut(rb1) {
|
||||||
is_first = true;
|
is_first = true;
|
||||||
character_rb = *bodies.get(rb1).unwrap();
|
character_rb = *bodies.get(rb1).unwrap();
|
||||||
@ -277,39 +207,7 @@ fn kinematic_controller_collisions(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if the slope is climbable or if it's too steep to walk on.
|
|
||||||
let slope_angle = normal.angle_to(Vector::Y);
|
|
||||||
let climbable = max_slope_angle.is_some_and(|angle| slope_angle.abs() <= angle.0);
|
|
||||||
|
|
||||||
if deepest_penetration > 0.0 {
|
if deepest_penetration > 0.0 {
|
||||||
// If the slope is climbable, snap the velocity so that the character
|
|
||||||
// up and down the surface smoothly.
|
|
||||||
if climbable {
|
|
||||||
// Points either left or right depending on which side the normal is leaning on.
|
|
||||||
// (This could be simplified for 2D, but this approach is dimension-agnostic)
|
|
||||||
let normal_direction_x =
|
|
||||||
normal.reject_from_normalized(Vector::Y).normalize_or_zero();
|
|
||||||
|
|
||||||
// The movement speed along the direction above.
|
|
||||||
let linear_velocity_x = linear_velocity.dot(normal_direction_x);
|
|
||||||
|
|
||||||
// Snap the Y speed based on the speed at which the character is moving
|
|
||||||
// up or down the slope, and how steep the slope is.
|
|
||||||
//
|
|
||||||
// A 2D visualization of the slope, the contact normal, and the velocity components:
|
|
||||||
//
|
|
||||||
// ╱
|
|
||||||
// normal ╱
|
|
||||||
// * ╱
|
|
||||||
// │ * ╱ velocity_x
|
|
||||||
// │ * - - - - - -
|
|
||||||
// │ * | velocity_y
|
|
||||||
// │ * |
|
|
||||||
// *───────────────────*
|
|
||||||
|
|
||||||
let max_y_speed = -linear_velocity_x * slope_angle.tan();
|
|
||||||
linear_velocity.y = linear_velocity.y.max(max_y_speed);
|
|
||||||
} else {
|
|
||||||
// The character is intersecting an unclimbable object, like a wall.
|
// The character is intersecting an unclimbable object, like a wall.
|
||||||
// We want the character to slide along the surface, similarly to
|
// We want the character to slide along the surface, similarly to
|
||||||
// a collide-and-slide algorithm.
|
// a collide-and-slide algorithm.
|
||||||
@ -322,7 +220,6 @@ fn kinematic_controller_collisions(
|
|||||||
// Slide along the surface, rejecting the velocity along the contact normal.
|
// Slide along the surface, rejecting the velocity along the contact normal.
|
||||||
let impulse = linear_velocity.reject_from_normalized(normal);
|
let impulse = linear_velocity.reject_from_normalized(normal);
|
||||||
linear_velocity.0 = impulse;
|
linear_velocity.0 = impulse;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// The character is not yet intersecting the other object,
|
// The character is not yet intersecting the other object,
|
||||||
// but the narrow phase detected a speculative collision.
|
// but the narrow phase detected a speculative collision.
|
||||||
@ -343,15 +240,10 @@ fn kinematic_controller_collisions(
|
|||||||
let mut impulse = impulse_magnitude * normal;
|
let mut impulse = impulse_magnitude * normal;
|
||||||
|
|
||||||
// Apply the impulse differently depending on the slope angle.
|
// Apply the impulse differently depending on the slope angle.
|
||||||
if climbable {
|
|
||||||
// Avoid sliding down slopes.
|
|
||||||
linear_velocity.y -= impulse.y.min(0.0);
|
|
||||||
} else {
|
|
||||||
// Avoid climbing up walls.
|
// Avoid climbing up walls.
|
||||||
impulse.y = impulse.y.max(0.0);
|
impulse.y = impulse.y.max(0.0);
|
||||||
linear_velocity.0 -= impulse;
|
linear_velocity.0 -= impulse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/main.rs
11
src/main.rs
@ -1,12 +1,11 @@
|
|||||||
|
use crate::avian::{CharacterControllerBundle, CharacterControllerPlugin};
|
||||||
use avian2d::{
|
use avian2d::{
|
||||||
PhysicsPlugins,
|
PhysicsPlugins,
|
||||||
math::{Scalar, Vector},
|
math::Vector,
|
||||||
prelude::{Collider, Gravity, RigidBody},
|
prelude::{Collider, Gravity, RigidBody},
|
||||||
};
|
};
|
||||||
use bevy::{camera::ScalingMode, color::palettes::css::GREEN, prelude::*};
|
use bevy::{camera::ScalingMode, color::palettes::css::GREEN, prelude::*};
|
||||||
|
|
||||||
use crate::avian::{CharacterControllerBundle, CharacterControllerPlugin};
|
|
||||||
|
|
||||||
pub mod avian;
|
pub mod avian;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -42,11 +41,7 @@ fn setup(
|
|||||||
Mesh2d(meshes.add(Capsule2d::new(12.5, 20.0))),
|
Mesh2d(meshes.add(Capsule2d::new(12.5, 20.0))),
|
||||||
MeshMaterial2d(materials.add(Color::srgb(0.2, 0.7, 0.9))),
|
MeshMaterial2d(materials.add(Color::srgb(0.2, 0.7, 0.9))),
|
||||||
Transform::from_xyz(0.0, -100.0, 0.0),
|
Transform::from_xyz(0.0, -100.0, 0.0),
|
||||||
CharacterControllerBundle::new(Collider::capsule(12.5, 20.0)).with_movement(
|
CharacterControllerBundle::new(Collider::capsule(12.5, 20.0)),
|
||||||
1250.0,
|
|
||||||
10.0,
|
|
||||||
(30.0 as Scalar).to_radians(),
|
|
||||||
),
|
|
||||||
));
|
));
|
||||||
|
|
||||||
// A cube to move around
|
// A cube to move around
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user