Player drag and alignment with gravity
+player jumping
This commit is contained in:
@@ -2,20 +2,48 @@ use bevy::prelude::*;
|
|||||||
use bevy_rapier3d::prelude::GravityScale;
|
use bevy_rapier3d::prelude::GravityScale;
|
||||||
|
|
||||||
#[derive(Component, Default, Reflect)]
|
#[derive(Component, Default, Reflect)]
|
||||||
#[require(PlayerVelocity)]
|
#[require(PlayerVelocity, MoveSpeed, JumpSpeed, PlayerDrag)]
|
||||||
pub struct PlayerMotion(pub Vec3);
|
pub struct PlayerMotion(pub Vec3);
|
||||||
|
|
||||||
#[derive(Component, Default, Reflect)]
|
#[derive(Component, Default, Reflect)]
|
||||||
pub struct PlayerForce(pub Vec3);
|
pub struct PlayerForce(pub Vec3);
|
||||||
|
|
||||||
#[derive(Component, Default, Reflect)]
|
#[derive(Component, Reflect)]
|
||||||
#[require(GravityScale)]
|
#[require(GravityScale)]
|
||||||
pub struct GravityDirection(pub Option<Dir3>);
|
pub struct GravityDirection(pub Option<Dir3>);
|
||||||
|
|
||||||
|
impl Default for GravityDirection {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::DOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl GravityDirection {
|
impl GravityDirection {
|
||||||
pub const DOWN: GravityDirection = GravityDirection(Some(Dir3::NEG_Y));
|
pub const DOWN: GravityDirection = GravityDirection(Some(Dir3::NEG_Y));
|
||||||
|
#[allow(dead_code)]
|
||||||
pub const NONE: GravityDirection = GravityDirection(None);
|
pub const NONE: GravityDirection = GravityDirection(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Default, Reflect)]
|
#[derive(Component, Default, Reflect)]
|
||||||
pub struct PlayerVelocity(pub Vec3);
|
pub struct PlayerVelocity(pub Vec3);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect)]
|
||||||
|
pub struct MoveSpeed(pub f32);
|
||||||
|
|
||||||
|
impl Default for MoveSpeed {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(10.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect)]
|
||||||
|
pub struct JumpSpeed(pub f32);
|
||||||
|
|
||||||
|
impl Default for JumpSpeed {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(10.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Default, Reflect)]
|
||||||
|
pub struct PlayerDrag(pub f32);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
components::{
|
components::{
|
||||||
camera::{CameraPitch, CameraRoot, FollowCam, MainCamera, Unfocused},
|
camera::{CameraPitch, CameraRoot, FollowCam, MainCamera, Unfocused},
|
||||||
player::GravityDirection,
|
player::{GravityDirection, MoveSpeed, PlayerDrag},
|
||||||
tags::{Player, Ship},
|
tags::{Player, Ship},
|
||||||
},
|
},
|
||||||
plugins::*,
|
plugins::*,
|
||||||
@@ -48,7 +48,7 @@ fn setup_scene(
|
|||||||
.spawn((
|
.spawn((
|
||||||
Name::new("Player"),
|
Name::new("Player"),
|
||||||
Player,
|
Player,
|
||||||
GravityDirection::DOWN,
|
PlayerDrag(0.5),
|
||||||
Collider::capsule_y(0.5, 0.5),
|
Collider::capsule_y(0.5, 0.5),
|
||||||
RigidBody::KinematicPositionBased,
|
RigidBody::KinematicPositionBased,
|
||||||
KinematicCharacterController::default(),
|
KinematicCharacterController::default(),
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
|
use std::f32::EPSILON;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_rapier3d::prelude::*;
|
use bevy_rapier3d::prelude::*;
|
||||||
|
|
||||||
use crate::components::{
|
use crate::{
|
||||||
player::{GravityDirection, PlayerForce, PlayerMotion, PlayerVelocity},
|
components::{
|
||||||
|
player::{GravityDirection, JumpSpeed, MoveSpeed, PlayerDrag, PlayerForce, PlayerMotion, PlayerVelocity},
|
||||||
tags::Player,
|
tags::Player,
|
||||||
|
},
|
||||||
|
utils::rotation::get_alignment_rotation,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PlayerPlugin;
|
pub struct PlayerPlugin;
|
||||||
@@ -11,15 +16,16 @@ pub struct PlayerPlugin;
|
|||||||
impl Plugin for PlayerPlugin {
|
impl Plugin for PlayerPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(PreUpdate, keyboard_input.in_set(PlayerInputSystems));
|
app.add_systems(PreUpdate, keyboard_input.in_set(PlayerInputSystems));
|
||||||
app.add_systems(Update, (apply_gravity, apply_forces, apply_motion).chain());
|
app.add_systems(Update, (apply_gravity, apply_forces, apply_motion, apply_drag).chain());
|
||||||
|
app.add_systems(Update, align_with_gravity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct PlayerInputSystems;
|
pub struct PlayerInputSystems;
|
||||||
|
|
||||||
fn apply_forces(player: Single<(&mut PlayerVelocity, &mut PlayerForce), With<Player>>, time: Res<Time>) {
|
fn apply_forces(player: Single<(&mut PlayerVelocity, &PlayerForce), With<Player>>, time: Res<Time>) {
|
||||||
let (mut vel, mut force) = player.into_inner();
|
let (mut vel, force) = player.into_inner();
|
||||||
|
|
||||||
vel.0 += force.0 * time.delta_secs();
|
vel.0 += force.0 * time.delta_secs();
|
||||||
}
|
}
|
||||||
@@ -44,8 +50,16 @@ fn apply_gravity(
|
|||||||
if !is_grounded {
|
if !is_grounded {
|
||||||
vel.0 += grav * scale.0 * 9.47 * time.delta_secs();
|
vel.0 += grav * scale.0 * 9.47 * time.delta_secs();
|
||||||
} else {
|
} else {
|
||||||
vel.0 = vel.0 - vel.0.dot(grav) * grav;
|
let dot = vel.0.dot(grav);
|
||||||
|
if dot > 0.0 {
|
||||||
|
vel.0 = vel.0 - dot * grav;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_drag(player: Single<(&mut PlayerVelocity, &PlayerDrag), With<Player>>, time: Res<Time>) {
|
||||||
|
let (mut vel, drag) = player.into_inner();
|
||||||
|
vel.0 *= (1.0 - drag.0 * time.delta_secs()).max(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_motion(
|
fn apply_motion(
|
||||||
@@ -61,10 +75,9 @@ fn apply_motion(
|
|||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
) {
|
) {
|
||||||
let (mut controller, transform, vel, motion) = player.into_inner();
|
let (mut controller, transform, vel, motion) = player.into_inner();
|
||||||
let max_vel = vel.0.clamp_length_max(500.0);
|
// let max_vel = vel.0.clamp_length_max(500.0);
|
||||||
let global_motion = transform.rotation * motion.0;
|
let global_motion = transform.rotation * motion.0;
|
||||||
controller.translation =
|
controller.translation = Some((vel.0 + global_motion) * controller.custom_mass.unwrap_or(1.0) * time.delta_secs());
|
||||||
Some((max_vel + global_motion) * controller.custom_mass.unwrap_or(1.0) * time.delta_secs());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyboard_input(
|
fn keyboard_input(
|
||||||
@@ -73,13 +86,15 @@ fn keyboard_input(
|
|||||||
(
|
(
|
||||||
&mut PlayerMotion,
|
&mut PlayerMotion,
|
||||||
&mut PlayerVelocity,
|
&mut PlayerVelocity,
|
||||||
|
&MoveSpeed,
|
||||||
|
&JumpSpeed,
|
||||||
&Transform,
|
&Transform,
|
||||||
Option<&KinematicCharacterControllerOutput>,
|
Option<&KinematicCharacterControllerOutput>,
|
||||||
),
|
),
|
||||||
With<Player>,
|
With<Player>,
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
let (mut motion, mut vel, transform, output) = player.into_inner();
|
let (mut motion, mut vel, speed, jump, transform, output) = player.into_inner();
|
||||||
let mut move_vec = Vec3::ZERO;
|
let mut move_vec = Vec3::ZERO;
|
||||||
|
|
||||||
if key.pressed(KeyCode::KeyW) {
|
if key.pressed(KeyCode::KeyW) {
|
||||||
@@ -94,18 +109,13 @@ fn keyboard_input(
|
|||||||
move_vec.x = 1.0;
|
move_vec.x = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
move_vec = move_vec.normalize_or_zero() * speed.0;
|
||||||
|
|
||||||
let is_grounded = output.map(|o| o.grounded).unwrap_or(false);
|
let is_grounded = output.map(|o| o.grounded).unwrap_or(false);
|
||||||
if key.just_pressed(KeyCode::Space) && is_grounded {
|
if key.just_pressed(KeyCode::Space) && is_grounded {
|
||||||
vel.0 += transform.up() * 200.0;
|
vel.0 += transform.up() * jump.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let angle = if key.pressed(KeyCode::KeyQ) {
|
|
||||||
-0.1
|
|
||||||
} else if key.pressed(KeyCode::KeyE) {
|
|
||||||
0.1
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
};
|
|
||||||
motion.0 = move_vec;
|
motion.0 = move_vec;
|
||||||
|
|
||||||
// transform.rotate_z(angle * time.delta_secs());
|
// transform.rotate_z(angle * time.delta_secs());
|
||||||
@@ -113,3 +123,32 @@ fn keyboard_input(
|
|||||||
// move_vec += transform.down().as_vec3();
|
// move_vec += transform.down().as_vec3();
|
||||||
// controller.translation = Some(move_vec * time.delta_secs());
|
// controller.translation = Some(move_vec * time.delta_secs());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn align_with_gravity(
|
||||||
|
player: Single<
|
||||||
|
(
|
||||||
|
&mut Transform,
|
||||||
|
&GravityDirection,
|
||||||
|
Option<&KinematicCharacterControllerOutput>,
|
||||||
|
),
|
||||||
|
With<Player>,
|
||||||
|
>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
let (mut transform, grav, output) = player.into_inner();
|
||||||
|
let is_grounded = output.map(|o| o.grounded).unwrap_or(false);
|
||||||
|
|
||||||
|
if !is_grounded {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(grav_dir) = grav.0 else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let cur_up = transform.up();
|
||||||
|
let grav_up = grav_dir * -1.0;
|
||||||
|
|
||||||
|
let desired_rotation = get_alignment_rotation(cur_up, grav_up);
|
||||||
|
transform.rotation = transform.rotation.lerp(desired_rotation, time.delta_secs());
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use bevy::prelude::*;
|
|||||||
|
|
||||||
use crate::components::{
|
use crate::components::{
|
||||||
camera::{FollowCam, FollowTarget},
|
camera::{FollowCam, FollowTarget},
|
||||||
player::{GravityDirection, PlayerForce, PlayerMotion, PlayerVelocity},
|
player::{GravityDirection, MoveSpeed, PlayerForce, PlayerMotion, PlayerVelocity},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TypesPlugin;
|
pub struct TypesPlugin;
|
||||||
@@ -15,5 +15,6 @@ impl Plugin for TypesPlugin {
|
|||||||
app.register_type::<PlayerForce>();
|
app.register_type::<PlayerForce>();
|
||||||
app.register_type::<PlayerMotion>();
|
app.register_type::<PlayerMotion>();
|
||||||
app.register_type::<GravityDirection>();
|
app.register_type::<GravityDirection>();
|
||||||
|
app.register_type::<MoveSpeed>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
pub mod input;
|
pub mod input;
|
||||||
|
pub mod rotation;
|
||||||
|
|||||||
25
src/utils/rotation.rs
Normal file
25
src/utils/rotation.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use std::f32::{EPSILON, consts::PI};
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub fn get_alignment_rotation(cur_dir: Dir3, target_dir: Vec3) -> Quat {
|
||||||
|
let tgt = target_dir.normalize();
|
||||||
|
let axis = cur_dir.cross(tgt);
|
||||||
|
let axis_len = axis.length();
|
||||||
|
if axis_len < EPSILON {
|
||||||
|
let dot = cur_dir.dot(tgt);
|
||||||
|
if dot > 0.999 {
|
||||||
|
return Quat::IDENTITY;
|
||||||
|
} else {
|
||||||
|
let ortho = if cur_dir.x < 0.99 {
|
||||||
|
cur_dir.cross(Vec3::X)
|
||||||
|
} else {
|
||||||
|
cur_dir.cross(Vec3::Z)
|
||||||
|
};
|
||||||
|
return Quat::from_axis_angle(ortho.normalize(), PI);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let angle = cur_dir.angle_between(tgt);
|
||||||
|
return Quat::from_axis_angle(axis.normalize(), angle);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user