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:
NIMFER 2024-04-03 05:35:53 +02:00
parent 5ebd95ec17
commit bb8a94f07e
11 changed files with 229 additions and 20 deletions

3
.cargo/config Normal file
View file

@ -0,0 +1,3 @@
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-Clink-arg=-fuse-ld=mold"]

View file

@ -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:

View file

@ -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
View 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
View file

@ -0,0 +1,3 @@
{
"rust-analyzer.debug.engine": "vadimcn.vscode-lldb",
}

View file

@ -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
View file

@ -0,0 +1,3 @@
mod character_controller;
mod input_processor;
mod player_look;

View file

@ -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>,

View file

@ -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
View file