@@ -1,202 +0,0 @@
|
||||
use super::{ControllerSet, ControllerSwitchEvent};
|
||||
use crate::{
|
||||
GameState,
|
||||
control::{SelectedController, controls::ControllerSettings},
|
||||
heads_database::HeadControls,
|
||||
player::PlayerBodyMesh,
|
||||
};
|
||||
use avian3d::{math::*, prelude::*};
|
||||
use bevy::prelude::*;
|
||||
use happy_feet::{
|
||||
KinematicVelocity,
|
||||
ground::{Grounding, GroundingConfig},
|
||||
prelude::{
|
||||
Character, CharacterDrag, CharacterFriction, CharacterGravity, CharacterMovement,
|
||||
CharacterPlugin, MoveInput, SteppingBehaviour, SteppingConfig,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_plugins(CharacterPlugin::default());
|
||||
|
||||
app.register_type::<MovementSpeedFactor>();
|
||||
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
reset_upon_switch
|
||||
.run_if(in_state(GameState::Playing))
|
||||
.before(ControllerSet::ApplyControlsRun)
|
||||
.before(ControllerSet::ApplyControlsFly),
|
||||
)
|
||||
.add_systems(
|
||||
FixedPreUpdate,
|
||||
decelerate.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 c: Commands,
|
||||
mut event_controller_switch: EventReader<ControllerSwitchEvent>,
|
||||
controller: Res<SelectedController>,
|
||||
mut rig_transform_q: Option<Single<&mut Transform, With<PlayerBodyMesh>>>,
|
||||
mut velocity: Single<&mut KinematicVelocity, With<Character>>,
|
||||
character: Single<Entity, With<Character>>,
|
||||
) {
|
||||
for _ in event_controller_switch.read() {
|
||||
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);
|
||||
}
|
||||
|
||||
match controller.0 {
|
||||
ControllerSet::ApplyControlsFly => {
|
||||
c.entity(*character).insert(FLYING_MOVEMENT_CONFIG);
|
||||
}
|
||||
ControllerSet::ApplyControlsRun => {
|
||||
c.entity(*character).insert(RUNNING_MOVEMENT_CONFIG);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decelerates the player in the directions of "undesired velocity"; velocity that is not aligned
|
||||
/// with the movement input direction. This makes it quicker to reverse direction, and prevents
|
||||
/// sliding around, even with low friction, without slowing down the player globally like high
|
||||
/// friction or drag would.
|
||||
fn decelerate(
|
||||
mut character: Query<(
|
||||
&mut KinematicVelocity,
|
||||
&MoveInput,
|
||||
Option<&Grounding>,
|
||||
&ControllerSettings,
|
||||
)>,
|
||||
) {
|
||||
for (mut velocity, input, grounding, settings) in character.iter_mut() {
|
||||
let direction = input.value.normalize();
|
||||
let ground_normal = grounding
|
||||
.and_then(|it| it.normal())
|
||||
.unwrap_or(Dir3::Y)
|
||||
.as_vec3();
|
||||
|
||||
let velocity_within_90_degrees = direction.dot(velocity.0) > 0.0;
|
||||
let desired_velocity = if direction != Vec3::ZERO && velocity_within_90_degrees {
|
||||
// project velocity onto direction to extract the component directly aligned with direction
|
||||
velocity.0.project_onto(direction)
|
||||
} else {
|
||||
// if velocity isn't within 90 degrees of direction then the projection would be in the
|
||||
// exact opposite direction of `direction`; so just zero it
|
||||
Vec3::ZERO
|
||||
};
|
||||
let undesired_velocity = velocity.0 - desired_velocity;
|
||||
let vertical_undesired_velocity = undesired_velocity.project_onto(ground_normal);
|
||||
// only select the velocity along the ground plane; that way the character can't decelerate
|
||||
// while falling or jumping, but will decelerate along slopes properly
|
||||
let undesired_velocity = undesired_velocity - vertical_undesired_velocity;
|
||||
let deceleration =
|
||||
Vec3::ZERO.move_towards(undesired_velocity, settings.deceleration_factor);
|
||||
velocity.0 -= deceleration;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct MovementSpeedFactor(pub f32);
|
||||
|
||||
/// A bundle that contains the components needed for a basic
|
||||
/// kinematic character controller.
|
||||
#[derive(Bundle)]
|
||||
pub struct CharacterControllerBundle {
|
||||
character_controller: Character,
|
||||
collider: Collider,
|
||||
move_input: MoveInput,
|
||||
movement_factor: MovementSpeedFactor,
|
||||
collision_events: CollisionEventsEnabled,
|
||||
movement_config: MovementConfig,
|
||||
interpolation: TransformInterpolation,
|
||||
}
|
||||
|
||||
impl CharacterControllerBundle {
|
||||
pub fn new(collider: Collider, controls: HeadControls) -> 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);
|
||||
|
||||
let config = match controls {
|
||||
HeadControls::Plane => FLYING_MOVEMENT_CONFIG,
|
||||
HeadControls::Walk => RUNNING_MOVEMENT_CONFIG,
|
||||
};
|
||||
|
||||
Self {
|
||||
character_controller: Character { up: Dir3::Y },
|
||||
collider,
|
||||
move_input: MoveInput::default(),
|
||||
movement_factor: MovementSpeedFactor(1.0),
|
||||
collision_events: CollisionEventsEnabled,
|
||||
movement_config: config,
|
||||
interpolation: TransformInterpolation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
struct MovementConfig {
|
||||
movement: CharacterMovement,
|
||||
step: SteppingConfig,
|
||||
ground: GroundingConfig,
|
||||
gravity: CharacterGravity,
|
||||
friction: CharacterFriction,
|
||||
drag: CharacterDrag,
|
||||
settings: ControllerSettings,
|
||||
}
|
||||
|
||||
const RUNNING_MOVEMENT_CONFIG: MovementConfig = MovementConfig {
|
||||
movement: CharacterMovement {
|
||||
target_speed: 15.0,
|
||||
acceleration: 40.0,
|
||||
},
|
||||
step: SteppingConfig {
|
||||
max_height: 0.25,
|
||||
behaviour: SteppingBehaviour::Grounded,
|
||||
},
|
||||
ground: GroundingConfig {
|
||||
max_angle: PI / 4.0,
|
||||
max_distance: 0.2,
|
||||
snap_to_surface: true,
|
||||
},
|
||||
gravity: CharacterGravity(vec3(0.0, -60.0, 0.0)),
|
||||
friction: CharacterFriction(10.0),
|
||||
drag: CharacterDrag(0.0),
|
||||
settings: ControllerSettings {
|
||||
jump_force: 25.0,
|
||||
deceleration_factor: 1.0,
|
||||
},
|
||||
};
|
||||
|
||||
const FLYING_MOVEMENT_CONFIG: MovementConfig = MovementConfig {
|
||||
movement: CharacterMovement {
|
||||
target_speed: 20.0,
|
||||
acceleration: 300.0,
|
||||
},
|
||||
step: SteppingConfig {
|
||||
max_height: 0.25,
|
||||
behaviour: SteppingBehaviour::Never,
|
||||
},
|
||||
ground: GroundingConfig {
|
||||
max_angle: 0.0,
|
||||
max_distance: -1.0,
|
||||
snap_to_surface: false,
|
||||
},
|
||||
gravity: CharacterGravity(Vec3::ZERO),
|
||||
friction: CharacterFriction(0.0),
|
||||
drag: CharacterDrag(10.0),
|
||||
settings: ControllerSettings {
|
||||
jump_force: 0.0,
|
||||
deceleration_factor: 0.0,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user