/* 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 . */ 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::(); } } #[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::(); } } } pub fn keyboard_input(mut movement_event_writer: EventWriter, keyboard_input: Res>){ 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