input基础

Bevy Input 基础教程

本教程基于Bevy官方示例,按主题组织,提供易于理解的输入系统使用参考。

键盘输入

基础键盘输入

示例文件: keyboard_input.rs

最基本的键盘输入处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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

监听所有键盘事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

处理组合键和修饰键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

处理字符输入(支持多语言):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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

处理鼠标按钮和移动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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

监听详细的鼠标事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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

控制鼠标光标的显示和锁定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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

处理触摸屏输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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

监听详细的触摸事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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

处理游戏手柄输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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

监听游戏手柄连接和输入事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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(输入法编辑器):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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的输入系统提供了全面的输入处理能力:

  1. 键盘输入: 基础按键、组合键、字符输入
  2. 鼠标输入: 按钮、移动、滚轮、光标控制
  3. 触摸输入: 多点触摸、手势识别
  4. 游戏手柄: 按钮、摇杆、扳机、震动
  5. 文本输入: IME支持、多语言输入

这些输入系统可以组合使用,创建丰富的交互体验。建议根据项目需求选择合适的输入方式。