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

Уроки · 51

Плавная погоня

В уроке «Погоня за мышью» кружок дёргается рядом с курсором: шаг там фиксированной длины, не зависит от расстояния, и на близкой дистанции кружок регулярно проскакивает мимо. Раз урок «Линейная интерполяция» уже пройден, дёргания можно убрать — причём без отдельного кода на торможение.

Идея: каждый кадр кружок сдвигается не на фиксированный шаг, а через lerp — на factor от текущего расстояния до курсора:

position = lerp(position, mouse, factor); // шаг — доля factor от текущего расстояния

Раз шаг всегда доля от расстояния, а не фиксированное число пикселей, он сам собой получается большим вдалеке и маленьким вблизи — торможение не пишется отдельно, оно берётся из самой формулы.

Води мышью по демке — кружок плавно подъезжает следом, замедляясь у цели. Дёрни мышь резко в сторону — кружок тут же снова ускорится. Поменяй factor ниже:

src/main.rs
use macroquad::prelude::*;
// Точка — как в уроке «Точка и прямоугольник».
struct Point {
x: f32,
y: f32,
}
// Линейная интерполяция (lerp) — как в уроке «Линейная интерполяция».
fn lerp(from: Point, to: Point, t: f32) -> Point {
Point {
x: from.x + (to.x - from.x) * t,
y: from.y + (to.y - from.y) * t,
}
}
// factor одна и та же на каждом кадре, поэтому это константа (как в
// уроке «Константы»), а не переменная внутри loop.
const FACTOR: f32 = 0.08;
#[macroquad::main("Плавная погоня")]
async fn main() {
// Кружок стартует в центре экрана. mut и вне loop — позиция должна
// помнить прошлый кадр, а не сбрасываться каждый раз.
let mut position = Point { x: screen_width() / 2.0, y: screen_height() / 2.0 };
loop {
clear_background(BLACK);
// Курсор — цель, к которой каждый кадр чуть-чуть приближаемся заново.
let (mouse_x, mouse_y) = mouse_position();
let mouse = Point { x: mouse_x, y: mouse_y };
// Каждый кадр сдвигаемся на factor от текущего расстояния до
// курсора: далеко от цели шаг большой, у цели — маленький.
position = lerp(position, mouse, FACTOR);
draw_circle(position.x, position.y, 30.0, YELLOW);
next_frame().await;
}
}
  • struct Point { x, y } и lerp(from, to, t) — те же самые, что и в уроке «Линейная интерполяция».
  • mouse считается заново каждый кадр — курсор снова цель, к которой едем, даже если он только что переместился.
  • position = lerp(position, mouse, factor) — кружок проезжает долю factor оставшегося расстояния. Например, при factor = 0.08 на каждом кадре разрыв сокращается на 8%, то есть остаётся 92% от того, что было — расстояние меньше, значит и следующий шаг меньше.
  • До курсора кружок никогда не доедет ровно — расстояние с каждым кадром лишь уменьшается, не обнуляется. Для демонстрации это не беда: разница становится незаметной за пару кадров.
  • Резко отодвинь мышь — расстояние снова большое, значит и шаг снова большой: кружок тут же ускоряется в сторону новой цели, без всякого отдельного кода на ускорение.
  • factor в коде — const FACTOR, а не переменная внутри loop: значение одно и то же на каждом кадре, пока ты сам не подвинешь ползунок, поэтому это константа, как в уроке «Константы».