0Pricing
Learn Rust Coding · Урок

Обработка ввода и времени

Реагируйте на действия игрока и ход часов.

«Обработка ввода и времени» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Мышление в стиле ECS
  2. Создание и перемещение сущностей
  3. Обработка ввода и времени
  4. Столкновения и состояние игры
← Назад к Learn Rust Coding