180 lines
No EOL
5.8 KiB
Rust
180 lines
No EOL
5.8 KiB
Rust
/*
|
|
Trouble in Terror Town: Community Edition
|
|
Copyright (C) 2024 Mikolaj Wojciech Gorski
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use bevy::prelude::*;
|
|
use bevy_xpbd_3d::{math::*, prelude::*};
|
|
|
|
pub struct CharacterControllerPlugin;
|
|
|
|
impl Plugin for CharacterControllerPlugin{
|
|
fn build(&self, app: &mut App) {
|
|
app.add_systems(Update,(
|
|
update_grounded,
|
|
keyboard_input,
|
|
move_character,
|
|
dampen_movement)
|
|
);
|
|
app.add_event::<MovementAction>();
|
|
}
|
|
}
|
|
|
|
#[derive(Event)]
|
|
pub enum MovementAction {
|
|
Move(Vector2),
|
|
Jump
|
|
}
|
|
|
|
#[derive(Component)]
|
|
#[component(storage = "SparseSet")]
|
|
pub struct Grounded;
|
|
|
|
#[derive(Component)]
|
|
pub struct CharacterController{
|
|
pub movement_acceleration: Scalar,
|
|
pub movement_dampening_factor: Scalar,
|
|
pub jump_impulse: Scalar,
|
|
pub max_slope_angle: Scalar,
|
|
}
|
|
|
|
impl Default for CharacterController {
|
|
fn default() -> Self {
|
|
CharacterController{
|
|
movement_acceleration: 30.0,
|
|
movement_dampening_factor: 0.95,
|
|
jump_impulse: 7.0,
|
|
max_slope_angle: (30.0 as Scalar).to_radians(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Bundle)]
|
|
pub struct CharacterControllerBundle{
|
|
character_controller: CharacterController,
|
|
rigid_body: RigidBody,
|
|
collider: Collider,
|
|
ground_caster: ShapeCaster,
|
|
locked_axis: LockedAxes,
|
|
}
|
|
|
|
impl CharacterControllerBundle {
|
|
pub fn new(controller_collider: Collider, controller: CharacterController) -> Self {
|
|
let mut caster_shape = controller_collider.clone();
|
|
caster_shape.set_scale(Vector::ONE * 0.99, 10);
|
|
|
|
|
|
Self{
|
|
character_controller: controller,
|
|
rigid_body: RigidBody::Dynamic,
|
|
collider: controller_collider,
|
|
ground_caster: ShapeCaster::new(
|
|
caster_shape,
|
|
Vector::ZERO,
|
|
Quaternion::default(),
|
|
Direction3d::NEG_Y,
|
|
),
|
|
locked_axis: LockedAxes::ROTATION_LOCKED,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for CharacterControllerBundle {
|
|
fn default() -> Self {
|
|
let collider = Collider::capsule(1.0, 0.5);
|
|
let mut caster_shape = collider.clone();
|
|
caster_shape.set_scale(Vector::ONE * 0.99, 10);
|
|
CharacterControllerBundle{
|
|
character_controller: CharacterController::default(),
|
|
rigid_body: RigidBody::Dynamic,
|
|
collider: collider,
|
|
ground_caster: ShapeCaster::new(
|
|
caster_shape,
|
|
Vector::ZERO,
|
|
Quaternion::default(),
|
|
Direction3d::NEG_Y,
|
|
),
|
|
locked_axis: LockedAxes::ROTATION_LOCKED,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn update_grounded(mut commands: Commands, mut query: Query<(Entity, &CharacterController, &Rotation, &ShapeHits)>){
|
|
|
|
|
|
for (entity, character_controller, rotation, hits) in &mut query {
|
|
let is_grounded = hits.iter().any(|hit| {
|
|
rotation.rotate(-hit.normal2).angle_between(Vector::Y).abs() <= character_controller.max_slope_angle
|
|
});
|
|
|
|
//println!("{}", is_grounded);
|
|
|
|
if is_grounded {
|
|
commands.entity(entity).insert(Grounded);
|
|
} else {
|
|
commands.entity(entity).remove::<Grounded>();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
pub fn keyboard_input(mut movement_event_writer: EventWriter<MovementAction>, keyboard_input: Res<ButtonInput<KeyCode>>){
|
|
let up = keyboard_input.any_pressed([KeyCode::KeyW, KeyCode::ArrowUp]);
|
|
let down = keyboard_input.any_pressed([KeyCode::KeyS, KeyCode::ArrowDown]);
|
|
let left = keyboard_input.any_pressed([KeyCode::KeyA, KeyCode::ArrowLeft]);
|
|
let right = keyboard_input.any_pressed([KeyCode::KeyD, KeyCode::ArrowRight]);
|
|
|
|
let horizontal = right as i8 - left as i8;
|
|
let vertical = up as i8 - down as i8;
|
|
let direction = Vector2::new(horizontal as Scalar, vertical as Scalar).clamp_length_max(1.0);
|
|
|
|
if direction != Vector2::ZERO {
|
|
movement_event_writer.send(MovementAction::Move(direction));
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Space) {
|
|
movement_event_writer.send(MovementAction::Jump);
|
|
}
|
|
}
|
|
|
|
pub fn move_character(time: Res<Time>, mut movement_event_reader: EventReader<MovementAction>,
|
|
mut qeury: Query<(&mut LinearVelocity, &CharacterController)>
|
|
){
|
|
let delta_time = time.delta_seconds_f64().adjust_precision();
|
|
|
|
for event in movement_event_reader.read() {
|
|
for (mut velocity, character_controller) in &mut qeury{
|
|
match event{
|
|
MovementAction::Move(direction) => {
|
|
velocity.x += direction.x * character_controller.movement_acceleration * delta_time;
|
|
velocity.z -= direction.y * character_controller.movement_acceleration * delta_time;
|
|
},
|
|
MovementAction::Jump => {
|
|
velocity.y = character_controller.jump_impulse;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
pub fn dampen_movement(mut qeury: Query<(&mut LinearVelocity, &CharacterController)>){
|
|
for (mut velocity, character_controller) in &mut qeury {
|
|
// We could use `LinearDamping`, but we don't want to dampen movement along the Y axis
|
|
velocity.x *= character_controller.movement_dampening_factor;
|
|
velocity.z *= character_controller.movement_dampening_factor;
|
|
}
|
|
} |