179 lines
5.3 KiB
Rust
179 lines
5.3 KiB
Rust
use avian3d::{math::*, prelude::*};
|
|
use bevy::prelude::*;
|
|
|
|
use crate::{
|
|
GameState, control::collisions::kinematic_controller_collisions, player::PlayerBodyMesh,
|
|
};
|
|
|
|
use super::ControllerSwitchEvent;
|
|
|
|
pub fn plugin(app: &mut App) {
|
|
app.init_resource::<PlayerMovement>();
|
|
app.init_resource::<MovementSettings>();
|
|
|
|
app.register_type::<MovementSettings>();
|
|
app.register_type::<MovementDampingFactor>();
|
|
app.register_type::<JumpImpulse>();
|
|
app.register_type::<ControllerGravity>();
|
|
app.register_type::<MovementSpeed>();
|
|
|
|
app.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(NarrowPhaseSet::Last)
|
|
.run_if(in_state(GameState::Playing)),
|
|
);
|
|
}
|
|
|
|
/// Reset the pitch and velocity of the character if the controller was switched.
|
|
pub fn reset_upon_switch(
|
|
mut event_controller_switch: EventReader<ControllerSwitchEvent>,
|
|
mut rig_transform_q: Option<Single<&mut Transform, With<PlayerBodyMesh>>>,
|
|
mut controllers: Query<&mut LinearVelocity>,
|
|
) {
|
|
for _ in event_controller_switch.read() {
|
|
// Reset velocity
|
|
for mut linear_velocity in &mut controllers {
|
|
linear_velocity.0 = Vec3::ZERO;
|
|
}
|
|
|
|
// Reset pitch but keep yaw the same
|
|
if let Some(ref mut rig_transform) = rig_transform_q {
|
|
let euler_rot = rig_transform.rotation.to_euler(EulerRot::YXZ);
|
|
let yaw = euler_rot.0;
|
|
rig_transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, 0.0, 0.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Resource, Default)]
|
|
pub struct PlayerMovement {
|
|
pub any_direction: bool,
|
|
}
|
|
|
|
/// A marker component indicating that an entity is using a character controller.
|
|
#[derive(Component)]
|
|
pub struct CharacterController;
|
|
|
|
/// A marker component indicating that an entity is on the ground.
|
|
#[derive(Component)]
|
|
#[component(storage = "SparseSet")]
|
|
pub struct Grounded;
|
|
|
|
#[derive(Resource, Reflect)]
|
|
#[reflect(Resource)]
|
|
pub struct MovementSettings {
|
|
pub damping_normal: f32,
|
|
pub damping_brake: f32,
|
|
pub damping_brake_air: f32,
|
|
}
|
|
|
|
// todo some duplicate with player.rs settings
|
|
impl Default for MovementSettings {
|
|
fn default() -> Self {
|
|
Self {
|
|
damping_normal: 1.0,
|
|
damping_brake: 30.0,
|
|
damping_brake_air: 1.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Component, Reflect)]
|
|
#[reflect(Component)]
|
|
pub struct MovementSpeedFactor(pub f32);
|
|
|
|
/// The speed used for character movement.
|
|
#[derive(Component, Reflect)]
|
|
#[reflect(Component)]
|
|
pub struct MovementSpeed(pub Scalar);
|
|
|
|
/// The damping factor used for slowing down movement.
|
|
#[derive(Component, Reflect)]
|
|
#[reflect(Component)]
|
|
pub struct MovementDampingFactor(pub Scalar);
|
|
|
|
/// The strength of a jump.
|
|
#[derive(Component, Reflect)]
|
|
#[reflect(Component)]
|
|
pub struct JumpImpulse(pub Scalar);
|
|
|
|
/// The gravitational acceleration used for a character controller.
|
|
#[derive(Component, Reflect)]
|
|
#[reflect(Component)]
|
|
pub struct ControllerGravity(pub Vector);
|
|
|
|
/// 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, Reflect)]
|
|
#[reflect(Component)]
|
|
pub struct MaxSlopeAngle(pub Scalar);
|
|
|
|
/// A bundle that contains the components needed for a basic
|
|
/// kinematic character controller.
|
|
#[derive(Bundle)]
|
|
pub struct CharacterControllerBundle {
|
|
character_controller: CharacterController,
|
|
rigid_body: RigidBody,
|
|
collider: Collider,
|
|
ground_caster: ShapeCaster,
|
|
gravity: ControllerGravity,
|
|
movement: MovementBundle,
|
|
collision_events: CollisionEventsEnabled,
|
|
}
|
|
|
|
/// A bundle that contains components for character movement.
|
|
#[derive(Bundle)]
|
|
pub struct MovementBundle {
|
|
acceleration: MovementSpeed,
|
|
jump_impulse: JumpImpulse,
|
|
max_slope_angle: MaxSlopeAngle,
|
|
factor: MovementSpeedFactor,
|
|
}
|
|
|
|
impl MovementBundle {
|
|
pub const fn new(acceleration: Scalar, jump_impulse: Scalar, max_slope_angle: Scalar) -> Self {
|
|
Self {
|
|
acceleration: MovementSpeed(acceleration),
|
|
jump_impulse: JumpImpulse(jump_impulse),
|
|
max_slope_angle: MaxSlopeAngle(max_slope_angle),
|
|
factor: MovementSpeedFactor(1.0),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for MovementBundle {
|
|
fn default() -> Self {
|
|
Self::new(30.0, 18.0, (60.0 as Scalar).to_radians())
|
|
}
|
|
}
|
|
|
|
impl CharacterControllerBundle {
|
|
pub fn new(collider: Collider, gravity: Vector) -> Self {
|
|
// Create shape caster as a slightly smaller version of collider
|
|
let mut caster_shape = collider.clone();
|
|
caster_shape.set_scale(Vector::ONE * 0.98, 10);
|
|
|
|
Self {
|
|
character_controller: CharacterController,
|
|
rigid_body: RigidBody::Kinematic,
|
|
collider,
|
|
ground_caster: ShapeCaster::new(
|
|
caster_shape,
|
|
Vector::ZERO,
|
|
Quaternion::default(),
|
|
Dir3::NEG_Y,
|
|
)
|
|
.with_max_distance(0.2),
|
|
gravity: ControllerGravity(gravity),
|
|
movement: MovementBundle::default(),
|
|
collision_events: CollisionEventsEnabled,
|
|
}
|
|
}
|
|
}
|