处理输入和时间
响应玩家操作和时钟。
处理输入和时间 是 CoddyKit 上的免费 Learn Rust Coding 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Learn Rust Coding 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Learn Rust Coding 课程共包含 4 节课。
作为资源的输入
键盘状态存储在由 DefaultPlugins 提供的 ButtonInput<KeyCode> 资源中。
请通过 Res 以只读方式请求它。Bevy 会在您的系统运行前逐帧更新该资源,因此您始终能看到当前状态。
fn read_keys(keys: Res<ButtonInput<KeyCode>>) {
if keys.pressed(KeyCode::Space) {
info!("space held");
}
}pressed 与 just_pressed
按住按键时,pressed 在每一帧都为 true,适合连续移动。just_pressed 只会在按键按下的那一帧为 true。
对于跳跃或开火等离散操作,请使用 just_pressed,这样一次点击只会触发一次。
fn jump(keys: Res<ButtonInput<KeyCode>>) {
if keys.just_pressed(KeyCode::ArrowUp) {
info!("jump!");
}
}构建方向向量
将多个按键组合成移动方向。从零向量开始,每检测到一个按下的按键,就在相应轴上加一个单位值。
对结果进行归一化,可以防止斜向移动比直线移动更快。
fn direction(keys: Res<ButtonInput<KeyCode>>) -> Vec2 {
let mut d = Vec2::ZERO;
if keys.pressed(KeyCode::KeyW) { d.y += 1.0; }
if keys.pressed(KeyCode::KeyS) { d.y -= 1.0; }
if keys.pressed(KeyCode::KeyA) { d.x -= 1.0; }
if keys.pressed(KeyCode::KeyD) { d.x += 1.0; }
d.normalize_or_zero()
}时间资源
不同设备的帧率各不相同,因此绝不能让对象每帧移动固定距离。Time 资源会提供 delta_secs(),也就是自上一帧以来经过的秒数。
将速度乘以 delta,可以让移动不受帧率影响。
fn move_player(time: Res<Time>, mut q: Query<&mut Transform, With<Player>>) {
let dt = time.delta_secs();
for mut tf in &mut q {
tf.translation.x += 200.0 * dt;
}
}组合输入与时间
真正的模式是在一个系统中结合这两种资源:从输入中读取方向,然后根据速度和时间增量进行缩放。
无论游戏以每秒 30 帧还是 144 帧运行,这样都能实现平滑且一致的控制。
fn drive(
keys: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
mut q: Query<&mut Transform, With<Player>>,
) {
let mut d = Vec2::ZERO;
if keys.pressed(KeyCode::KeyA) { d.x -= 1.0; }
if keys.pressed(KeyCode::KeyD) { d.x += 1.0; }
let step = d.normalize_or_zero() * 300.0 * time.delta_secs();
for mut tf in &mut q {
tf.translation += step.extend(0.0);
}
}鼠标按钮
鼠标按钮使用相同的 ButtonInput API,只是使用 MouseButton 而不是 KeyCode。
这种一致性意味着,您为键盘掌握的 pressed 和 just_pressed 用法可以直接应用于点击操作。
fn shoot(buttons: Res<ButtonInput<MouseButton>>) {
if buttons.just_pressed(MouseButton::Left) {
info!("fire");
}
}用于冷却时间的计时器
Timer 会记录向目标时长推进的已用时间。请在每次更新时使用帧时间增量让它前进。
请使用重复计时器控制开火节奏,并检查 just_finished(),以确定何时执行操作。
#[derive(Component)]
struct Cooldown(Timer);
fn tick(time: Res<Time>, mut q: Query<&mut Cooldown>) {
for mut cd in &mut q {
cd.0.tick(time.delta());
if cd.0.just_finished() { info!("ready"); }
}
}创建计时器
请根据一个时长和一个 TimerMode 构造计时器。Once 在触发后停止;Repeating 会自动重置。
请将它存储在组件中,这样每个实体(例如炮塔)都可以拥有独立的动作节奏。
use std::time::Duration;
commands.spawn((
Turret,
Cooldown(Timer::new(Duration::from_millis(500), TimerMode::Repeating)),
));用于确定性的 FixedUpdate
物理模拟和网络代码通常需要固定的时间步长。FixedUpdate 调度中的系统每秒运行固定次数,与渲染速率无关。
在这些系统中,请使用 Time<Fixed>,它的时间增量始终相同,从而提供可复现的模拟结果。
App::new()
.add_plugins(DefaultPlugins)
.add_systems(FixedUpdate, physics_step)
.run();已用时间与时间增量
time.delta_secs() 是自上一帧以来经过的时间。time.elapsed_secs() 是自启动以来经过的总时间。
对于由正弦波驱动的动画,已用时间非常有用。例如制作上下浮动的拾取物时,可以将总时间传入该函数。
fn bob(time: Res<Time>, mut q: Query<&mut Transform, With<Pickup>>) {
let t = time.elapsed_secs();
for mut tf in &mut q {
tf.translation.y = (t * 2.0).sin() * 10.0;
}
}限制在边界内
移动后,请将变换限制在窗口半尺寸范围内,以确保玩家不会移出屏幕。
f32::clamp 会限制每个坐标轴的范围。需要精确的边界时,请从 Window 查询中读取窗口大小。
fn clamp(mut q: Query<&mut Transform, With<Player>>) {
for mut tf in &mut q {
tf.translation.x = tf.translation.x.clamp(-300.0, 300.0);
tf.translation.y = tf.translation.y.clamp(-200.0, 200.0);
}
}快速检查
请思考如何确保逻辑不受帧率影响。
回顾:输入与时间
输入存储在 ButtonInput 资源中;对于持续按住的操作,请使用 pressed,对于单次点击,请使用 just_pressed。Time 资源和 Timer 组件可以让移动和冷却时间不受帧率影响。
控制功能就绪后,最后一课将加入碰撞和游戏状态转换。
常见问题解答
「处理输入和时间」课时是免费的吗?
是的 — 「处理输入和时间」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Learn Rust Coding 课程的其余内容,请升级到 CoddyKit PRO。 Learn Rust Coding 课程共包含 4 节课。
「处理输入和时间」这节课中我会学到什么?
响应玩家操作和时钟。 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Learn Rust Coding 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Learn Rust Coding 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「处理输入和时间」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Learn Rust Coding 课中编写并运行代码吗?
能。每节 Learn Rust Coding 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。