Fly controller (#26)
This commit is contained in:
@@ -2,10 +2,8 @@ use avian3d::prelude::*;
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
GameState,
|
GameState, control::Controls, control::controller_common::PlayerMovement,
|
||||||
control::{Controls, controller::PlayerMovement},
|
loading_assets::UIAssets, physics_layers::GameLayer,
|
||||||
loading_assets::UIAssets,
|
|
||||||
physics_layers::GameLayer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Component, Reflect, Debug)]
|
#[derive(Component, Reflect, Debug)]
|
||||||
@@ -14,9 +12,10 @@ pub struct CameraTarget;
|
|||||||
#[derive(Component, Reflect, Debug)]
|
#[derive(Component, Reflect, Debug)]
|
||||||
pub struct CameraArmRotation;
|
pub struct CameraArmRotation;
|
||||||
|
|
||||||
#[derive(Component, Reflect, Debug)]
|
/// Requested camera rotation based on various input sources (keyboard, gamepad)
|
||||||
|
#[derive(Component, Reflect, Debug, Default, Deref, DerefMut)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct CameraRotation(pub f32);
|
pub struct CameraRotationInput(pub Vec2);
|
||||||
|
|
||||||
#[derive(Resource, Reflect, Debug, Default)]
|
#[derive(Resource, Reflect, Debug, Default)]
|
||||||
#[reflect(Resource)]
|
#[reflect(Resource)]
|
||||||
@@ -43,7 +42,7 @@ impl MainCamera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.register_type::<CameraRotation>();
|
app.register_type::<CameraRotationInput>();
|
||||||
app.register_type::<CameraState>();
|
app.register_type::<CameraState>();
|
||||||
app.register_type::<MainCamera>();
|
app.register_type::<MainCamera>();
|
||||||
|
|
||||||
@@ -66,7 +65,7 @@ fn startup(mut commands: Commands) {
|
|||||||
commands.spawn((
|
commands.spawn((
|
||||||
Camera3d::default(),
|
Camera3d::default(),
|
||||||
MainCamera::new(Vec3::new(0., 2.5, -13.)),
|
MainCamera::new(Vec3::new(0., 2.5, -13.)),
|
||||||
CameraRotation(0.),
|
CameraRotationInput::default(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +117,7 @@ fn update_ui(
|
|||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
mut cam: Query<
|
mut cam: Query<
|
||||||
(&MainCamera, &mut Transform, &CameraRotation),
|
(&MainCamera, &mut Transform, &CameraRotationInput),
|
||||||
(Without<CameraTarget>, Without<CameraArmRotation>),
|
(Without<CameraTarget>, Without<CameraArmRotation>),
|
||||||
>,
|
>,
|
||||||
target: Query<&GlobalTransform, (With<CameraTarget>, Without<CameraArmRotation>)>,
|
target: Query<&GlobalTransform, (With<CameraTarget>, Without<CameraArmRotation>)>,
|
||||||
@@ -134,15 +133,15 @@ fn update(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(rotation) = arm_rotation.get_single() else {
|
let Ok(arm_tf) = arm_rotation.get_single() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok((camera, mut cam_transform, cam_rotation)) = cam.get_single_mut() else {
|
let Ok((camera, mut cam_transform, cam_rotation_input)) = cam.get_single_mut() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let direction = rotation.rotation * Quat::from_rotation_y(cam_rotation.0) * camera.dir;
|
let direction = arm_tf.rotation * Quat::from_rotation_y(cam_rotation_input.x) * camera.dir;
|
||||||
|
|
||||||
let max_distance = camera.distance;
|
let max_distance = camera.distance;
|
||||||
|
|
||||||
@@ -162,7 +161,7 @@ fn update(
|
|||||||
|
|
||||||
fn rotate_view_keyboard(
|
fn rotate_view_keyboard(
|
||||||
mut controls: ResMut<Controls>,
|
mut controls: ResMut<Controls>,
|
||||||
mut cam: Query<&mut CameraRotation>,
|
mut cam: Query<&mut CameraRotationInput>,
|
||||||
movement: Res<PlayerMovement>,
|
movement: Res<PlayerMovement>,
|
||||||
) {
|
) {
|
||||||
let Ok(mut cam) = cam.get_single_mut() else {
|
let Ok(mut cam) = cam.get_single_mut() else {
|
||||||
@@ -171,20 +170,21 @@ fn rotate_view_keyboard(
|
|||||||
|
|
||||||
if !controls.keyboard_state.view_mode {
|
if !controls.keyboard_state.view_mode {
|
||||||
if movement.any_direction {
|
if movement.any_direction {
|
||||||
cam.0 = 0.;
|
cam.x = 0.;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cam.0 += controls.keyboard_state.look_dir.x * -0.001;
|
cam.x += controls.keyboard_state.look_dir.x * -0.001;
|
||||||
|
cam.y += controls.keyboard_state.look_dir.y * -0.001;
|
||||||
|
|
||||||
controls.keyboard_state.look_dir = Vec2::ZERO;
|
controls.keyboard_state.look_dir = Vec2::ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate_view_gamepad(
|
fn rotate_view_gamepad(
|
||||||
controls: Res<Controls>,
|
controls: Res<Controls>,
|
||||||
mut cam: Query<&mut CameraRotation>,
|
mut cam: Query<&mut CameraRotationInput>,
|
||||||
movement: Res<PlayerMovement>,
|
movement: Res<PlayerMovement>,
|
||||||
) {
|
) {
|
||||||
let Ok(mut cam) = cam.get_single_mut() else {
|
let Ok(mut cam) = cam.get_single_mut() else {
|
||||||
@@ -197,9 +197,10 @@ fn rotate_view_gamepad(
|
|||||||
|
|
||||||
if !gamepad.view_mode {
|
if !gamepad.view_mode {
|
||||||
if movement.any_direction {
|
if movement.any_direction {
|
||||||
cam.0 = 0.;
|
cam.x = 0.;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cam.0 += gamepad.look_dir.x * -0.001;
|
cam.x += gamepad.look_dir.x * -0.001;
|
||||||
|
cam.y += gamepad.look_dir.y * -0.001;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use avian3d::{math::*, prelude::*};
|
use avian3d::{math::*, prelude::*};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use super::controller::{CharacterController, MaxSlopeAngle};
|
use super::controller_common::{CharacterController, MaxSlopeAngle};
|
||||||
|
|
||||||
/// Kinematic bodies do not get pushed by collisions by default,
|
/// Kinematic bodies do not get pushed by collisions by default,
|
||||||
/// so it needs to be done manually.
|
/// so it needs to be done manually.
|
||||||
|
|||||||
@@ -1,246 +0,0 @@
|
|||||||
use super::{ControllerSet, Controls, collisions::kinematic_controller_collisions};
|
|
||||||
use crate::{GameState, player::PlayerBodyMesh};
|
|
||||||
use avian3d::{math::*, prelude::*};
|
|
||||||
use bevy::{ecs::query::Has, prelude::*};
|
|
||||||
|
|
||||||
pub struct CharacterControllerPlugin;
|
|
||||||
|
|
||||||
impl Plugin for CharacterControllerPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
app.init_resource::<PlayerMovement>();
|
|
||||||
|
|
||||||
app.register_type::<JumpImpulse>();
|
|
||||||
app.register_type::<ControllerGravity>();
|
|
||||||
app.register_type::<MovementAcceleration>();
|
|
||||||
|
|
||||||
app.add_systems(
|
|
||||||
Update,
|
|
||||||
(set_movement_flag, update_grounded, apply_gravity, movement)
|
|
||||||
.chain()
|
|
||||||
.in_set(ControllerSet::ApplyControls)
|
|
||||||
.run_if(in_state(GameState::Playing)),
|
|
||||||
);
|
|
||||||
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.
|
|
||||||
PostProcessCollisions,
|
|
||||||
kinematic_controller_collisions.run_if(in_state(GameState::Playing)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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, Default)]
|
|
||||||
pub struct PlayerMovement {
|
|
||||||
pub any_direction: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The acceleration used for character movement.
|
|
||||||
#[derive(Component, Reflect)]
|
|
||||||
#[reflect(Component)]
|
|
||||||
pub struct MovementAcceleration(Scalar);
|
|
||||||
|
|
||||||
#[derive(Component, Reflect)]
|
|
||||||
#[reflect(Component)]
|
|
||||||
pub struct MovementSpeedFactor(pub f32);
|
|
||||||
|
|
||||||
/// The strength of a jump.
|
|
||||||
#[derive(Component, Reflect)]
|
|
||||||
#[reflect(Component)]
|
|
||||||
pub struct JumpImpulse(Scalar);
|
|
||||||
|
|
||||||
/// The gravitational acceleration used for a character controller.
|
|
||||||
#[derive(Component, Reflect)]
|
|
||||||
#[reflect(Component)]
|
|
||||||
pub struct ControllerGravity(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,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A bundle that contains components for character movement.
|
|
||||||
#[derive(Bundle)]
|
|
||||||
pub struct MovementBundle {
|
|
||||||
acceleration: MovementAcceleration,
|
|
||||||
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: MovementAcceleration(acceleration),
|
|
||||||
jump_impulse: JumpImpulse(jump_impulse),
|
|
||||||
max_slope_angle: MaxSlopeAngle(max_slope_angle),
|
|
||||||
factor: MovementSpeedFactor(1.),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CharacterControllerBundle {
|
|
||||||
pub fn new(collider: Collider, gravity: Vector, movement: MovementBundle) -> 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the [`Grounded`] status for character controllers.
|
|
||||||
fn update_grounded(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut query: Query<
|
|
||||||
(Entity, &ShapeHits, &Rotation, Option<&MaxSlopeAngle>),
|
|
||||||
With<CharacterController>,
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
for (entity, hits, rotation, max_slope_angle) in &mut query {
|
|
||||||
// The character is grounded if the shape caster has a hit with a normal
|
|
||||||
// that isn't too steep.
|
|
||||||
let is_grounded = hits.iter().any(|hit| {
|
|
||||||
if let Some(angle) = max_slope_angle {
|
|
||||||
(rotation * -hit.normal2).angle_between(Vector::Y).abs() <= angle.0
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if is_grounded {
|
|
||||||
debug!("grounded");
|
|
||||||
commands.entity(entity).insert(Grounded);
|
|
||||||
} else {
|
|
||||||
debug!("not grounded");
|
|
||||||
commands.entity(entity).remove::<Grounded>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the movement flag, which is an indicator for the rig animation and the braking system.
|
|
||||||
fn set_movement_flag(mut player_movement: ResMut<PlayerMovement>, controls: Res<Controls>) {
|
|
||||||
let mut direction = controls.keyboard_state.move_dir;
|
|
||||||
let deadzone = 0.2;
|
|
||||||
|
|
||||||
if let Some(gamepad) = controls.gamepad_state {
|
|
||||||
direction += gamepad.move_dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
if player_movement.any_direction {
|
|
||||||
if direction.length_squared() < deadzone {
|
|
||||||
player_movement.any_direction = false;
|
|
||||||
}
|
|
||||||
} else if direction.length_squared() > deadzone {
|
|
||||||
player_movement.any_direction = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Responds to [`MovementAction`] events and moves character controllers accordingly.
|
|
||||||
fn movement(
|
|
||||||
time: Res<Time>,
|
|
||||||
controls: Res<Controls>,
|
|
||||||
mut controllers: Query<(
|
|
||||||
&MovementAcceleration,
|
|
||||||
&MovementSpeedFactor,
|
|
||||||
&JumpImpulse,
|
|
||||||
&mut LinearVelocity,
|
|
||||||
Has<Grounded>,
|
|
||||||
)>,
|
|
||||||
rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
|
|
||||||
mut jump_used: Local<bool>,
|
|
||||||
) {
|
|
||||||
let delta_time = time.delta_secs();
|
|
||||||
|
|
||||||
let mut direction = controls.keyboard_state.move_dir;
|
|
||||||
|
|
||||||
let mut jump_requested = controls.keyboard_state.jump;
|
|
||||||
|
|
||||||
if let Some(gamepad) = controls.gamepad_state {
|
|
||||||
direction += gamepad.move_dir;
|
|
||||||
|
|
||||||
jump_requested |= gamepad.jump;
|
|
||||||
}
|
|
||||||
|
|
||||||
direction = direction.normalize_or_zero();
|
|
||||||
|
|
||||||
for (movement_acceleration, factor, jump_impulse, mut linear_velocity, is_grounded) in
|
|
||||||
&mut controllers
|
|
||||||
{
|
|
||||||
let mut direction = direction.extend(0.0).xzy();
|
|
||||||
|
|
||||||
if let Some(ref rig_transform) = rig_transform_q {
|
|
||||||
direction =
|
|
||||||
(rig_transform.forward() * direction.z) + (rig_transform.right() * direction.x);
|
|
||||||
}
|
|
||||||
|
|
||||||
linear_velocity.x = -direction.x * movement_acceleration.0 * delta_time * factor.0;
|
|
||||||
linear_velocity.z = -direction.z * movement_acceleration.0 * delta_time * factor.0;
|
|
||||||
|
|
||||||
if is_grounded && jump_requested && !*jump_used {
|
|
||||||
linear_velocity.y = jump_impulse.0;
|
|
||||||
debug!("jump");
|
|
||||||
*jump_used = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !controls.keyboard_state.jump
|
|
||||||
&& !controls
|
|
||||||
.gamepad_state
|
|
||||||
.map(|state| state.jump)
|
|
||||||
.unwrap_or_default()
|
|
||||||
{
|
|
||||||
*jump_used = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies [`ControllerGravity`] to character controllers.
|
|
||||||
fn apply_gravity(
|
|
||||||
time: Res<Time>,
|
|
||||||
mut controllers: Query<(&ControllerGravity, &mut LinearVelocity, Option<&Grounded>)>,
|
|
||||||
) {
|
|
||||||
let delta_time = time.delta_secs();
|
|
||||||
|
|
||||||
for (gravity, mut linear_velocity, grounded) in &mut controllers {
|
|
||||||
if grounded.is_none() {
|
|
||||||
linear_velocity.0 += gravity.0 * delta_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
174
src/control/controller_common.rs
Normal file
174
src/control/controller_common.rs
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
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.
|
||||||
|
PostProcessCollisions,
|
||||||
|
kinematic_controller_collisions.run_if(in_state(GameState::Playing)), // todo check if we can make this less viral,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
122
src/control/controller_flying.rs
Normal file
122
src/control/controller_flying.rs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
use avian3d::prelude::*;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::player::PlayerBodyMesh;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
ControllerSet, Controls,
|
||||||
|
controller_common::{JumpImpulse, MovementDampingFactor, MovementSpeed, PlayerMovement},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct CharacterControllerPlugin;
|
||||||
|
|
||||||
|
impl Plugin for CharacterControllerPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
set_movement_flag,
|
||||||
|
rotate_rig,
|
||||||
|
movement,
|
||||||
|
apply_movement_damping,
|
||||||
|
)
|
||||||
|
.chain()
|
||||||
|
.in_set(ControllerSet::ApplyControlsFly), // todo only in GameState::Playing?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the movement flag, which is an indicator for the rig animation and the braking system.
|
||||||
|
fn set_movement_flag(mut player_movement: ResMut<PlayerMovement>) {
|
||||||
|
player_movement.any_direction = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate_rig(
|
||||||
|
mut rig_transform_q: Option<Single<&mut Transform, With<PlayerBodyMesh>>>,
|
||||||
|
mut controls: ResMut<Controls>,
|
||||||
|
) {
|
||||||
|
if controls.keyboard_state.view_mode {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let look_dir;
|
||||||
|
|
||||||
|
if let Some(gamepad) = controls.gamepad_state {
|
||||||
|
look_dir = gamepad.look_dir + controls.keyboard_state.look_dir;
|
||||||
|
|
||||||
|
if gamepad.view_mode {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
look_dir = controls.keyboard_state.look_dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ref mut rig_transform) = rig_transform_q {
|
||||||
|
// todo: Make consistent with the running controller
|
||||||
|
let sensitivity = 0.001;
|
||||||
|
let max_pitch = 35.0 * PI / 180.0;
|
||||||
|
let min_pitch = -25.0 * PI / 180.0;
|
||||||
|
|
||||||
|
rig_transform.rotate_y(look_dir.x * -sensitivity);
|
||||||
|
|
||||||
|
let euler_rot = rig_transform.rotation.to_euler(EulerRot::YXZ);
|
||||||
|
let yaw = euler_rot.0;
|
||||||
|
let pitch = euler_rot.1 + look_dir.y * sensitivity;
|
||||||
|
|
||||||
|
let pitch_clamped = pitch.clamp(min_pitch, max_pitch);
|
||||||
|
rig_transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch_clamped, 0.0);
|
||||||
|
|
||||||
|
// The following can be used to limit the amount of rotation per frame
|
||||||
|
// let target_rotation = rig_transform.rotation
|
||||||
|
// * Quat::from_rotation_x(controls.keyboard_state.look_dir.y * sensitivity);
|
||||||
|
// let clamped_rotation = rig_transform.rotation.rotate_towards(target_rotation, 0.01);
|
||||||
|
// rig_transform.rotation = clamped_rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
controls.keyboard_state.look_dir = Vec2::ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Responds to [`MovementAction`] events and moves character controllers accordingly.
|
||||||
|
fn movement(
|
||||||
|
controls: Res<Controls>,
|
||||||
|
mut controllers: Query<(&MovementSpeed, &JumpImpulse, &mut LinearVelocity)>,
|
||||||
|
rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
|
||||||
|
) {
|
||||||
|
let move_dir = Vec2::new(0.0, 1.0); // Always full-throttle forwards when flying
|
||||||
|
let mut jump_requested = controls.keyboard_state.jump;
|
||||||
|
|
||||||
|
if let Some(gamepad) = controls.gamepad_state {
|
||||||
|
jump_requested |= gamepad.jump;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (movement_acceleration, jump_impulse, mut linear_velocity) in &mut controllers {
|
||||||
|
let mut direction = move_dir.extend(0.0).xzy();
|
||||||
|
|
||||||
|
if let Some(ref rig_transform) = rig_transform_q {
|
||||||
|
direction =
|
||||||
|
(rig_transform.forward() * direction.z) + (rig_transform.right() * direction.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
linear_velocity.0 = -direction * movement_acceleration.0;
|
||||||
|
|
||||||
|
if jump_requested {
|
||||||
|
linear_velocity.y = jump_impulse.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Slows down movement in the XZ plane.
|
||||||
|
fn apply_movement_damping(
|
||||||
|
time: Res<Time>,
|
||||||
|
mut query: Query<(&MovementDampingFactor, &mut LinearVelocity)>,
|
||||||
|
) {
|
||||||
|
let delta_time = time.delta_secs();
|
||||||
|
|
||||||
|
for (damping_factor, mut linear_velocity) in &mut query {
|
||||||
|
// We could use `LinearDamping`, but we don't want to dampen movement along the Y axis
|
||||||
|
linear_velocity.x *= 1.0 - damping_factor.0 * delta_time;
|
||||||
|
linear_velocity.z *= 1.0 - damping_factor.0 * delta_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
178
src/control/controller_running.rs
Normal file
178
src/control/controller_running.rs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
use super::{ControllerSet, Controls, controller_common::MovementSpeedFactor};
|
||||||
|
use crate::{GameState, player::PlayerBodyMesh};
|
||||||
|
use avian3d::{math::*, prelude::*};
|
||||||
|
use bevy::{ecs::query::Has, prelude::*};
|
||||||
|
|
||||||
|
use super::controller_common::{
|
||||||
|
CharacterController, ControllerGravity, Grounded, JumpImpulse, MaxSlopeAngle, MovementSpeed,
|
||||||
|
PlayerMovement, reset_upon_switch,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct CharacterControllerPlugin;
|
||||||
|
|
||||||
|
impl Plugin for CharacterControllerPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
reset_upon_switch,
|
||||||
|
set_movement_flag,
|
||||||
|
update_grounded,
|
||||||
|
apply_gravity,
|
||||||
|
rotate_view_keyboard,
|
||||||
|
rotate_view_gamepad, // todo consider merging these two
|
||||||
|
movement,
|
||||||
|
)
|
||||||
|
.chain()
|
||||||
|
.in_set(ControllerSet::ApplyControlsRun)
|
||||||
|
.run_if(in_state(GameState::Playing)), // todo check if we can make this less viral
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the [`Grounded`] status for character controllers.
|
||||||
|
fn update_grounded(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut query: Query<
|
||||||
|
(Entity, &ShapeHits, &Rotation, Option<&MaxSlopeAngle>),
|
||||||
|
With<CharacterController>,
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
for (entity, hits, rotation, max_slope_angle) in &mut query {
|
||||||
|
// The character is grounded if the shape caster has a hit with a normal
|
||||||
|
// that isn't too steep.
|
||||||
|
let is_grounded = hits.iter().any(|hit| {
|
||||||
|
if let Some(angle) = max_slope_angle {
|
||||||
|
(rotation * -hit.normal2).angle_between(Vector::Y).abs() <= angle.0
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if is_grounded {
|
||||||
|
debug!("grounded");
|
||||||
|
commands.entity(entity).insert(Grounded);
|
||||||
|
} else {
|
||||||
|
debug!("not grounded");
|
||||||
|
commands.entity(entity).remove::<Grounded>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the movement flag, which is an indicator for the rig animation and the braking system.
|
||||||
|
fn set_movement_flag(mut player_movement: ResMut<PlayerMovement>, controls: Res<Controls>) {
|
||||||
|
let mut direction = controls.keyboard_state.move_dir;
|
||||||
|
let deadzone = 0.2;
|
||||||
|
|
||||||
|
if let Some(gamepad) = controls.gamepad_state {
|
||||||
|
direction += gamepad.move_dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
if player_movement.any_direction {
|
||||||
|
if direction.length_squared() < deadzone {
|
||||||
|
player_movement.any_direction = false;
|
||||||
|
}
|
||||||
|
} else if direction.length_squared() > deadzone {
|
||||||
|
player_movement.any_direction = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate_view_gamepad(
|
||||||
|
controls: Res<Controls>,
|
||||||
|
mut player: Query<&mut Transform, With<PlayerBodyMesh>>,
|
||||||
|
) {
|
||||||
|
let Some(gamepad) = controls.gamepad_state else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if gamepad.view_mode {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for mut tr in &mut player {
|
||||||
|
tr.rotate_y(gamepad.look_dir.x * -0.001);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate_view_keyboard(
|
||||||
|
mut controls: ResMut<Controls>,
|
||||||
|
mut player: Query<&mut Transform, With<PlayerBodyMesh>>,
|
||||||
|
) {
|
||||||
|
if controls.keyboard_state.view_mode {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for mut tr in &mut player {
|
||||||
|
tr.rotate_y(controls.keyboard_state.look_dir.x * -0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
controls.keyboard_state.look_dir = Vec2::ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Responds to [`MovementAction`] events and moves character controllers accordingly.
|
||||||
|
fn movement(
|
||||||
|
controls: Res<Controls>,
|
||||||
|
mut controllers: Query<(
|
||||||
|
&MovementSpeed,
|
||||||
|
&MovementSpeedFactor,
|
||||||
|
&JumpImpulse,
|
||||||
|
&mut LinearVelocity,
|
||||||
|
Has<Grounded>,
|
||||||
|
)>,
|
||||||
|
rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
|
||||||
|
mut jump_used: Local<bool>,
|
||||||
|
) {
|
||||||
|
let mut direction = controls.keyboard_state.move_dir;
|
||||||
|
|
||||||
|
let mut jump_requested = controls.keyboard_state.jump;
|
||||||
|
|
||||||
|
if let Some(gamepad) = controls.gamepad_state {
|
||||||
|
direction += gamepad.move_dir;
|
||||||
|
|
||||||
|
jump_requested |= gamepad.jump;
|
||||||
|
}
|
||||||
|
|
||||||
|
direction = direction.normalize_or_zero();
|
||||||
|
|
||||||
|
for (movement_speed, factor, jump_impulse, mut linear_velocity, is_grounded) in &mut controllers
|
||||||
|
{
|
||||||
|
let mut direction = direction.extend(0.0).xzy();
|
||||||
|
|
||||||
|
if let Some(ref rig_transform) = rig_transform_q {
|
||||||
|
direction =
|
||||||
|
(rig_transform.forward() * direction.z) + (rig_transform.right() * direction.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
linear_velocity.x = -direction.x * movement_speed.0 * factor.0;
|
||||||
|
linear_velocity.z = -direction.z * movement_speed.0 * factor.0;
|
||||||
|
|
||||||
|
if is_grounded && jump_requested && !*jump_used {
|
||||||
|
linear_velocity.y = jump_impulse.0;
|
||||||
|
debug!("jump");
|
||||||
|
*jump_used = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !controls.keyboard_state.jump
|
||||||
|
&& !controls
|
||||||
|
.gamepad_state
|
||||||
|
.map(|state| state.jump)
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
*jump_used = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies [`ControllerGravity`] to character controllers.
|
||||||
|
fn apply_gravity(
|
||||||
|
time: Res<Time>,
|
||||||
|
mut controllers: Query<(&ControllerGravity, &mut LinearVelocity, Option<&Grounded>)>,
|
||||||
|
) {
|
||||||
|
let delta_time = time.delta_secs();
|
||||||
|
|
||||||
|
for (gravity, mut linear_velocity, grounded) in &mut controllers {
|
||||||
|
if grounded.is_none() {
|
||||||
|
linear_velocity.0 += gravity.0 * delta_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,20 +3,26 @@ use bevy::prelude::*;
|
|||||||
use crate::GameState;
|
use crate::GameState;
|
||||||
|
|
||||||
mod collisions;
|
mod collisions;
|
||||||
pub mod controller;
|
pub mod controller_common;
|
||||||
|
pub mod controller_flying;
|
||||||
|
pub mod controller_running;
|
||||||
pub mod controls;
|
pub mod controls;
|
||||||
|
|
||||||
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Default)]
|
||||||
enum ControllerSet {
|
enum ControllerSet {
|
||||||
CollectInputs,
|
CollectInputs,
|
||||||
ApplyControls,
|
ApplyControlsFly,
|
||||||
|
#[default]
|
||||||
|
ApplyControlsRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource, Debug, Clone, Copy, Default, PartialEq)]
|
#[derive(Resource, Debug, Clone, Copy, Default, PartialEq)]
|
||||||
pub struct ControlState {
|
pub struct ControlState {
|
||||||
|
/// Movement direction with a maximum length of 1.0
|
||||||
pub move_dir: Vec2,
|
pub move_dir: Vec2,
|
||||||
pub look_dir: Vec2,
|
pub look_dir: Vec2,
|
||||||
pub jump: bool,
|
pub jump: bool,
|
||||||
|
/// Determines if the camera can rotate freely around the player
|
||||||
pub view_mode: bool,
|
pub view_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,12 +32,54 @@ pub struct Controls {
|
|||||||
pub gamepad_state: Option<ControlState>,
|
pub gamepad_state: Option<ControlState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct ControllerSwitchEvent;
|
||||||
|
|
||||||
|
#[derive(Resource, Debug, Default, PartialEq)]
|
||||||
|
pub struct SelectedController(ControllerSet);
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
|
app.init_resource::<SelectedController>();
|
||||||
|
|
||||||
app.add_plugins(controls::plugin);
|
app.add_plugins(controls::plugin);
|
||||||
|
app.add_plugins(controller_common::plugin);
|
||||||
|
app.add_plugins(controller_flying::CharacterControllerPlugin);
|
||||||
|
app.add_plugins(controller_running::CharacterControllerPlugin);
|
||||||
|
|
||||||
|
app.add_event::<ControllerSwitchEvent>();
|
||||||
|
|
||||||
|
app.add_systems(
|
||||||
|
Update,
|
||||||
|
switch_controller.before(ControllerSet::CollectInputs),
|
||||||
|
);
|
||||||
app.configure_sets(
|
app.configure_sets(
|
||||||
Update,
|
Update,
|
||||||
(ControllerSet::CollectInputs, ControllerSet::ApplyControls)
|
(
|
||||||
|
ControllerSet::CollectInputs,
|
||||||
|
ControllerSet::ApplyControlsFly.run_if(resource_equals(SelectedController(
|
||||||
|
ControllerSet::ApplyControlsFly,
|
||||||
|
))),
|
||||||
|
ControllerSet::ApplyControlsRun.run_if(resource_equals(SelectedController(
|
||||||
|
ControllerSet::ApplyControlsRun,
|
||||||
|
))),
|
||||||
|
)
|
||||||
.chain()
|
.chain()
|
||||||
.run_if(in_state(GameState::Playing)),
|
.run_if(in_state(GameState::Playing)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn switch_controller(
|
||||||
|
mut selected_controller: ResMut<SelectedController>,
|
||||||
|
keyboard: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut event_controller_switch: EventWriter<ControllerSwitchEvent>,
|
||||||
|
) {
|
||||||
|
if keyboard.just_pressed(KeyCode::KeyF) {
|
||||||
|
event_controller_switch.send(ControllerSwitchEvent);
|
||||||
|
|
||||||
|
if selected_controller.0 == ControllerSet::ApplyControlsFly {
|
||||||
|
selected_controller.0 = ControllerSet::ApplyControlsRun;
|
||||||
|
} else {
|
||||||
|
selected_controller.0 = ControllerSet::ApplyControlsFly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ use bevy_steamworks::{FriendFlags, SteamworksClient, SteamworksPlugin};
|
|||||||
use bevy_trenchbroom::prelude::*;
|
use bevy_trenchbroom::prelude::*;
|
||||||
use bevy_ui_gradients::UiGradientsPlugin;
|
use bevy_ui_gradients::UiGradientsPlugin;
|
||||||
use camera::MainCamera;
|
use camera::MainCamera;
|
||||||
use control::controller::CharacterControllerPlugin;
|
|
||||||
use heads_database::HeadDatabaseAsset;
|
use heads_database::HeadDatabaseAsset;
|
||||||
use loading_assets::AudioAssets;
|
use loading_assets::AudioAssets;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
@@ -106,7 +105,6 @@ fn main() {
|
|||||||
|
|
||||||
app.add_plugins(bevy_debug_log::LogViewerPlugin::default());
|
app.add_plugins(bevy_debug_log::LogViewerPlugin::default());
|
||||||
app.add_plugins(PhysicsPlugins::default());
|
app.add_plugins(PhysicsPlugins::default());
|
||||||
app.add_plugins(CharacterControllerPlugin);
|
|
||||||
app.add_plugins(PolylinePlugin);
|
app.add_plugins(PolylinePlugin);
|
||||||
app.add_plugins(Sprite3dPlugin);
|
app.add_plugins(Sprite3dPlugin);
|
||||||
app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));
|
app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ use crate::{
|
|||||||
alien::Animations,
|
alien::Animations,
|
||||||
camera::{CameraArmRotation, CameraTarget},
|
camera::{CameraArmRotation, CameraTarget},
|
||||||
cash::{Cash, CashCollectEvent},
|
cash::{Cash, CashCollectEvent},
|
||||||
control::{
|
control::controller_common::{CharacterControllerBundle, PlayerMovement},
|
||||||
Controls,
|
|
||||||
controller::{CharacterControllerBundle, MovementBundle, PlayerMovement},
|
|
||||||
},
|
|
||||||
global_observer,
|
global_observer,
|
||||||
head::ActiveHead,
|
head::ActiveHead,
|
||||||
heads::HeadChanged,
|
heads::HeadChanged,
|
||||||
@@ -17,10 +14,7 @@ use crate::{
|
|||||||
sounds::PlaySound,
|
sounds::PlaySound,
|
||||||
tb_entities::SpawnPoint,
|
tb_entities::SpawnPoint,
|
||||||
};
|
};
|
||||||
use avian3d::{
|
use avian3d::{math::Vector, prelude::*};
|
||||||
math::{Scalar, Vector},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
input::common_conditions::input_just_pressed,
|
input::common_conditions::input_just_pressed,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@@ -55,11 +49,6 @@ pub fn plugin(app: &mut App) {
|
|||||||
.run_if(in_state(GameState::Playing)),
|
.run_if(in_state(GameState::Playing)),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.add_systems(
|
|
||||||
Update,
|
|
||||||
(rotate_view_keyboard, rotate_view_gamepad).run_if(in_state(GameState::Playing)),
|
|
||||||
);
|
|
||||||
|
|
||||||
global_observer!(app, on_update_head);
|
global_observer!(app, on_update_head);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,9 +69,6 @@ fn spawn(
|
|||||||
|
|
||||||
let gravity = Vector::NEG_Y * 40.0;
|
let gravity = Vector::NEG_Y * 40.0;
|
||||||
let collider = Collider::capsule(0.9, 1.2);
|
let collider = Collider::capsule(0.9, 1.2);
|
||||||
let acceleration = 30.0 * 60.;
|
|
||||||
let jump_impulse = 18.0;
|
|
||||||
let max_slope_angle = (60.0 as Scalar).to_radians();
|
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
@@ -95,11 +81,7 @@ fn spawn(
|
|||||||
Visibility::default(),
|
Visibility::default(),
|
||||||
// LockedAxes::ROTATION_LOCKED, todo
|
// LockedAxes::ROTATION_LOCKED, todo
|
||||||
CollisionLayers::new(LayerMask(GameLayer::Player.to_bits()), LayerMask::ALL),
|
CollisionLayers::new(LayerMask(GameLayer::Player.to_bits()), LayerMask::ALL),
|
||||||
CharacterControllerBundle::new(
|
CharacterControllerBundle::new(collider, gravity),
|
||||||
collider,
|
|
||||||
gravity,
|
|
||||||
MovementBundle::new(acceleration, jump_impulse, max_slope_angle),
|
|
||||||
),
|
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent
|
parent
|
||||||
@@ -127,38 +109,6 @@ fn spawn(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate_view_gamepad(
|
|
||||||
controls: Res<Controls>,
|
|
||||||
mut player: Query<&mut Transform, With<PlayerBodyMesh>>,
|
|
||||||
) {
|
|
||||||
let Some(gamepad) = controls.gamepad_state else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if gamepad.view_mode {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for mut tr in &mut player {
|
|
||||||
tr.rotate_y(gamepad.look_dir.x * -0.001);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rotate_view_keyboard(
|
|
||||||
mut controls: ResMut<Controls>,
|
|
||||||
mut player: Query<&mut Transform, With<PlayerBodyMesh>>,
|
|
||||||
) {
|
|
||||||
if controls.keyboard_state.view_mode {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for mut tr in &mut player {
|
|
||||||
tr.rotate_y(controls.keyboard_state.look_dir.x * -0.001);
|
|
||||||
}
|
|
||||||
|
|
||||||
controls.keyboard_state.look_dir = Vec2::ZERO;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_recenter(mut q_windows: Query<&mut Window, With<PrimaryWindow>>) {
|
fn cursor_recenter(mut q_windows: Query<&mut Window, With<PrimaryWindow>>) {
|
||||||
let mut primary_window = q_windows.single_mut();
|
let mut primary_window = q_windows.single_mut();
|
||||||
let center = Vec2::new(primary_window.width() / 2.0, primary_window.height() / 2.0);
|
let center = Vec2::new(primary_window.width() / 2.0, primary_window.height() / 2.0);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
GameState, control::controller::MovementSpeedFactor, global_observer, player::Player,
|
GameState, control::controller_common::MovementSpeedFactor, global_observer, player::Player,
|
||||||
tb_entities::Water,
|
tb_entities::Water,
|
||||||
};
|
};
|
||||||
use avian3d::prelude::*;
|
use avian3d::prelude::*;
|
||||||
|
|||||||
Reference in New Issue
Block a user