input基础
Bevy Input 基础教程
本教程基于Bevy官方示例,按主题组织,提供易于理解的输入系统使用参考。
键盘输入
基础键盘输入
示例文件: keyboard_input.rs
最基本的键盘输入处理:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, keyboard_input_system)
.run();
}
/// 处理键盘输入的系统
fn keyboard_input_system(keyboard_input: Res<ButtonInput<KeyCode>>) {
// 检查按键是否正在被按下
if keyboard_input.pressed(KeyCode::KeyA) {
info!("'A' 键正在被按下");
}
// 检查按键是否刚刚被按下
if keyboard_input.just_pressed(KeyCode::KeyA) {
info!("'A' 键刚刚被按下");
}
// 检查按键是否刚刚被释放
if keyboard_input.just_released(KeyCode::KeyA) {
info!("'A' 键刚刚被释放");
}
}关键要点:
- 使用
Res<ButtonInput<KeyCode>>获取键盘输入状态 pressed()检查按键是否正在被按下just_pressed()检查按键是否刚刚被按下(只在按下瞬间触发一次)just_released()检查按键是否刚刚被释放
键盘事件监听
示例文件: keyboard_input_events.rs
监听所有键盘事件:
use bevy::{input::keyboard::KeyboardInput, prelude::*};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, print_keyboard_event_system)
.run();
}
/// 打印所有键盘事件的系统
fn print_keyboard_event_system(mut keyboard_input_events: EventReader<KeyboardInput>) {
for event in keyboard_input_events.read() {
info!("键盘事件: {:?}", event);
}
}关键要点:
- 使用
EventReader<KeyboardInput>监听键盘事件 - 事件包含按键状态、物理键、逻辑键等信息
- 适合需要详细键盘信息的场景
键盘修饰键
示例文件: keyboard_modifiers.rs
处理组合键和修饰键:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, keyboard_input_system)
.run();
}
/// 处理组合键的系统
fn keyboard_input_system(input: Res<ButtonInput<KeyCode>>) {
// 检查Shift键是否被按下(左Shift或右Shift)
let shift = input.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);
// 检查Ctrl键是否被按下(左Ctrl或右Ctrl)
let ctrl = input.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]);
// 检查组合键 Ctrl + Shift + A
if ctrl && shift && input.just_pressed(KeyCode::KeyA) {
info!("刚刚按下了 Ctrl + Shift + A!");
}
// 检查其他组合键
if ctrl && input.just_pressed(KeyCode::KeyS) {
info!("保存快捷键被触发");
}
if ctrl && input.just_pressed(KeyCode::KeyZ) {
info!("撤销快捷键被触发");
}
}关键要点:
any_pressed()检查多个按键中是否有任意一个被按下- 支持左右修饰键的检测
- 适合实现快捷键和组合键功能
字符输入
示例文件: char_input_events.rs
处理字符输入(支持多语言):
use bevy::{
input::keyboard::{Key, KeyboardInput},
prelude::*,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, print_char_event_system)
.run();
}
/// 处理字符输入的系统
fn print_char_event_system(mut char_input_events: EventReader<KeyboardInput>) {
for event in char_input_events.read() {
// 只处理按键按下事件
if !event.state.is_pressed() {
continue;
}
// 检查是否为字符输入
if let Key::Character(character) = &event.logical_key {
info!("输入字符: '{}'", character);
}
}
}关键要点:
- 使用
event.logical_key获取逻辑键值 Key::Character表示字符输入- 支持多语言字符输入
- 适合文本输入和聊天功能
鼠标输入
基础鼠标输入
示例文件: mouse_input.rs
处理鼠标按钮和移动:
use bevy::{
input::mouse::{AccumulatedMouseMotion, AccumulatedMouseScroll},
prelude::*,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, (mouse_click_system, mouse_move_system))
.run();
}
/// 处理鼠标点击的系统
fn mouse_click_system(mouse_button_input: Res<ButtonInput<MouseButton>>) {
// 检查左键是否正在被按下
if mouse_button_input.pressed(MouseButton::Left) {
info!("左键正在被按下");
}
// 检查左键是否刚刚被按下
if mouse_button_input.just_pressed(MouseButton::Left) {
info!("左键刚刚被按下");
}
// 检查左键是否刚刚被释放
if mouse_button_input.just_released(MouseButton::Left) {
info!("左键刚刚被释放");
}
// 检查右键
if mouse_button_input.just_pressed(MouseButton::Right) {
info!("右键刚刚被按下");
}
// 检查中键
if mouse_button_input.just_pressed(MouseButton::Middle) {
info!("中键刚刚被按下");
}
}
/// 处理鼠标移动和滚轮的系统
fn mouse_move_system(
accumulated_mouse_motion: Res<AccumulatedMouseMotion>,
accumulated_mouse_scroll: Res<AccumulatedMouseScroll>,
) {
// 处理鼠标移动
if accumulated_mouse_motion.delta != Vec2::ZERO {
let delta = accumulated_mouse_motion.delta;
info!("鼠标移动了 ({}, {})", delta.x, delta.y);
}
// 处理鼠标滚轮
if accumulated_mouse_scroll.delta != Vec2::ZERO {
let delta = accumulated_mouse_scroll.delta;
info!("鼠标滚动了 ({}, {})", delta.x, delta.y);
}
}关键要点:
ButtonInput<MouseButton>处理鼠标按钮AccumulatedMouseMotion处理鼠标移动AccumulatedMouseScroll处理滚轮滚动- 支持左键、右键、中键检测
鼠标事件监听
示例文件: mouse_input_events.rs
监听详细的鼠标事件:
use bevy::{
input::{
gestures::*,
mouse::{MouseButtonInput, MouseMotion, MouseWheel},
},
prelude::*,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, print_mouse_events_system)
.run();
}
/// 打印所有鼠标事件的系统
fn print_mouse_events_system(
mut mouse_button_input_events: EventReader<MouseButtonInput>,
mut mouse_motion_events: EventReader<MouseMotion>,
mut cursor_moved_events: EventReader<CursorMoved>,
mut mouse_wheel_events: EventReader<MouseWheel>,
mut pinch_gesture_events: EventReader<PinchGesture>,
mut rotation_gesture_events: EventReader<RotationGesture>,
mut double_tap_gesture_events: EventReader<DoubleTapGesture>,
) {
// 鼠标按钮事件
for event in mouse_button_input_events.read() {
info!("鼠标按钮事件: {:?}", event);
}
// 鼠标移动事件
for event in mouse_motion_events.read() {
info!("鼠标移动事件: {:?}", event);
}
// 光标移动事件
for event in cursor_moved_events.read() {
info!("光标移动事件: {:?}", event);
}
// 鼠标滚轮事件
for event in mouse_wheel_events.read() {
info!("鼠标滚轮事件: {:?}", event);
}
// 手势事件(仅macOS)
for event in pinch_gesture_events.read() {
info!("捏合手势事件: {:?}", event);
}
for event in rotation_gesture_events.read() {
info!("旋转手势事件: {:?}", event);
}
for event in double_tap_gesture_events.read() {
info!("双击手势事件: {:?}", event);
}
}关键要点:
- 多种事件类型提供不同粒度的鼠标信息
- 手势事件仅在macOS上可用
- 适合需要精确鼠标控制的场景
鼠标光标控制
示例文件: mouse_grab.rs
控制鼠标光标的显示和锁定:
use bevy::{prelude::*, window::CursorGrabMode};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, grab_mouse)
.run();
}
/// 控制鼠标光标抓取的系统
fn grab_mouse(
mut window: Single<&mut Window>,
mouse: Res<ButtonInput<MouseButton>>,
key: Res<ButtonInput<KeyCode>>,
) {
// 左键点击时隐藏光标并锁定鼠标
if mouse.just_pressed(MouseButton::Left) {
window.cursor_options.visible = false;
window.cursor_options.grab_mode = CursorGrabMode::Locked;
info!("鼠标已锁定");
}
// ESC键释放鼠标
if key.just_pressed(KeyCode::Escape) {
window.cursor_options.visible = true;
window.cursor_options.grab_mode = CursorGrabMode::None;
info!("鼠标已释放");
}
// 其他光标模式示例
if key.just_pressed(KeyCode::Key1) {
// 限制光标在窗口内
window.cursor_options.grab_mode = CursorGrabMode::Confined;
}
if key.just_pressed(KeyCode::Key2) {
// 隐藏光标但不锁定
window.cursor_options.visible = false;
window.cursor_options.grab_mode = CursorGrabMode::None;
}
}关键要点:
CursorGrabMode::Locked锁定鼠标到窗口中心CursorGrabMode::Confined限制鼠标在窗口内CursorGrabMode::None正常模式- 适合FPS游戏等需要鼠标控制的场景
触摸输入
基础触摸输入
示例文件: touch_input.rs
处理触摸屏输入:
use bevy::{input::touch::*, prelude::*};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, touch_system)
.run();
}
fn touch_system(touches: Res<Touches>) {
// 处理刚刚按下的触摸
for touch in touches.iter_just_pressed() {
info!(
"触摸按下 - ID: {}, 位置: {}",
touch.id(),
touch.position()
);
}
// 处理刚刚释放的触摸
for touch in touches.iter_just_released() {
info!(
"触摸释放 - ID: {}, 位置: {}",
touch.id(),
touch.position()
);
}
// 处理被取消的触摸
for touch in touches.iter_just_canceled() {
info!("触摸取消 - ID: {}", touch.id());
}
// 处理所有当前活动的触摸
for touch in touches.iter() {
info!("活动触摸: {touch:?}");
info!(" 是否刚刚按下: {}", touches.just_pressed(touch.id()));
info!(" 是否正在按下: {}", touches.pressed(touch.id()));
info!(" 位置: {}", touch.position());
info!(" 压力: {}", touch.force().unwrap_or(0.0));
}
// 获取触摸数量
info!("当前触摸数量: {}", touches.count());
}关键要点:
Touches资源提供触摸状态- 每个触摸有唯一ID
- 支持多点触摸
- 包含位置和压力信息
触摸事件监听
示例文件: touch_input_events.rs
监听详细的触摸事件:
use bevy::{input::touch::*, prelude::*};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, touch_event_system)
.run();
}
fn touch_event_system(mut touch_events: EventReader<TouchInput>) {
for event in touch_events.read() {
info!("触摸事件: {:?}", event);
match event.phase {
TouchPhase::Started => {
info!("触摸开始 - ID: {}, 位置: {}", event.id, event.position);
}
TouchPhase::Moved => {
info!("触摸移动 - ID: {}, 位置: {}", event.id, event.position);
}
TouchPhase::Ended => {
info!("触摸结束 - ID: {}, 位置: {}", event.id, event.position);
}
TouchPhase::Canceled => {
info!("触摸取消 - ID: {}", event.id);
}
}
}
}关键要点:
TouchInput事件提供详细的触摸信息TouchPhase表示触摸的不同阶段- 适合需要精确触摸控制的场景
游戏手柄输入
基础游戏手柄输入
示例文件: gamepad_input.rs
处理游戏手柄输入:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, gamepad_system)
.run();
}
fn gamepad_system(gamepads: Query<(Entity, &Gamepad)>) {
for (entity, gamepad) in &gamepads {
// 处理按钮输入
if gamepad.just_pressed(GamepadButton::South) {
info!("手柄 {} 刚刚按下 South 按钮", entity);
} else if gamepad.just_released(GamepadButton::South) {
info!("手柄 {} 刚刚释放 South 按钮", entity);
}
// 处理扳机键(模拟输入)
let right_trigger = gamepad.get(GamepadButton::RightTrigger2).unwrap();
if right_trigger.abs() > 0.01 {
info!("手柄 {} 右扳机值: {}", entity, right_trigger);
}
// 处理摇杆输入
let left_stick_x = gamepad.get(GamepadAxis::LeftStickX).unwrap();
let left_stick_y = gamepad.get(GamepadAxis::LeftStickY).unwrap();
if left_stick_x.abs() > 0.01 || left_stick_y.abs() > 0.01 {
info!("手柄 {} 左摇杆: ({}, {})", entity, left_stick_x, left_stick_y);
}
let right_stick_x = gamepad.get(GamepadAxis::RightStickX).unwrap();
let right_stick_y = gamepad.get(GamepadAxis::RightStickY).unwrap();
if right_stick_x.abs() > 0.01 || right_stick_y.abs() > 0.01 {
info!("手柄 {} 右摇杆: ({}, {})", entity, right_stick_x, right_stick_y);
}
// 处理其他按钮
if gamepad.just_pressed(GamepadButton::North) {
info!("手柄 {} 按下 North 按钮", entity);
}
if gamepad.just_pressed(GamepadButton::East) {
info!("手柄 {} 按下 East 按钮", entity);
}
if gamepad.just_pressed(GamepadButton::West) {
info!("手柄 {} 按下 West 按钮", entity);
}
}
}关键要点:
Gamepad组件提供手柄输入状态- 支持多个手柄同时连接
- 按钮有数字和模拟两种输入
- 摇杆提供X、Y轴模拟输入
游戏手柄事件监听
示例文件: gamepad_input_events.rs
监听游戏手柄连接和输入事件:
use bevy::{
input::gamepad::{
GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadButtonStateChangedEvent,
GamepadConnectionEvent, GamepadEvent,
},
prelude::*,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, (gamepad_events, gamepad_ordered_events))
.run();
}
fn gamepad_events(
mut connection_events: EventReader<GamepadConnectionEvent>,
mut axis_changed_events: EventReader<GamepadAxisChangedEvent>,
mut button_changed_events: EventReader<GamepadButtonChangedEvent>,
mut button_input_events: EventReader<GamepadButtonStateChangedEvent>,
) {
// 处理连接事件
for connection_event in connection_events.read() {
info!("手柄连接事件: {:?}", connection_event);
}
// 处理轴变化事件
for axis_changed_event in axis_changed_events.read() {
info!(
"手柄轴变化 - 轴: {:?}, 手柄: {}, 值: {}",
axis_changed_event.axis, axis_changed_event.entity, axis_changed_event.value
);
}
// 处理按钮变化事件
for button_changed_event in button_changed_events.read() {
info!(
"手柄按钮变化 - 按钮: {:?}, 手柄: {}, 值: {}",
button_changed_event.button, button_changed_event.entity, button_changed_event.value
);
}
// 处理按钮状态变化事件
for button_input_event in button_input_events.read() {
info!("手柄按钮状态变化: {:?}", button_input_event);
}
}
// 处理有序的游戏手柄事件
fn gamepad_ordered_events(mut gamepad_events: EventReader<GamepadEvent>) {
for gamepad_event in gamepad_events.read() {
match gamepad_event {
GamepadEvent::Connection(connection_event) => {
info!("手柄连接事件: {:?}", connection_event);
}
GamepadEvent::Button(button_event) => {
info!("手柄按钮事件: {:?}", button_event);
}
GamepadEvent::Axis(axis_event) => {
info!("手柄轴事件: {:?}", axis_event);
}
}
}
}关键要点:
- 多种事件类型提供不同粒度的手柄信息
- 连接事件处理手柄插拔
- 轴和按钮事件提供精确的输入变化
- 有序事件确保事件处理的正确顺序
文本输入
基础文本输入
示例文件: text_input.rs
处理文本输入和IME(输入法编辑器):
use std::mem;
use bevy::{
input::keyboard::{Key, KeyboardInput},
prelude::*,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup_scene)
.add_systems(
Update,
(
toggle_ime,
listen_ime_events,
listen_keyboard_input_events,
bubbling_text,
),
)
.run();
}
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
// 使用支持更多字符的字体
let font = asset_server.load("fonts/FiraMono-Medium.ttf");
// 创建UI文本显示
commands.spawn((
Text::default(),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
},
children![
TextSpan::new("点击切换IME。按回车开始新行。\\n\\n"),
TextSpan::new("IME启用: "),
TextSpan::new("false\\n"),
TextSpan::new("IME激活: "),
TextSpan::new("false\\n"),
TextSpan::new("IME缓冲区: "),
(
TextSpan::new("\\n"),
TextFont {
font: font.clone(),
..default()
},
),
],
));
// 创建2D文本用于输入
commands.spawn((
Text2d::new(""),
TextFont {
font,
font_size: 100.0,
..default()
},
));
}
// 切换IME状态
fn toggle_ime(
input: Res<ButtonInput<MouseButton>>,
mut window: Single<&mut Window>,
status_text: Single<Entity, (With<Node>, With<Text>)>,
mut ui_writer: TextUiWriter,
) {
if input.just_pressed(MouseButton::Left) {
window.ime_position = window.cursor_position().unwrap();
window.ime_enabled = !window.ime_enabled;
*ui_writer.text(*status_text, 3) = format!("{}\\n", window.ime_enabled);
}
}
// 监听IME事件
fn listen_ime_events(
mut events: EventReader<Ime>,
status_text: Single<Entity, (With<Node>, With<Text>)>,
mut edit_text: Single<&mut Text2d, (Without<Node>, Without<Bubble>)>,
mut ui_writer: TextUiWriter,
) {
for event in events.read() {
match event {
Ime::Preedit { value, cursor } => {
*ui_writer.text(*status_text, 5) = format!("{}\\n", value);
*ui_writer.text(*status_text, 4) = "true\\n".to_string();
}
Ime::Commit { value } => {
edit_text.0 = format!("{}{}", edit_text.0, value);
*ui_writer.text(*status_text, 5) = "\\n".to_string();
*ui_writer.text(*status_text, 4) = "false\\n".to_string();
}
Ime::Enabled { .. } => {
*ui_writer.text(*status_text, 4) = "true\\n".to_string();
}
Ime::Disabled { .. } => {
*ui_writer.text(*status_text, 4) = "false\\n".to_string();
}
}
}
}
// 监听键盘输入事件
fn listen_keyboard_input_events(
mut commands: Commands,
mut events: EventReader<KeyboardInput>,
edit_text: Single<(&mut Text2d, &TextFont), (Without<Node>, Without<Bubble>)>,
) {
for event in events.read() {
if !event.state.is_pressed() {
continue;
}
match &event.logical_key {
Key::Character(character) => {
if is_printable_char(*character) {
edit_text.0 .0 = format!("{}{}", edit_text.0 .0, character);
}
}
Key::Enter => {
edit_text.0 .0 = format!("{}\\n", edit_text.0 .0);
}
Key::Backspace => {
edit_text.0 .0.pop();
}
_ => {}
}
}
}
// 检查是否为可打印字符
fn is_printable_char(chr: char) -> bool {
!chr.is_control() || chr == '\\n' || chr == '\\t'
}关键要点:
- IME支持多语言输入
- 处理预编辑和提交事件
- 支持特殊键如回车、退格
- 需要合适的字体支持多语言字符
总结
Bevy的输入系统提供了全面的输入处理能力:
- 键盘输入: 基础按键、组合键、字符输入
- 鼠标输入: 按钮、移动、滚轮、光标控制
- 触摸输入: 多点触摸、手势识别
- 游戏手柄: 按钮、摇杆、扳机、震动
- 文本输入: IME支持、多语言输入
这些输入系统可以组合使用,创建丰富的交互体验。建议根据项目需求选择合适的输入方式。