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

Уроки · 55

Поворот и движение

В уроке «Движение» стрелки двигали кружок прямо по осям экрана: влево-вправо трогали x, вверх-вниз — y. Сделаем по-другому: пусть влево и вправо поворачивают игрока, а вверх и вниз — двигают его вперёд или назад вдоль того направления, куда он сейчас повёрнут, как машина или танк, а не свободно летающая точка.

Для этого мало помнить x и y — нужен ещё и угол поворота, он и говорит, куда сейчас “вперёд”. Все три числа удобно держать вместе, в своей структуре, как в уроке «Свои структуры», а направление по этому углу получить методом, как в уроке «Методы» — тем же самым cos/sin, что и в уроке «Вращение»:

struct Player {
x: f32,
y: f32,
angle: f32,
}
impl Player {
fn direction(&self) -> (f32, f32) {
(self.angle.cos(), self.angle.sin())
}
}

Обрати внимание на &self, а не просто self: в main мы вызываем player.direction(), а сразу следом ещё и меняем player.x, player.y. Если бы direction забирала player в собственность, player после вызова стал бы недоступен (как в уроке «Владение и ссылки») — поменять его координаты уже не вышло бы.

Дальше всё просто: влево и вправо крутят angle, а вверх и вниз двигают x и y вдоль того направления, которое этот угол сейчас задаёт — вызвали direction() один раз за кадр и используем результат дважды. И раз экран перевёрнут по y (как в уроке «Координаты»), рост angle поворачивает игрока по часовой стрелке, а не против, — та же деталь, что уже была в уроке «Вращение».

Стрелки влево/вправо поворачивают игрока, стрелки вверх/вниз двигают его вперёд и назад вдоль того направления, куда он сейчас повёрнут. Глазик показывает, куда он смотрит (клик по демо переносит на него фокус клавиатуры). Поменяй скорости полями ниже:

src/main.rs
use macroquad::prelude::*;
// Игрок — положение и угол поворота вместе, как в уроке «Свои структуры».
struct Player {
x: f32,
y: f32,
angle: f32,
}
impl Player {
// Направление, куда сейчас смотрит игрок, — единичный вектор по
// текущему углу (как в уроке «Синус и косинус»).
fn direction(&self) -> (f32, f32) {
(self.angle.cos(), self.angle.sin())
}
}
// Скорости одни и те же на каждом кадре, поэтому это константы
// (как в уроке «Константы»), а не переменные внутри loop.
const SPEED: f32 = 4.0;
const ROTATE_SPEED: f32 = 0.050;
#[macroquad::main("Поворот и движение")]
async fn main() {
// Игрок стартует в центре экрана и смотрит вправо — угол 0.
let mut player = Player {
x: screen_width() / 2.0,
y: screen_height() / 2.0,
angle: 0.0,
};
loop {
clear_background(BLACK);
// Стрелка влево — поворот против часовой стрелки.
if is_key_down(KeyCode::Left) {
player.angle -= ROTATE_SPEED;
}
// Стрелка вправо — поворот по часовой стрелке.
if is_key_down(KeyCode::Right) {
player.angle += ROTATE_SPEED;
}
// Направление, куда сейчас смотрит игрок, — пересчитывается
// заново на каждом кадре, сразу после поворота.
let (direction_x, direction_y) = player.direction();
// Стрелка вверх — шаг вперёд вдоль направления взгляда.
if is_key_down(KeyCode::Up) {
player.x += direction_x * SPEED;
player.y += direction_y * SPEED;
}
// Стрелка вниз — шаг назад вдоль того же направления.
if is_key_down(KeyCode::Down) {
player.x -= direction_x * SPEED;
player.y -= direction_y * SPEED;
}
// Тело игрока.
draw_circle(player.x, player.y, 75.0, YELLOW);
// Глазик — как у турели: точка плюс вектор, растянутый на 62.5
// пикселей в направлении взгляда.
let eye_x = player.x + direction_x * 62.5;
let eye_y = player.y + direction_y * 62.5;
draw_circle(eye_x, eye_y, 20.0, DARKBLUE);
next_frame().await;
}
}
  • struct Player { x, y, angle } — положение и поворот вместе, одной структурой, как в уроке «Свои структуры».
  • player.direction() — метод, возвращающий единичный вектор взгляда по текущему angle; внутри self.angle.cos() и self.angle.sin() — то же самое, что и в уроке «Вращение».
  • &self вместо self — не отбирает player в собственность: direction() вызывается, а player остаётся на месте и его можно менять дальше в этом же кадре.
  • SPEED и ROTATE_SPEED заданы полями speed и rotate_speed ниже. Они не меняются от кадра к кадру — поэтому это константы, а не обычные переменные внутри loop.
  • Глазик — та же точка плюс вектор, что и в уроке «Турель стреляет»: положение игрока плюс направление взгляда, растянутое на фиксированные 62.5 пикселя.