Split crate into shared logic library and binary crate (#52) (#53)

This commit is contained in:
extrawurst
2025-06-29 12:45:25 +02:00
committed by GitHub
parent 5d4c7630ef
commit 7996d632f7
65 changed files with 497 additions and 82 deletions

View File

@@ -0,0 +1,202 @@
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,
},
};