如何用winit检测按键与鼠标移动?Rust相机控制实现求助
使用winit实现WASD+鼠标控制相机
一、核心思路
要实现相机控制,核心是三点:
- 跟踪WASD按键的按住状态,控制相机前后左右平移
- 捕获鼠标并获取相对移动量,控制相机视角旋转
- 基于帧时间差更新相机状态,保证不同帧率下速度一致
二、分步实现
1. 定义输入状态结构体
先封装一个结构体跟踪按键和鼠标的状态,方便帧更新时读取:
use winit::event::{ElementState, MouseButton, VirtualKeyCode, WindowEvent}; #[derive(Default)] struct InputState { // WASD按键按住状态 w_pressed: bool, a_pressed: bool, s_pressed: bool, d_pressed: bool, // 鼠标相对移动量(每帧重置) mouse_delta: (f32, f32), // 是否锁定鼠标 cursor_locked: bool, }
2. 处理窗口事件,更新输入状态
在事件循环中捕获键盘、鼠标事件,实时更新输入状态:
fn handle_event(event: &WindowEvent, input_state: &mut InputState, window: &winit::window::Window) { match event { // 键盘按键按下/抬起事件 WindowEvent::KeyboardInput { input, .. } => { let is_pressed = input.state == ElementState::Pressed; if let Some(key) = input.virtual_keycode { match key { VirtualKeyCode::W => input_state.w_pressed = is_pressed, VirtualKeyCode::A => input_state.a_pressed = is_pressed, VirtualKeyCode::S => input_state.s_pressed = is_pressed, VirtualKeyCode::D => input_state.d_pressed = is_pressed, VirtualKeyCode::Escape => { // 按ESC切换鼠标锁定状态 input_state.cursor_locked = !input_state.cursor_locked; window.set_cursor_grab(input_state.cursor_locked).unwrap(); window.set_cursor_visible(!input_state.cursor_locked); } _ => {} } } } // 鼠标移动事件(仅锁定时处理相对位移) WindowEvent::MouseMotion { delta, .. } => { if input_state.cursor_locked { input_state.mouse_delta = (delta.0 as f32, delta.1 as f32); } } // 左键点击锁定鼠标(可选操作) WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left, .. } => { if !input_state.cursor_locked { input_state.cursor_locked = true; window.set_cursor_grab(true).unwrap(); window.set_cursor_visible(false); } } _ => {} } }
3. 定义相机结构体与更新逻辑
用glam库简化3D数学计算,封装相机的位置、朝向和更新逻辑:
use glam::{Vec3, Mat4}; struct Camera { position: Vec3, yaw: f32, // 左右旋转角度(偏航角) pitch: f32, // 上下旋转角度(俯仰角) move_speed: f32, look_speed: f32, } impl Camera { fn new(init_pos: Vec3) -> Self { Self { position: init_pos, yaw: -90.0, // 默认朝向-Z轴(屏幕前方) pitch: 0.0, move_speed: 5.0, look_speed: 0.1, } } // 获取相机视图矩阵(用于渲染) fn view_matrix(&self) -> Mat4 { let front = self.get_front_vector(); Mat4::look_at_rh(self.position, self.position + front, Vec3::Y) } // 计算相机前方向向量 fn get_front_vector(&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 update(&mut self, input: &mut InputState, delta_time: f32) { // 处理WASD平移 let front = self.get_front_vector(); let right = front.cross(Vec3::Y).normalize(); let speed = self.move_speed * delta_time; if input.w_pressed { self.position += front * speed; } if input.s_pressed { self.position -= front * speed; } if input.a_pressed { self.position -= right * speed; } if input.d_pressed { self.position += right * speed; } // 处理鼠标旋转 if input.cursor_locked { let (dx, dy) = input.mouse_delta; self.yaw += dx * self.look_speed; self.pitch -= dy * self.look_speed; // 反转Y轴,符合直觉 // 限制俯仰角,避免相机翻转 self.pitch = self.pitch.clamp(-89.0, 89.0); } // 重置鼠标位移,避免下一帧重复处理 input.mouse_delta = (0.0, 0.0); } }
4. 整合到主事件循环
将上述模块整合到winit的主循环中,完成完整流程:
fn main() { let event_loop = winit::event_loop::EventLoop::new(); let window = winit::window::WindowBuilder::new() .with_title("WASD Mouse Camera") .build(&event_loop) .unwrap(); let mut input_state = InputState::default(); let mut camera = Camera::new(Vec3::new(0.0, 0.0, 5.0)); let mut last_frame = std::time::Instant::now(); event_loop.run(move |event, _, control_flow| { *control_flow = winit::event_loop::ControlFlow::Poll; match event { winit::event::Event::WindowEvent { event, window_id } if window_id == window.id() => { if matches!(event, WindowEvent::CloseRequested) { *control_flow = winit::event_loop::ControlFlow::Exit; } handle_event(&event, &mut input_state, &window); } winit::event::Event::MainEventsCleared => { // 计算帧时间差,保证移动速度稳定 let now = std::time::Instant::now(); let delta_time = now.duration_since(last_frame).as_secs_f32(); last_frame = now; // 更新相机状态 camera.update(&mut input_state, delta_time); // 这里可以添加渲染逻辑,比如使用camera.view_matrix() println!("Pos: {:?}, Yaw: {:.1}, Pitch: {:.1}", camera.position, camera.yaw, camera.pitch); window.request_redraw(); } winit::event::Event::RedrawRequested(_) => { // 渲染代码写在此处 } _ => {} } }); }
三、依赖配置
在Cargo.toml中添加所需依赖:
[dependencies] winit = "0.29" glam = "0.24"
四、关键注意事项
- 鼠标锁定:调用
set_cursor_grab(true)后,鼠标会被限制在窗口内,此时的MouseMotion事件返回相对位移,适合控制视角 - 帧时间差:用
delta_time缩放移动/旋转速度,避免帧率波动导致速度不稳定 - 俯仰角限制:将俯仰角限制在-89°到89°之间,防止相机翻转后视图异常
- 退出控制:按ESC键可切换鼠标锁定状态,方便退出相机控制模式
内容的提问来源于stack exchange,提问作者Question Asker
相关产品推荐
相关产品推荐

