Implement MenuSystem, reimplement the mouse grab and menu system, add a new common module in the game systems for common functions, various other fixes

This commit is contained in:
reo 2025-11-22 14:15:53 +03:00
parent 47c3b2b111
commit b17a7636d8
7 changed files with 219 additions and 150 deletions

View file

@ -6,7 +6,8 @@ use rapier3d::dynamics::{CoefficientCombineRule, RigidBodyType};
use rapier3d::prelude::ColliderBuilder; use rapier3d::prelude::ColliderBuilder;
use winit::event::{Event, WindowEvent}; use winit::event::{Event, WindowEvent};
use systems::debug_camera::FPSDebugCameraSystem; use systems::debug_camera::FPSDebugCameraSystem;
use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; use crate::systems::common::should_draw_menu;
use crate::systems::{KeybindsSystem, KinematicCharacterController, MenuSystem, PhysicsSystem};
const TEST_GLTF: &str = "sphere.glb"; const TEST_GLTF: &str = "sphere.glb";
const PLANE_GLTF: &str = "plane.glb"; const PLANE_GLTF: &str = "plane.glb";
@ -96,22 +97,23 @@ impl System for MainSystem {
InputState, InputState,
)>().unwrap(); )>().unwrap();
let mut egui_queue = pctx.egui_queue.borrow_mut(); if should_draw_menu(scene) {
let time_ctx = pctx.time_ctx.clone(); let mut egui_queue = pctx.egui_queue.borrow_mut();
let mut character_pos = Vec3::ZERO; let time_ctx = pctx.time_ctx.clone();
for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { let mut character_pos = Vec3::ZERO;
character_pos = tr.translation; for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() {
} character_pos = tr.translation;
egui_queue.queue(move |egui_ctx| { }
egui::Window::new("Debug").show(egui_ctx, |ui| { egui_queue.queue(move |egui_ctx| {
ui.label("Hello World!"); egui::Window::new("Debug").show(egui_ctx, |ui| {
ui.label(format!("Frame Delta: {}", time_ctx.frame_dt)); ui.label("Hello World!");
ui.label(format!("Fixed Delta: {}", time_ctx.fixed_dt)); ui.label(format!("Frame Delta: {}", time_ctx.frame_dt));
ui.label(format!("FPS: {}", 1.0 / time_ctx.frame_dt)); ui.label(format!("Fixed Delta: {}", time_ctx.fixed_dt));
ui.label(format!("Character POS: {}", character_pos)); ui.label(format!("FPS: {}", 1.0 / time_ctx.frame_dt));
ui.label(format!("Character POS: {}", character_pos));
});
}); });
}); }
} }
} }
@ -121,6 +123,7 @@ fn main() {
.add_system::<KeybindsSystem>() .add_system::<KeybindsSystem>()
.add_system::<KinematicCharacterController>() .add_system::<KinematicCharacterController>()
.add_system::<FPSDebugCameraSystem>() .add_system::<FPSDebugCameraSystem>()
.add_system::<MenuSystem>()
.add_system::<MainSystem>() .add_system::<MainSystem>()
.add_system::<UpdateAspectRatioSystem>() .add_system::<UpdateAspectRatioSystem>()
.add_scene(MAIN_SCENE_ID, Scene::new(MAIN_SCENE_ID.to_owned(), None)) .add_scene(MAIN_SCENE_ID, Scene::new(MAIN_SCENE_ID.to_owned(), None))

View file

@ -0,0 +1,34 @@
use glam::Vec3;
use raidillon_app::prelude::*;
use crate::systems::menu::MenuState;
pub fn is_camera_mode_valid(scene: &mut Scene, mode: CameraMode) -> bool {
let mut q = scene.world.query::<(&Camera, &CameraMode)>();
let (cam_ent, (cam, cam_mode)) = q
.iter()
.next()
.unwrap();
*cam_mode == mode
}
pub fn is_mouse_look_enabled(scene: &mut Scene) -> bool {
let mut q = scene.world.query::<(&MenuState)>();
let (_ent, mode) = q.iter().next().unwrap();
*mode == MenuState::Closed
}
pub fn camera_front(yaw: f32, pitch: f32) -> Vec3 {
let yaw_rad = yaw.to_radians();
let pitch_rad = pitch.to_radians();
Vec3::new(
yaw_rad.cos() * pitch_rad.cos(),
pitch_rad.sin(),
yaw_rad.sin() * pitch_rad.cos(),
).normalize()
}
pub fn should_draw_menu(scene: &mut Scene) -> bool {
let mut q = scene.world.query::<(&MenuState)>();
let (_ent, mode) = q.iter().next().unwrap();
*mode == MenuState::Open
}

View file

@ -5,9 +5,11 @@ use winit::keyboard::{KeyCode, PhysicalKey};
use winit::window::CursorGrabMode; use winit::window::CursorGrabMode;
use raidillon_app::prelude::*; use raidillon_app::prelude::*;
use crate::systems::common::{camera_front, is_camera_mode_valid, is_mouse_look_enabled};
use crate::systems::menu::MenuState;
pub struct FPSDebugCameraSystem { pub struct FPSDebugCameraSystem {
mouse_delta: (f64, f64), mouse_delta: (f64, f64),
mouse_enabled: bool,
position: Vec3, position: Vec3,
yaw: f32, yaw: f32,
pitch: f32, pitch: f32,
@ -19,7 +21,6 @@ impl Default for FPSDebugCameraSystem {
fn default() -> Self { fn default() -> Self {
Self { Self {
mouse_delta: Default::default(), mouse_delta: Default::default(),
mouse_enabled: Default::default(),
position: Vec3::new(0.0, 0.0, 2.0), position: Vec3::new(0.0, 0.0, 2.0),
yaw: -90.0, yaw: -90.0,
pitch: 0.0, pitch: 0.0,
@ -31,7 +32,7 @@ impl Default for FPSDebugCameraSystem {
impl System for FPSDebugCameraSystem { impl System for FPSDebugCameraSystem {
fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) {
if !self.is_camera_mode_valid(scene) { if !(is_camera_mode_valid(scene, CameraMode::Debug) && is_mouse_look_enabled(scene)) {
return return
} }
let pctx = res.get::<PlatformContext>().unwrap(); let pctx = res.get::<PlatformContext>().unwrap();
@ -46,89 +47,43 @@ impl System for FPSDebugCameraSystem {
_ => {} _ => {}
} }
}, },
Event::WindowEvent { event, .. } => match event {
WindowEvent::MouseInput { state, button, .. } => {
if button == MouseButton::Right {
// blood and tear
let window = pctx.window.lock().unwrap();
match state {
ElementState::Pressed => {
if window
.set_cursor_grab(CursorGrabMode::Confined)
.or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked))
.is_ok()
{
window.set_cursor_visible(false);
self.mouse_enabled = true;
}
}
ElementState::Released => {
let _ = window.set_cursor_grab(CursorGrabMode::None);
window.set_cursor_visible(true);
self.mouse_enabled = false;
}
}
}
}
_ => {},
},
_ => {}, _ => {},
} }
} }
fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) {
if !self.is_camera_mode_valid(scene) {
return
}
let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap();
if self.mouse_enabled { if is_mouse_look_enabled(scene) {
self.yaw += self.mouse_delta.0 as f32 * self.sensitivity; self.yaw += self.mouse_delta.0 as f32 * self.sensitivity;
self.pitch -= self.mouse_delta.1 as f32 * self.sensitivity; self.pitch -= self.mouse_delta.1 as f32 * self.sensitivity;
self.pitch = self.pitch.clamp(-89.0, 89.0); self.pitch = self.pitch.clamp(-89.0, 89.0);
} }
let front = self.front(); let front = camera_front(self.yaw, self.pitch);
let right_vec = front.cross(Vec3::Y).normalize(); let right_vec = front.cross(Vec3::Y).normalize();
if input.key_held(KeyCode::KeyW) { if is_mouse_look_enabled(scene) {
self.position += front * pctx.time_ctx.frame_dt * self.speed; if input.key_held(KeyCode::KeyW) {
} self.position += front * pctx.time_ctx.frame_dt * self.speed;
if input.key_held(KeyCode::KeyS) { }
self.position -= front * pctx.time_ctx.frame_dt * self.speed; if input.key_held(KeyCode::KeyS) {
} self.position -= front * pctx.time_ctx.frame_dt * self.speed;
if input.key_held(KeyCode::KeyA) { }
self.position -= right_vec * pctx.time_ctx.frame_dt * self.speed; if input.key_held(KeyCode::KeyA) {
} self.position -= right_vec * pctx.time_ctx.frame_dt * self.speed;
if input.key_held(KeyCode::KeyD) { }
self.position += right_vec * pctx.time_ctx.frame_dt * self.speed; if input.key_held(KeyCode::KeyD) {
self.position += right_vec * pctx.time_ctx.frame_dt * self.speed;
}
} }
scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { if is_camera_mode_valid(scene, CameraMode::Debug) {
camera.eye = self.position; scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| {
camera.center = self.position + front; camera.eye = self.position;
}); camera.center = self.position + front;
});
}
self.mouse_delta = (0.0, 0.0); self.mouse_delta = (0.0, 0.0);
} }
} }
impl FPSDebugCameraSystem {
pub fn front(&self) -> Vec3 {
let yaw_rad = self.yaw.to_radians();
let pitch_rad = self.pitch.to_radians();
Vec3::new(
yaw_rad.cos() * pitch_rad.cos(),
pitch_rad.sin(),
yaw_rad.sin() * pitch_rad.cos(),
).normalize()
}
fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool {
let mut q = scene.world.query::<(&Camera, &CameraMode)>();
let (cam_ent, (cam, cam_mode)) = q
.iter()
.next()
.unwrap();
*cam_mode == CameraMode::Debug
}
}

View file

@ -1,6 +1,8 @@
use winit::keyboard::KeyCode; use winit::keyboard::KeyCode;
use raidillon_app::prelude::*; use raidillon_app::prelude::*;
use crate::systems::{common::should_draw_menu, menu::MenuState};
#[derive(Default)] #[derive(Default)]
pub struct KeybindsSystem { pub struct KeybindsSystem {
camera_toggle_held: bool, camera_toggle_held: bool,
@ -19,21 +21,31 @@ impl System for KeybindsSystem {
} }
fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) {
let pctx = res.get_mut::<PlatformContext>().unwrap(); if should_draw_menu(scene) {
let mut q = scene.world.query::<(&Camera, &CameraMode)>(); let pctx = res.get_mut::<PlatformContext>().unwrap();
let (cam_ent, (cam, cam_mode)) = q let mut q = scene.world.query::<(&Camera, &CameraMode)>();
.iter() let (cam_ent, (cam, cam_mode)) = q
.next() .iter()
.unwrap(); .next()
.unwrap();
let cam_mode_str = format!("Camera Mode: {:?}", cam_mode);
let cam_mode_str = format!("Camera Mode: {:?}", cam_mode); let mut q = scene.world.query::<(&MenuState)>();
let (_ent, menu_state) = q
.iter()
.next()
.unwrap();
pctx.egui_queue.borrow_mut().queue(move |egui_ctx| { let menu_state_str = format!("Menu State: {:?}", menu_state);
egui::Window::new("Camera").show(egui_ctx, |ui| {
ui.label("F5 to switch camera"); pctx.egui_queue.borrow_mut().queue(move |egui_ctx| {
ui.label(cam_mode_str); egui::Window::new("Camera").show(egui_ctx, |ui| {
ui.label("F5 to switch camera");
ui.label(cam_mode_str);
ui.label(menu_state_str)
});
}); });
}); }
} }
} }

View file

@ -8,6 +8,9 @@ use winit::event::Event;
use winit::keyboard::KeyCode; use winit::keyboard::KeyCode;
use raidillon_app::prelude::*; use raidillon_app::prelude::*;
use crate::systems::common::{camera_front, is_camera_mode_valid, is_mouse_look_enabled};
use crate::systems::menu::MenuState;
#[derive(Default)] #[derive(Default)]
pub struct KinematicCharacterController { pub struct KinematicCharacterController {
character_controller: RapierKinematicCharacterController, character_controller: RapierKinematicCharacterController,
@ -57,7 +60,7 @@ impl System for KinematicCharacterController {
} }
fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) {
if !self.is_camera_mode_valid(scene) { if !is_camera_mode_valid(scene, CameraMode::Kinematic) {
return return
} }
@ -78,45 +81,44 @@ impl System for KinematicCharacterController {
} }
fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) {
if !self.is_camera_mode_valid(scene) { let front = camera_front(self.yaw, self.pitch);
return
}
let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap();
self.yaw += (self.mouse_delta.0 as f32) * self.sensitivity;
self.pitch -= (self.mouse_delta.1 as f32) * self.sensitivity;
self.pitch = self.pitch.clamp(-89.0, 89.0);
let front = self.front();
let right_vec = front.cross(Vec3::Y).normalize(); let right_vec = front.cross(Vec3::Y).normalize();
if input.key_held(KeyCode::KeyW) { if is_camera_mode_valid(scene, CameraMode::Kinematic) && is_mouse_look_enabled(scene) {
self.desired_movement += front * pctx.time_ctx.frame_dt * self.speed; let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap();
}
if input.key_held(KeyCode::KeyS) { self.yaw += (self.mouse_delta.0 as f32) * self.sensitivity;
self.desired_movement -= front * pctx.time_ctx.frame_dt * self.speed; self.pitch -= (self.mouse_delta.1 as f32) * self.sensitivity;
} self.pitch = self.pitch.clamp(-89.0, 89.0);
if input.key_held(KeyCode::KeyA) {
self.desired_movement -= right_vec * pctx.time_ctx.frame_dt * self.speed; if input.key_held(KeyCode::KeyW) {
} self.desired_movement += front * pctx.time_ctx.frame_dt * self.speed;
if input.key_held(KeyCode::KeyD) { }
self.desired_movement += right_vec * pctx.time_ctx.frame_dt * self.speed; if input.key_held(KeyCode::KeyS) {
self.desired_movement -= front * pctx.time_ctx.frame_dt * self.speed;
}
if input.key_held(KeyCode::KeyA) {
self.desired_movement -= right_vec * pctx.time_ctx.frame_dt * self.speed;
}
if input.key_held(KeyCode::KeyD) {
self.desired_movement += right_vec * pctx.time_ctx.frame_dt * self.speed;
}
} }
let pos = Physics::rapier_translation_to_glam(&self.last_position); if is_camera_mode_valid(scene, CameraMode::Kinematic) {
let pos = Physics::rapier_translation_to_glam(&self.last_position);
scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| {
// INTERPOLATION NEEDED.
camera.eye = pos;
camera.center = pos + front;
});
}
scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| {
// INTERPOLATION NEEDED.
camera.eye = pos;
camera.center = pos + front;
});
self.mouse_delta = (0.0, 0.0); self.mouse_delta = (0.0, 0.0);
} }
fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) {
if !self.is_camera_mode_valid(scene) {
return
}
let p = scene.resources.get_mut::<Physics>().unwrap(); let p = scene.resources.get_mut::<Physics>().unwrap();
let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap();
@ -166,24 +168,3 @@ impl System for KinematicCharacterController {
self.desired_movement = Vec3::ZERO; self.desired_movement = Vec3::ZERO;
} }
} }
impl KinematicCharacterController {
pub fn front(&self) -> Vec3 {
let yaw_rad = self.yaw.to_radians();
let pitch_rad = self.pitch.to_radians();
Vec3::new(
yaw_rad.cos() * pitch_rad.cos(),
pitch_rad.sin(),
yaw_rad.sin() * pitch_rad.cos(),
).normalize()
}
fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool {
let mut q = scene.world.query::<(&Camera, &CameraMode)>();
let (cam_ent, (cam, cam_mode)) = q
.iter()
.next()
.unwrap();
*cam_mode == CameraMode::Kinematic
}
}

81
game/src/systems/menu.rs Normal file
View file

@ -0,0 +1,81 @@
use egui::Id;
use raidillon_app::prelude::*;
use winit::{dpi::{LogicalPosition, Position}, keyboard::KeyCode, window::CursorGrabMode};
#[derive(Default)]
pub struct MenuSystem {
escape_key_held: bool,
/// Unoptimal solution to fix windows event delay
times_ran_initial_win_event: u32,
}
#[derive(Default, Eq, PartialEq, Debug)]
pub enum MenuState {
Open,
#[default]
Closed,
}
impl System for MenuSystem {
fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) {
scene.world.spawn((MenuState::Closed,));
}
fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) {
// Windows won't register some events in the first miliseconds after initialization
if self.times_ran_initial_win_event < 20 {
let window = res.get::<PlatformContext>().unwrap().window.lock().unwrap();
window.set_cursor_grab(CursorGrabMode::Confined).or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked));
window.set_cursor_visible(false);
self.times_ran_initial_win_event += 1
}
let mut egui_queue = res.get::<PlatformContext>().unwrap().egui_queue.borrow_mut();
egui_queue.queue(|egui_ctx| {
egui::Area::new(Id::new("esc to pause"))
.anchor(egui::Align2::RIGHT_TOP, [-10.0, 10.0])
.show(egui_ctx, |ui| {
ui.label(
egui::RichText::new("ESC to pause").size(24.0).color(egui::Color32::BLACK)
);
});
});
}
fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) {
// The menu is toggled by pressing the escape key
let input = res.get::<InputState>().unwrap();
if input.key_held(KeyCode::Escape) {
if self.escape_key_held { return }
self.toggle_menu(res, scene);
self.escape_key_held = true;
} else {
self.escape_key_held = false;
}
}
}
impl MenuSystem {
fn toggle_menu(&mut self, res: &mut EngineResources, scene: &mut Scene) {
let q = scene.world.query_mut::<(&mut MenuState)>();
let (menu_ent, menu_state) = q
.into_iter()
.next()
.unwrap();
let window = res.get::<PlatformContext>().unwrap().window.lock().unwrap();
match *menu_state {
MenuState::Open => {
*menu_state = MenuState::Closed;
window.set_cursor_grab(CursorGrabMode::Confined).or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked));
window.set_cursor_visible(false);
},
MenuState::Closed => {
*menu_state = MenuState::Open;
window.set_cursor_grab(CursorGrabMode::None);
window.set_cursor_visible(true);
},
}
}
}

View file

@ -1,8 +1,11 @@
mod physics; mod physics;
mod kinematic_character_controller; mod kinematic_character_controller;
mod keybinds; mod keybinds;
mod menu;
pub mod debug_camera; pub mod debug_camera;
pub mod common;
pub use physics::PhysicsSystem; pub use physics::PhysicsSystem;
pub use kinematic_character_controller::KinematicCharacterController; pub use kinematic_character_controller::KinematicCharacterController;
pub use keybinds::KeybindsSystem; pub use keybinds::KeybindsSystem;
pub use menu::MenuSystem;