use bevy::{ input::mouse::AccumulatedMouseMotion, prelude::*, render::view::RenderLayers, window::{PrimaryWindow, WindowResized} }; use bevy_rapier3d::prelude::*; pub mod toolbar; use crate::{asset_loading::FlashlightAssets, GameState}; #[derive(Debug, Component, Default)] pub struct Player { pub speed_factor: f32, } #[derive(Debug, Component, Deref, DerefMut)] pub struct CameraSensitivity(Vec2); impl Default for CameraSensitivity { fn default() -> Self { Self(Vec2::new(0.003, 0.002)) } } #[derive(Debug, Component)] pub struct WorldModelCamera; #[derive(Debug, Component, Default)] pub struct PlayerInput { movement_direction: Vec3, } #[derive(Component, Debug)] pub struct HeadBob { pub enabled: bool, pub intensity: f32, pub speed: f32, pub time_offset: f32, } impl Default for HeadBob { fn default() -> Self { Self { enabled: true, intensity: 0.05, speed: 10.0, time_offset: 0.0, } } } #[derive(Component, Debug)] pub struct BaseTransform(pub Transform); pub fn plugin(app: &mut App) { app.add_plugins(toolbar::plugin) .add_systems(OnEnter(GameState::Playing), (init_player, hide_cursor)) .add_systems( Update, (move_camera, handle_input, apply_head_bob, on_resize_system).run_if(in_state(GameState::Playing)), ) .add_systems( FixedUpdate, apply_player_movement.run_if(in_state(GameState::Playing)), ); } // used by the view model camera and the player's arm. const STATIC_LAYER: usize = 1; pub fn init_player( mut commands: Commands, flashlights: Res, window: Query<&Window>, ) { commands .spawn(( Player::default(), PlayerAction::default(), CameraSensitivity::default(), PlayerInput::default(), toolbar::Item::none(), HeadBob::default(), // rapier RigidBody::Dynamic, Collider::capsule(Vec3::new(0.0, -0.5, 0.0), Vec3::new(0.0, 0.5, 0.0), 0.5), Velocity::zero(), LockedAxes::ROTATION_LOCKED, Damping { linear_damping: 6.0, angular_damping: 1.0, }, GravityScale(3.0), Transform::from_xyz(0.0, 0.5, 0.0), )) .with_children(|parent| { parent.spawn(( WorldModelCamera, Camera3d::default(), Projection::from(PerspectiveProjection { fov: 90.0_f32.to_radians(), ..default() }), )); // camera voor pitslampke parent.spawn(( Camera2d::default(), Camera { order: 1, ..default() }, RenderLayers::layer(STATIC_LAYER), )); let window = window.single(); let transform = flashlight_base_transform(window.width(), window.height()); parent.spawn(( Sprite::from_image(flashlights.flash_hold_4.clone()), transform.0.clone(), transform, RenderLayers::layer(STATIC_LAYER), )); }); } fn on_resize_system( mut resize_reader: EventReader, mut sprites: Query<(&mut Transform, &mut BaseTransform), With>, ) { for e in resize_reader.read() { for (mut transform, mut base_transform) in sprites.iter_mut() { let new_pos = flashlight_base_transform(e.width, e.height); *transform = new_pos.0.clone(); *base_transform = new_pos; } } } fn flashlight_base_transform(window_width: f32, window_height: f32) -> BaseTransform { let window_size = Vec2::new(window_width, window_height); let sprite_size = Vec2::new(101.0, 101.0); let scale = window_width / 600.0; let world_size = sprite_size * scale; let xoffset = window_size.x / 4.0 - 40.0; let yoffset = 15.0; let mut transform = Transform::from_translation( Vec3::new(window_size.x / 2.0 - world_size.x / 2.0 - xoffset, -window_size.y / 2.0 + world_size.y / 2.0 - yoffset, 0.0) ); transform.scale = Vec3::new(scale, scale, 1.0); return BaseTransform(transform); } fn hide_cursor(mut windows: Query<&mut Window, With>) { for mut window in windows.iter_mut() { window.cursor_options.visible = false; } } const PITCH_LIMIT: f32 = std::f32::consts::FRAC_PI_2 - 0.01; pub fn move_camera( accumulated_mouse_motion: Res, mut player: Query<(&mut Transform, &CameraSensitivity), With>, ) { let Ok((mut transform, camera_sensitivity)) = player.get_single_mut() else { return; }; let delta = accumulated_mouse_motion.delta; if delta != Vec2::ZERO { let delta_yaw = -delta.x * camera_sensitivity.x; let delta_pitch = -delta.y * camera_sensitivity.y; let (yaw, pitch, roll) = transform.rotation.to_euler(EulerRot::YXZ); let yaw = yaw + delta_yaw; let pitch = (pitch + delta_pitch).clamp(-PITCH_LIMIT, PITCH_LIMIT); transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, roll); } } #[derive(Component, Debug, Clone, Copy, Eq, PartialEq, Hash, Reflect, Default)] pub(crate) enum PlayerAction { #[default] Move, Sprint, Jump, Interact, ToggleFlashlight } pub fn handle_input( keyboard_input: Res>, mut query: Query<(&Transform, &mut PlayerInput, &mut PlayerAction, &mut Player), With>, ) { for (transform, mut input, mut action, mut player) in query.iter_mut() { let forward = transform.forward(); let right = transform.right(); let mut movement_direction = Vec3::ZERO; if keyboard_input.pressed(KeyCode::KeyW) { movement_direction += Vec3::new(forward.x, 0.0, forward.z).normalize_or_zero(); } if keyboard_input.pressed(KeyCode::KeyS) { movement_direction -= Vec3::new(forward.x, 0.0, forward.z).normalize_or_zero(); } if keyboard_input.pressed(KeyCode::KeyA) { movement_direction -= Vec3::new(right.x, 0.0, right.z).normalize_or_zero(); } if keyboard_input.pressed(KeyCode::KeyD) { movement_direction += Vec3::new(right.x, 0.0, right.z).normalize_or_zero(); } if keyboard_input.pressed(KeyCode::Space) { movement_direction += Vec3::Y; } if keyboard_input.pressed(KeyCode::ShiftLeft) { player.speed_factor = 1.35; *action = PlayerAction::Sprint } else if keyboard_input.pressed(KeyCode::ControlLeft) { player.speed_factor = 0.65; } else { player.speed_factor = 1.0; } if keyboard_input.pressed(KeyCode::KeyE) { *action = PlayerAction::Interact } if keyboard_input.pressed(KeyCode::KeyA) { *action = PlayerAction::ToggleFlashlight; } input.movement_direction = movement_direction.normalize_or_zero(); } } pub fn apply_player_movement(mut player_query: Query<(&PlayerInput, &mut Velocity, &Player), With>) { const SPEED: f32 = 2.6; const JUMP_FORCE: f32 = 4.0; for (input, mut velocity, player) in player_query.iter_mut() { let speed = SPEED * player.speed_factor; let horizontal_movement = Vec3::new( input.movement_direction.x * speed, 0.0, input.movement_direction.z * speed, ); velocity.linvel.x = horizontal_movement.x; velocity.linvel.z = horizontal_movement.z; if input.movement_direction.y > 0.0 { velocity.linvel.y = JUMP_FORCE; } } } pub fn apply_head_bob( time: Res