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::(); app.init_resource::(); app.register_type::(); app.register_type::(); app.register_type::(); app.register_type::(); app.register_type::(); 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, mut rig_transform_q: Option>>, 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, } } }