feat: First iteration of character controller overhaul

feat/player-physics
hheik 2022-12-18 06:17:17 +02:00
parent 50367637b1
commit 50544b06bf
5 changed files with 68 additions and 48 deletions

View File

@ -70,13 +70,13 @@ pub struct KinematicProperties {
impl Default for KinematicProperties { impl Default for KinematicProperties {
fn default() -> Self { fn default() -> Self {
Self { Self {
ground_speed: 100.0, ground_speed: 75.0,
ground_acceleration: 20.0, ground_acceleration: 20.0,
ground_friction: 30.0, ground_friction: 30.0,
air_speed: 100.0, air_speed: 75.0,
air_acceleration: 10.0, air_acceleration: 10.0,
air_friction: 10.0, air_friction: 10.0,
jump_height: 150.0, jump_height: 100.0,
gravity: Some(1.0), gravity: Some(1.0),
} }
} }
@ -90,7 +90,6 @@ pub struct KinematicInput {
} }
fn kinematic_movement( fn kinematic_movement(
time: Res<Time>,
mut query: Query<( mut query: Query<(
Entity, Entity,
&mut KinematicState, &mut KinematicState,
@ -98,14 +97,22 @@ fn kinematic_movement(
&KinematicProperties, &KinematicProperties,
&GlobalTransform, &GlobalTransform,
Option<&KinematicInput>, Option<&KinematicInput>,
Option<&CollisionGroups>,
)>, )>,
shape_query: Query<&Collider, Without<Sensor>>, shape_query: Query<&Collider, Without<Sensor>>,
child_query: Query<&Children>, child_query: Query<&Children>,
mut rapier_context: ResMut<RapierContext>, mut rapier_context: ResMut<RapierContext>,
) { ) {
let dt = rapier_context.integration_parameters.dt; let dt = rapier_context.integration_parameters.dt;
for (entity, mut kinematic_state, mut transform, props, global_transform, input) in for (
query.iter_mut() entity,
mut kinematic_state,
mut transform,
props,
global_transform,
input,
collision_groups,
) in query.iter_mut()
{ {
let default = &KinematicInput::default(); let default = &KinematicInput::default();
let input = input.unwrap_or(default); let input = input.unwrap_or(default);
@ -125,13 +132,21 @@ fn kinematic_movement(
}; };
const GRAVITY_DIR: Vec2 = Vec2::NEG_Y; const GRAVITY_DIR: Vec2 = Vec2::NEG_Y;
const GRAVITY_COEFFICIENT: f32 = 2.0;
let current_velocity = kinematic_state let current_velocity = kinematic_state
.last_move .last_move
.as_ref() .as_ref()
.map_or(Vec2::ZERO, |last| last.effective_translation) / dt; .map_or(Vec2::ZERO, |last| {
let target_velocity = if last.grounded {
input.movement * speed; last.effective_translation
.reject_from_normalized(GRAVITY_DIR)
} else {
last.effective_translation
}
})
/ dt;
let target_velocity = input.movement * speed;
let angle_lerp = if current_velocity.length_squared() > 0.01 { let angle_lerp = if current_velocity.length_squared() > 0.01 {
let result = inverse_lerp( let result = inverse_lerp(
@ -153,11 +168,13 @@ fn kinematic_movement(
let velocity_change_speed = lerp(acceleration, friction, delta_interpolation) * speed; let velocity_change_speed = lerp(acceleration, friction, delta_interpolation) * speed;
let mut velocity = if let Some(gravity) = props.gravity { let mut velocity = if let Some(gravity) = props.gravity {
// Also apply gravity
move_towards_vec2( move_towards_vec2(
current_velocity, current_velocity,
target_velocity.reject_from_normalized(GRAVITY_DIR) + current_velocity.project_onto_normalized(GRAVITY_DIR), target_velocity.reject_from_normalized(GRAVITY_DIR)
+ current_velocity.project_onto_normalized(GRAVITY_DIR),
velocity_change_speed * dt, velocity_change_speed * dt,
) + GRAVITY_DIR * gravity * dt ) + GRAVITY_DIR * GRAVITY_COEFFICIENT * gravity
} else { } else {
move_towards_vec2( move_towards_vec2(
current_velocity, current_velocity,
@ -166,10 +183,6 @@ fn kinematic_movement(
) )
}; };
if velocity.y > 0.01 || velocity.y < -0.01 {
println!("vertical velocity: {velocity:?}")
}
if input.want_jump && kinematic_state.can_jump() { if input.want_jump && kinematic_state.can_jump() {
velocity = Vec2 { velocity = Vec2 {
y: props.jump_height, y: props.jump_height,
@ -188,11 +201,6 @@ fn kinematic_movement(
None None
}; };
// gravity
if let Some(gravity) = props.gravity {
velocity.y += -9.81 * gravity * dt;
}
// move // move
kinematic_state.last_move = if let Some(shape) = shape { kinematic_state.last_move = if let Some(shape) = shape {
let (_scale, rotation, translation) = global_transform.to_scale_rotation_translation(); let (_scale, rotation, translation) = global_transform.to_scale_rotation_translation();
@ -208,24 +216,43 @@ fn kinematic_movement(
max_slope_climb_angle: (50.0_f32).to_radians(), max_slope_climb_angle: (50.0_f32).to_radians(),
min_slope_slide_angle: (50.0_f32).to_radians(), min_slope_slide_angle: (50.0_f32).to_radians(),
snap_to_ground: Some(CharacterLength::Absolute(5.0)), snap_to_ground: Some(CharacterLength::Absolute(5.0)),
// snap_to_ground: props.gravity.map_or(None, |_| {
// if velocity.y <= 0.0 {
// Some(CharacterLength::Absolute(5.0))
// } else {
// None
// }
// }),
offset: CharacterLength::Absolute(0.01), offset: CharacterLength::Absolute(0.01),
..MoveShapeOptions::default() ..MoveShapeOptions::default()
}; };
let mut filter = QueryFilter::new();
let predicate = |coll_entity| coll_entity != entity;
filter.predicate = Some(&predicate);
if let Some(collision_groups) = collision_groups {
filter.groups(InteractionGroups::new(
bevy_rapier2d::rapier::geometry::Group::from_bits_truncate(
collision_groups.memberships.bits(),
),
bevy_rapier2d::rapier::geometry::Group::from_bits_truncate(
collision_groups.filters.bits(),
),
));
}
let last_move: MoveShapeOutput = rapier_context.move_shape( let last_move: MoveShapeOutput = rapier_context.move_shape(
velocity * dt, velocity * dt,
// Vec2::X * dt,
shape, shape,
translation.truncate(), translation.truncate(),
rotation.to_euler(EulerRot::ZYX).0, rotation.to_euler(EulerRot::ZYX).0,
shape.raw.0.mass_properties(1.0).mass(), shape.raw.0.mass_properties(1.0).mass(),
move_options, move_options,
QueryFilter::new(), filter,
|_coll: CharacterCollision| (), |_coll: CharacterCollision| (),
); );
// println!("moved: {moved:?}, diff: {diff:?}", moved=last_move.effective_translation, diff=(velocity * dt - last_move.effective_translation));
// Apply movement // Apply movement
transform.translation += last_move.effective_translation.extend(0.0); transform.translation += last_move.effective_translation.extend(0.0);
@ -239,24 +266,6 @@ fn kinematic_movement(
if velocity.y <= 0.0 { if velocity.y <= 0.0 {
kinematic_state.did_jump = false; kinematic_state.did_jump = false;
} }
// if let Some(collider) = collider {
// let (_, rot, pos) = global_transform.to_scale_rotation_translation();
// let angle = rot.to_euler(EulerRot::YXZ).2;
// let mut shape = collider.clone();
// shape.set_scale(Vec2::ONE * 0.9, 1);
// if let Some((_coll_entity, _hit)) = rapier_context.cast_shape(
// Vec2::new(pos.x, pos.y),
// angle,
// Vec2::NEG_Y,
// &shape,
// 2.0,
// QueryFilter::new().exclude_collider(entity),
// ) {
// kinematic_state.on_ground = true;
// } else {
// kinematic_state.on_ground = false;
// }
// }
} }
} }
} }

View File

@ -63,10 +63,6 @@ pub fn player_spawn(mut commands: Commands) {
transform: TransformBundle::from_transform(Transform::from_translation(Vec3::new( transform: TransformBundle::from_transform(Transform::from_translation(Vec3::new(
256.0, 128.0, 0.0, 256.0, 128.0, 0.0,
))), ))),
properties: KinematicProperties {
gravity: None,
..default()
},
..default() ..default()
}; };

View File

@ -4,7 +4,7 @@ use super::{
local_to_texel_index, texel_index_to_local, Terrain2D, TerrainEvent2D, Texel2D, TexelID, local_to_texel_index, texel_index_to_local, Terrain2D, TerrainEvent2D, Texel2D, TexelID,
NEIGHBOUR_INDEX_MAP, NEIGHBOUR_INDEX_MAP,
}; };
use crate::util::{Segment2I, Vector2I}; use crate::util::{CollisionLayers, Segment2I, Vector2I};
use bevy::{ use bevy::{
prelude::*, prelude::*,
render::{render_resource::Extent3d, texture::ImageSampler}, render::{render_resource::Extent3d, texture::ImageSampler},
@ -574,7 +574,9 @@ pub fn chunk_collision_sync(
} }
} }
// Kinda messy, partly due do how entity creatin is queued // let layer_membership = CollisionLayers::WORLD;
// REM: Kinda messy, partly due do how entity creation is timed
for (entity, chunk_component) in updated_chunks.iter() { for (entity, chunk_component) in updated_chunks.iter() {
let chunk = terrain.index_to_chunk(&chunk_component.index).unwrap(); let chunk = terrain.index_to_chunk(&chunk_component.index).unwrap();
let new_islands = chunk.create_collision_data(); let new_islands = chunk.create_collision_data();
@ -594,6 +596,7 @@ pub fn chunk_collision_sync(
builder builder
.spawn(Collider::polyline(island.clone(), None)) .spawn(Collider::polyline(island.clone(), None))
.insert(TransformBundle::default()) .insert(TransformBundle::default())
.insert(CollisionGroups::new(CollisionLayers::WORLD, Group::ALL))
.insert(Name::new(format!("Island #{}", index))); .insert(Name::new(format!("Island #{}", index)));
}); });
} }
@ -605,6 +608,7 @@ pub fn chunk_collision_sync(
builder builder
.spawn(Collider::polyline(island.clone(), None)) .spawn(Collider::polyline(island.clone(), None))
.insert(TransformBundle::default()) .insert(TransformBundle::default())
.insert(CollisionGroups::new(CollisionLayers::WORLD, Group::ALL))
.insert(Name::new(format!("Island #{}", index))); .insert(Name::new(format!("Island #{}", index)));
}); });
} }

View File

@ -1,10 +1,12 @@
use bevy::prelude::*; use bevy::prelude::*;
mod collision_layers;
pub mod math; pub mod math;
mod segment2_i32; mod segment2_i32;
mod vector2; mod vector2;
mod vector2_i32; mod vector2_i32;
pub use collision_layers::*;
pub use segment2_i32::*; pub use segment2_i32::*;
pub use vector2::*; pub use vector2::*;
pub use vector2_i32::*; pub use vector2_i32::*;

View File

@ -0,0 +1,9 @@
use bevy_rapier2d::prelude::*;
pub struct CollisionLayers;
impl CollisionLayers {
pub const WORLD: Group = Group::GROUP_1;
pub const PLAYER: Group = Group::GROUP_2;
pub const ENEMY: Group = Group::GROUP_3;
}