Kinematic character controller (#11)

This commit is contained in:
GitGhillie
2025-03-21 11:54:03 +01:00
committed by GitHub
parent 9e8485d3a3
commit f80e1a9d0b
7 changed files with 672 additions and 235 deletions

View File

@@ -2,18 +2,21 @@ use crate::{
alien::{ALIEN_ASSET_PATH, Animations},
camera::{CameraArmRotation, CameraTarget},
cash::{Cash, CashCollectEvent},
controller::{CharacterControllerBundle, PlayerMovement},
controls::Controls,
heads_ui::HeadChanged,
physics_layers::GameLayer,
tb_entities::SpawnPoint,
};
use avian3d::prelude::*;
use avian3d::{
math::{Scalar, Vector},
prelude::*,
};
use bevy::{
prelude::*,
window::{CursorGrabMode, PrimaryWindow},
};
use bevy_tnua::{TnuaUserControlsSystemSet, prelude::*};
use bevy_tnua_avian3d::TnuaAvian3dSensorShape;
use std::time::Duration;
#[derive(Component, Default)]
@@ -33,14 +36,8 @@ struct PlayerSpawned {
spawned: bool,
}
#[derive(Resource, Default)]
pub struct PlayerMovement {
pub any_direction: bool,
}
pub fn plugin(app: &mut App) {
app.init_resource::<PlayerSpawned>();
app.init_resource::<PlayerMovement>();
app.add_systems(Startup, (initial_grab_cursor, cursor_recenter));
app.add_systems(
Update,
@@ -51,10 +48,6 @@ pub fn plugin(app: &mut App) {
setup_animations_marker_for_player,
),
);
app.add_systems(
FixedUpdate,
apply_controls.in_set(TnuaUserControlsSystemSet),
);
app.add_systems(Update, (rotate_view_keyboard, rotate_view_gamepad));
@@ -80,21 +73,28 @@ fn spawn(
let mesh = asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/heads/angry demonstrator.glb"));
let gravity = Vector::NEG_Y * 40.0;
let collider = Collider::capsule(0.9, 1.2);
let acceleration = 30.0;
let damping = 0.95;
let jump_impulse = 18.0;
let max_slope_angle = (60.0 as Scalar).to_radians();
commands
.spawn((
Name::from("player"),
Player,
CameraTarget,
transform,
TransformInterpolation,
TransformExtrapolation,
Visibility::default(),
RigidBody::Dynamic,
Collider::capsule(1.2, 1.5),
// LockedAxes::ROTATION_LOCKED, todo
CollisionLayers::new(LayerMask(GameLayer::Player.to_bits()), LayerMask::ALL),
LockedAxes::ROTATION_LOCKED,
TnuaController::default(),
TnuaAvian3dSensorShape(Collider::cylinder(0.8, 0.0)),
CharacterControllerBundle::new(collider, gravity).with_movement(
acceleration,
damping,
jump_impulse,
max_slope_angle,
),
))
.with_children(|parent| {
parent
@@ -102,7 +102,7 @@ fn spawn(
Name::from("body rig"),
PlayerRig,
CameraArmRotation,
Transform::from_translation(Vec3::new(0., -3., 0.))
Transform::from_translation(Vec3::new(0., -1.45, 0.))
.with_rotation(Quat::from_rotation_y(std::f32::consts::PI))
.with_scale(Vec3::splat(1.4)),
SceneRoot(
@@ -187,57 +187,6 @@ fn initial_grab_cursor(mut primary_window: Query<&mut Window, With<PrimaryWindow
}
}
fn apply_controls(
controls: Res<Controls>,
mut query: Query<&mut TnuaController>,
player: Query<&Transform, With<PlayerRig>>,
mut movement: ResMut<PlayerMovement>,
) {
let Ok(mut controller) = query.get_single_mut() else {
return;
};
let Ok(player) = player.get_single() else {
return;
};
let mut direction = player.forward().as_vec3() * -controls.keyboard_state.move_dir.y;
direction += player.right().as_vec3() * -controls.keyboard_state.move_dir.x;
if let Some(gamepad) = controls.gamepad_state {
direction += player.forward().as_vec3() * -gamepad.move_dir.y;
direction += player.right().as_vec3() * -gamepad.move_dir.x;
}
if movement.any_direction != (direction != Vec3::ZERO) {
movement.any_direction = direction != Vec3::ZERO;
}
controller.basis(TnuaBuiltinWalk {
// The `desired_velocity` determines how the character will move.
desired_velocity: direction.normalize_or_zero() * 15.0,
spring_strengh: 1000.,
spring_dampening: 0.5,
// The `float_height` must be greater (even if by little) from the distance between the
// character's center and the lowest point of its collider.
float_height: 3.0,
// `TnuaBuiltinWalk` has many other fields for customizing the movement - but they have
// sensible defaults. Refer to the `TnuaBuiltinWalk`'s documentation to learn what they do.
..Default::default()
});
// Feed the jump action every frame as long as the player holds the jump button. If the player
// stops holding the jump button, simply stop feeding the action.
if controls.keyboard_state.jump || controls.gamepad_state.map(|gp| gp.jump).unwrap_or(false) {
controller.action(TnuaBuiltinJump {
// The height is the only mandatory field of the jump button.
height: 4.0,
// `TnuaBuiltinJump` also has customization fields with sensible defaults.
..Default::default()
});
}
}
fn collect_cash(
mut commands: Commands,
mut collision_event_reader: EventReader<CollisionStarted>,
@@ -264,7 +213,7 @@ fn setup_animations_marker_for_player(
mut commands: Commands,
animation_handles: Query<Entity, Added<AnimationGraphHandle>>,
parent_query: Query<&Parent>,
player: Query<&Player>,
player: Query<&PlayerRig>,
) {
for entity in animation_handles.iter() {
for ancestor in parent_query.iter_ancestors(entity) {