working on a kinematic source like character controller based on Project Borealis's open source implementation https://github.com/ProjectBorealis/PBCharacterMovement, also working on re-organizing the src folder
This commit is contained in:
parent
5ebd95ec17
commit
bb8a94f07e
11 changed files with 229 additions and 20 deletions
3
.cargo/config
Normal file
3
.cargo/config
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[target.x86_64-unknown-linux-gnu]
|
||||||
|
linker = "clang"
|
||||||
|
rustflags = ["-Clink-arg=-fuse-ld=mold"]
|
||||||
2
.github/workflows/deploy-page.yaml
vendored
2
.github/workflows/deploy-page.yaml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: sudo apt-get update; sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev
|
run: apt-get update; apt-get install --no-install-recommends -y pkg-config libx11-dev libasound2-dev libudev-dev libwayland-dev
|
||||||
- name: Install trunk
|
- name: Install trunk
|
||||||
uses: jetli/trunk-action@v0.4.0
|
uses: jetli/trunk-action@v0.4.0
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
|
|
@ -94,7 +94,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: sudo apt-get update; sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev
|
run: apt-get update; apt-get install -y pkg-config libx11-dev libasound2-dev libudev-dev
|
||||||
- name: Build release
|
- name: Build release
|
||||||
run: |
|
run: |
|
||||||
cargo build --profile dist
|
cargo build --profile dist
|
||||||
|
|
@ -180,7 +180,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: sudo apt-get update; sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev
|
run: apt-get update; apt-get install pkg-config libx11-dev libasound2-dev libudev-dev libwayland-dev
|
||||||
- name: Install trunk
|
- name: Install trunk
|
||||||
uses: jetli/trunk-action@v0.4.0
|
uses: jetli/trunk-action@v0.4.0
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
29
.lapce/run.toml
Normal file
29
.lapce/run.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# The run config is used for both run mode and debug mode
|
||||||
|
|
||||||
|
[[configs]]
|
||||||
|
# the name of this task
|
||||||
|
name = "[Dev] Build and run"
|
||||||
|
|
||||||
|
# the type of the debugger. If not set, it can't be debugged but can still be run
|
||||||
|
# type = "lldb"
|
||||||
|
|
||||||
|
# the program to run
|
||||||
|
program = "nix-shell"
|
||||||
|
|
||||||
|
# the program arguments, e.g. args = ["arg1", "arg2"], optional
|
||||||
|
args = ["--run"]
|
||||||
|
|
||||||
|
# current working directory, optional
|
||||||
|
# cwd = "${workspace}"
|
||||||
|
|
||||||
|
# enviroment variables, optional
|
||||||
|
# [configs.env]
|
||||||
|
# VAR1 = "VAL1"
|
||||||
|
# VAR2 = "VAL2"
|
||||||
|
|
||||||
|
# task to run before the run/debug session is started, optional
|
||||||
|
# [configs.prelaunch]
|
||||||
|
# program = "cargo"
|
||||||
|
# args = [
|
||||||
|
# "build",
|
||||||
|
# ]
|
||||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"rust-analyzer.debug.engine": "vadimcn.vscode-lldb",
|
||||||
|
}
|
||||||
|
|
@ -23,11 +23,7 @@ use bevy::prelude::*;
|
||||||
use bevy_editor_pls::prelude::*;
|
use bevy_editor_pls::prelude::*;
|
||||||
use bevy_xpbd_3d::prelude::*;
|
use bevy_xpbd_3d::prelude::*;
|
||||||
|
|
||||||
mod charcacter_controller;
|
use character_controller::*;
|
||||||
mod input_processor;
|
|
||||||
mod player_look;
|
|
||||||
|
|
||||||
use charcacter_controller::*;
|
|
||||||
use input_processor::*;
|
use input_processor::*;
|
||||||
use player_look::*;
|
use player_look::*;
|
||||||
|
|
||||||
|
|
|
||||||
3
src/player_utils.rs
Normal file
3
src/player_utils.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod character_controller;
|
||||||
|
mod input_processor;
|
||||||
|
mod player_look;
|
||||||
|
|
@ -16,15 +16,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::{input_processor::*, player_look::*};
|
use crate::{input_processor::*, player_look::*};
|
||||||
use bevy::{prelude::*, transform::commands};
|
use bevy::prelude::*;
|
||||||
use bevy_xpbd_3d::{math::*, prelude::*};
|
use bevy_xpbd_3d::{math::*, prelude::*, SubstepSchedule, SubstepSet};
|
||||||
|
|
||||||
pub struct CharacterControllerPlugin;
|
pub struct CharacterControllerPlugin;
|
||||||
|
|
||||||
impl Plugin for CharacterControllerPlugin {
|
impl Plugin for CharacterControllerPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(Update, move_character);
|
app.add_systems(Update, move_character);
|
||||||
app.add_systems(FixedUpdate, (dampen_movement, update_grounded));
|
app.add_systems(FixedUpdate, (apply_gravity, update_grounded, dampen_movement,));
|
||||||
|
app.add_systems(SubstepSchedule, kinematic_controller_collisions.in_set(SubstepSet::SolveUserConstraints));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +39,9 @@ pub struct CharacterController {
|
||||||
pub air_control_factor: Scalar,
|
pub air_control_factor: Scalar,
|
||||||
pub movement_dampening_factor: Scalar,
|
pub movement_dampening_factor: Scalar,
|
||||||
pub jump_impulse: Scalar,
|
pub jump_impulse: Scalar,
|
||||||
|
pub gravity: Vector,
|
||||||
pub max_slope_angle: Scalar,
|
pub max_slope_angle: Scalar,
|
||||||
|
pub speed_limit: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CharacterController {
|
impl Default for CharacterController {
|
||||||
|
|
@ -48,7 +51,9 @@ impl Default for CharacterController {
|
||||||
air_control_factor: 0.5,
|
air_control_factor: 0.5,
|
||||||
movement_dampening_factor: 0.95,
|
movement_dampening_factor: 0.95,
|
||||||
jump_impulse: 4.0,
|
jump_impulse: 4.0,
|
||||||
|
gravity: Vector::NEG_Y * 9.81 * 2.0,
|
||||||
max_slope_angle: (45.0 as Scalar).to_radians(),
|
max_slope_angle: (45.0 as Scalar).to_radians(),
|
||||||
|
speed_limit: 6668.5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +64,6 @@ pub struct CharacterControllerBundle {
|
||||||
rigid_body: RigidBody,
|
rigid_body: RigidBody,
|
||||||
collider: Collider,
|
collider: Collider,
|
||||||
ground_caster: ShapeCaster,
|
ground_caster: ShapeCaster,
|
||||||
locked_axis: LockedAxes,
|
|
||||||
restitution: Restitution,
|
restitution: Restitution,
|
||||||
player_look_rotatatable: PlayerLookRotatatable,
|
player_look_rotatatable: PlayerLookRotatatable,
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +75,7 @@ impl CharacterControllerBundle {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
character_controller: controller,
|
character_controller: controller,
|
||||||
rigid_body: RigidBody::Dynamic,
|
rigid_body: RigidBody::Kinematic,
|
||||||
collider: controller_collider,
|
collider: controller_collider,
|
||||||
ground_caster: ShapeCaster::new(
|
ground_caster: ShapeCaster::new(
|
||||||
caster_shape,
|
caster_shape,
|
||||||
|
|
@ -80,7 +84,6 @@ impl CharacterControllerBundle {
|
||||||
Direction3d::NEG_Y,
|
Direction3d::NEG_Y,
|
||||||
)
|
)
|
||||||
.with_max_time_of_impact(0.2),
|
.with_max_time_of_impact(0.2),
|
||||||
locked_axis: LockedAxes::ROTATION_LOCKED,
|
|
||||||
restitution: Restitution::new(0.0).with_combine_rule(CoefficientCombine::Min),
|
restitution: Restitution::new(0.0).with_combine_rule(CoefficientCombine::Min),
|
||||||
player_look_rotatatable: PlayerLookRotatatable,
|
player_look_rotatatable: PlayerLookRotatatable,
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +100,109 @@ impl Default for CharacterControllerBundle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_lateral_acceleration(
|
||||||
|
controller: &CharacterController,
|
||||||
|
delta_time: f32,
|
||||||
|
velocity: LinearVelocity,
|
||||||
|
) -> Vec3 {
|
||||||
|
// No acceleration in Z
|
||||||
|
let fall_acceleration = Vec3::new(
|
||||||
|
controller.gravity.x,
|
||||||
|
0.0,
|
||||||
|
controller.gravity.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bound acceleration, falling object has minimal ability to impact acceleration
|
||||||
|
let mut acceleration = fall_acceleration;
|
||||||
|
if velocity.length_squared() > 0.0 {
|
||||||
|
acceleration *= controller.air_control_factor;
|
||||||
|
acceleration = acceleration.clamp_length(0.0, controller.movement_acceleration);
|
||||||
|
}
|
||||||
|
|
||||||
|
acceleration
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// function "borrowed" from UE
|
||||||
|
fn point_plane_project(point: Vec3, plane_base: Vec3, plane_normal: Vec3) -> Vec3 {
|
||||||
|
let projection = point - plane_normal * plane_normal.dot(point - plane_base);
|
||||||
|
projection
|
||||||
|
}
|
||||||
|
|
||||||
|
// function "borrowed" from UE
|
||||||
|
fn new_fall_velocity(initial_velocity: LinearVelocity, gravity: Vec3, delta_time: f32, speed_limit: f32)
|
||||||
|
-> Vec3 {
|
||||||
|
|
||||||
|
let mut result: Vec3 = Vec3 { x: initial_velocity.x, y: initial_velocity.y, z: initial_velocity.z };
|
||||||
|
let terminal_velocity: f32 = 50.0;
|
||||||
|
|
||||||
|
if delta_time > 0.0
|
||||||
|
{
|
||||||
|
|
||||||
|
// Apply gravity.
|
||||||
|
result += gravity * delta_time;
|
||||||
|
|
||||||
|
|
||||||
|
// Don't exceed terminal velocity.
|
||||||
|
let terminal_limit: f32 = f32::abs(terminal_velocity); //FMath::Abs(GetPhysicsVolume()->TerminalVelocity);
|
||||||
|
if result.length_squared() > (terminal_limit * terminal_limit)
|
||||||
|
{
|
||||||
|
let gravity_dir: Vec3 = gravity.normalize();
|
||||||
|
|
||||||
|
if result.y > terminal_limit || gravity_dir.y > terminal_limit
|
||||||
|
{
|
||||||
|
result = point_plane_project(result, Vec3::ZERO, gravity_dir) + gravity_dir * terminal_limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.z = f32::clamp(result.z, -speed_limit, speed_limit);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_gravity(
|
||||||
|
time: Res<Time>,
|
||||||
|
mut controllers: Query<(&CharacterController, &mut LinearVelocity)>,
|
||||||
|
) {
|
||||||
|
// Precision is adjusted so that the example works with
|
||||||
|
// both the `f32` and `f64` features. Otherwise you don't need this.
|
||||||
|
let delta_time = time.delta_seconds_f64().adjust_precision();
|
||||||
|
|
||||||
|
for (character_controller, mut linear_velocity) in &mut controllers {
|
||||||
|
|
||||||
|
let fall_acceleration: Vec3 = get_lateral_acceleration(character_controller, delta_time, linear_velocity);
|
||||||
|
fall_acceleration.y = 0.0;
|
||||||
|
let has_limited_air_control: bool = false;
|
||||||
|
|
||||||
|
|
||||||
|
// Ugly fix for mismatch types, find a proper solution later
|
||||||
|
let new_velocity: Vec3 = new_fall_velocity(linear_velocity.clone(), character_controller.gravity, delta_time, character_controller.speed_limit);
|
||||||
|
linear_velocity.x = new_velocity.x;
|
||||||
|
linear_velocity.y = new_velocity.y;
|
||||||
|
linear_velocity.z = new_velocity.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn calculate_velocity(delta_time: f32, friction: f32, fluid: bool , braking_deceleration: f32, acceleration: Vec3, velocity: LinearVelocity) -> Vec3 {
|
||||||
|
let new_friction: f32 = f32::max(0.0, friction);
|
||||||
|
const MAX_ACCELERATION: f32 = 857.25;
|
||||||
|
let max_speed: f32 = 1714.5;
|
||||||
|
let force_max_acceleration: bool = false;
|
||||||
|
let analog_input_modifier: f32 = 1.0;
|
||||||
|
let analog_speed: f32 = 10.0;
|
||||||
|
|
||||||
|
if(force_max_acceleration){
|
||||||
|
|
||||||
|
acceleration = acceleration.normalize() * MAX_ACCELERATION;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
max_speed = f32::max(max_speed * analog_input_modifier, analog_speed)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fn update_grounded(
|
fn update_grounded(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut query: Query<(Entity, &CharacterController, &Rotation, &ShapeHits)>,
|
mut query: Query<(Entity, &CharacterController, &Rotation, &ShapeHits)>,
|
||||||
|
|
@ -113,7 +219,7 @@ fn update_grounded(
|
||||||
angle <= character_controller.max_slope_angle
|
angle <= character_controller.max_slope_angle
|
||||||
});
|
});
|
||||||
|
|
||||||
println!("is grounded: {}", is_grounded);
|
//println!("is grounded: {}", is_grounded);
|
||||||
|
|
||||||
if is_grounded {
|
if is_grounded {
|
||||||
commands.entity(entity).insert(Grounded);
|
commands.entity(entity).insert(Grounded);
|
||||||
|
|
@ -123,6 +229,78 @@ fn update_grounded(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn kinematic_controller_collisions(
|
||||||
|
collisions: Res<Collisions>,
|
||||||
|
collider_parents: Query<&ColliderParent, Without<Sensor>>,
|
||||||
|
mut character_controllers: Query<
|
||||||
|
(
|
||||||
|
&RigidBody,
|
||||||
|
&mut Position,
|
||||||
|
&Rotation,
|
||||||
|
&mut LinearVelocity,
|
||||||
|
&CharacterController,
|
||||||
|
)
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
// Iterate through collisions and move the kinematic body to resolve penetration
|
||||||
|
for contacts in collisions.iter() {
|
||||||
|
// If the collision didn't happen during this substep, skip the collision
|
||||||
|
if !contacts.during_current_substep {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the rigid body entities of the colliders (colliders could be children)
|
||||||
|
let Ok([collider_parent1, collider_parent2]) =
|
||||||
|
collider_parents.get_many([contacts.entity1, contacts.entity2])
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the body of the character controller and whether it is the first
|
||||||
|
// or second entity in the collision.
|
||||||
|
let is_first: bool;
|
||||||
|
let (rb, mut position, rotation, mut linear_velocity, character_controller) =
|
||||||
|
if let Ok(character) = character_controllers.get_mut(collider_parent1.get()) {
|
||||||
|
is_first = true;
|
||||||
|
character
|
||||||
|
} else if let Ok(character) = character_controllers.get_mut(collider_parent2.get()) {
|
||||||
|
is_first = false;
|
||||||
|
character
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This system only handles collision response for kinematic character controllers
|
||||||
|
if !rb.is_kinematic() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through contact manifolds and their contacts.
|
||||||
|
// Each contact in a single manifold shares the same contact normal.
|
||||||
|
for manifold in contacts.manifolds.iter() {
|
||||||
|
let normal = if is_first {
|
||||||
|
-manifold.global_normal1(rotation)
|
||||||
|
} else {
|
||||||
|
-manifold.global_normal2(rotation)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Solve each penetrating contact in the manifold
|
||||||
|
for contact in manifold.contacts.iter().filter(|c| c.penetration > 0.0) {
|
||||||
|
position.0 += normal * contact.penetration;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the slope isn't too steep to walk on but the character
|
||||||
|
// is falling, reset vertical velocity.
|
||||||
|
if normal.angle_between(Vector::Y).abs() <= character_controller.max_slope_angle
|
||||||
|
&& linear_velocity.y < 0.0
|
||||||
|
{
|
||||||
|
linear_velocity.y = linear_velocity.y.max(0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn move_character(
|
fn move_character(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut movement_event_reader: EventReader<MovementAction>,
|
mut movement_event_reader: EventReader<MovementAction>,
|
||||||
|
|
@ -15,12 +15,9 @@ You should have received a copy of the GNU General Public License 3.0
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::{charcacter_controller, input_processor::*, CharacterController};
|
use crate::input_processor::*;
|
||||||
use bevy::{
|
use bevy::{
|
||||||
input::{
|
input::mouse::MouseMotion,
|
||||||
mouse::{MouseButtonInput, MouseMotion, MouseWheel},
|
|
||||||
touchpad::{TouchpadMagnify, TouchpadRotate},
|
|
||||||
},
|
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
0
src/weapon_framework.rs
Normal file
0
src/weapon_framework.rs
Normal file
Loading…
Reference in a new issue