Перейти к содержимому

Уроки · 24

Погоня за мышью

Теперь у нас есть всё нужное: «Расстояние» считает, далеко ли курсор, «Направление» превращает это в чистое “куда”, а «Точка плюс вектор» — в способ сдвинуть точку в эту сторону. Осталось повторять это на каждом кадре — и кружок сам побежит за мышью.

Вместо того чтобы телепортироваться прямо на курсор, кружок каждый кадр делает один шаг фиксированной длины в его сторону. direction_x и direction_y сами по себе — направление длиной ровно 1, тот самый единичный вектор из урока «Направление», а длина 1 — это один пиксель за кадр, еле заметное движение. Чтобы шаг получился побольше, направление нужно растянуть — умножить на нужную длину шага, SPEED, тем же приёмом, что и в конце того же урока:

// Направление длиной 1, растянутое до длины SPEED, — и есть шаг.
x += direction_x * SPEED;
y += direction_y * SPEED;

Как и в уроке «Движение», позиция (x, y) объявлена переменной вне loop — она должна помнить, где кружок был на прошлом кадре, а не начинать каждый кадр заново (см. «Где живёт переменная»). А SPEED — наоборот: она одна и та же на каждом кадре, поэтому это const вне main, как в уроке «Константы».

Кружок бежит за курсором с фиксированной скоростью. Двигай мышь по демке — если водить её резко, кружок может проскочить мимо и затрястись рядом, пока не подстроится: это ожидаемо, скорость-то не замедляется у цели. Поменяй скорость полем ниже:

src/main.rs
use macroquad::prelude::*;
// Скорость одна и та же на каждом кадре, поэтому это константа
// (как в уроке «Константы»), а не переменная внутри loop.
const SPEED: f32 = 8.0;
#[macroquad::main("Погоня за мышью")]
async fn main() {
// Кружок стартует в центре экрана. mut и вне loop — позиция
// должна помнить прошлый кадр, а не сбрасываться каждый раз.
let mut x = screen_width() / 2.0;
let mut y = screen_height() / 2.0;
loop {
clear_background(BLACK);
// Координаты курсора.
let (mouse_x, mouse_y) = mouse_position();
// Вектор от кружка к курсору.
let dx = mouse_x - x;
let dy = mouse_y - y;
let distance = (dx * dx + dy * dy).sqrt();
// Курсор не точно на кружке — есть куда шагать.
if distance > 0.0 {
// Направление на курсор, длиной ровно 1.
let direction_x = dx / distance;
let direction_y = dy / distance;
// Точка + вектор: шаг фиксированной длины в эту сторону.
x += direction_x * SPEED;
y += direction_y * SPEED;
}
draw_circle(x, y, 30.0, YELLOW);
next_frame().await;
}
}
  • Каждый кадр кружок узнаёт направление на курсор — так же, как в уроке «Направление», — а значит, оно уже длиной 1. Умножение на SPEED растягивает этот единичный шаг до нужного размера: без умножения кружок полз бы по одному пикселю за кадр.
  • SPEED задаётся полем ниже. Она не меняется от кадра к кадру — поэтому это const, а не обычная переменная внутри loop.
  • Скорость фиксированная и не замедляется у цели: если курсор рядом, а шаг больше, чем оставшееся расстояние, кружок проскочит мимо. На следующем кадре направление развернётся в обратную сторону — кружок как бы трясётся вокруг курсора. Это ожидаемо: код нарочно простой, без плавного торможения.
  • Резко дёрни мышь — кружок не успеет среагировать мгновенно, ему нужно несколько кадров, чтобы догнать курсор, каждый раз пересчитывая направление заново.