Обработка ввода и времени
Реагируйте на действия игрока и ход часов.
«Обработка ввода и времени» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 4 уроков всего.
Ввод как ресурс
Состояние клавиатуры хранится в ресурсе ButtonInput<KeyCode>, который предоставляется через DefaultPlugins.
Запросите его только для чтения с помощью 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() — количество секунд с прошлого кадра.
Умножение скорости на эту величину делает перемещение независимым от частоты кадров.
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);
}
}Кнопки мыши
Для кнопок мыши используется тот же API ButtonInput, но вместо KeyCode применяется MouseButton.
Благодаря такой единообразности привычки работы с 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 делают перемещение и задержки между действиями независимыми от частоты кадров.
Теперь управление готово, и в последнем уроке мы добавим столкновения и переходы между состояниями игры.
Часто задаваемые вопросы
Урок «Обработка ввода и времени» бесплатный?
Да — полный текст урока «Обработка ввода и времени» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 4 уроков всего.
Чему я научусь в уроке «Обработка ввода и времени»?
Реагируйте на действия игрока и ход часов. Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Learn Rust Coding?
Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Обработка ввода и времени»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Learn Rust Coding?
Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Мышление в стиле ECS
- Создание и перемещение сущностей
- Обработка ввода и времени
- Столкновения и состояние игры