Уроки · 28
Турель стреляет
Давай сделаем неподвижную турель, которая умеет стрелять в сторону мыши. Разложим задачу на то, что мы уже умеем:
- направление на мышь мы уже считаем в уроке «Направление»;
- сдвинуть точку по этому направлению — это «Точка плюс вектор»;
- отличить сам момент клика от того, что кнопка просто зажата, — этому
учил «Отрезок мышью» через
is_mouse_button_pressed.
Складываем это вместе. Тело турели — обычный круг в центре экрана. У неё есть глазик — ещё один кружок, смещённый от центра в сторону мыши: центр плюс направление на мышь, растянутое до нужной длины (та самая точка плюс вектор). Глазик — просто украшение: он ничего не считает и ни на что не влияет, а только показывает, куда турель смотрит, — визуально удобно видеть направление выстрела ещё до самого выстрела. Направление пересчитывается каждый кадр, поэтому глазик постоянно довёрнут на мышь, как бы та ни двигалась. А по клику из глазика вылетает пуля — в том направлении, куда турель смотрела именно в момент выстрела.
// Глазик — центр турели плюс направление, растянутое на 25 пикселей.let eye_x = center_x + direction_x * 25.0;let eye_y = center_y + direction_y * 25.0;Пуля не следит за мышью
Заголовок раздела «Пуля не следит за мышью»Это важное отличие от урока «Погоня за мышью»: там направление пересчитывалось каждый кадр, и кружок подруливал за мышью на лету. Здесь направление пули запоминается один раз, в момент клика, и дальше не трогается — как настоящая пуля, которая летит по прямой, даже если ты потом уводишь мышь в сторону.
Пуля существует всегда — просто иногда далеко
Заголовок раздела «Пуля существует всегда — просто иногда далеко»Пуля не появляется и не исчезает — она просто стоит на месте, пока не было ни одного клика. Стартовая позиция для неё выбрана далеко за пределами экрана, там, где её никто не увидит. Клик просто переставляет её туда, где сейчас глазик, и с этого момента она летит в выбранную сторону — так же, как и после любого следующего клика.
Благодаря этому не приходится отдельно помнить, стреляли уже или нет, и решать, рисовать пулю или нет, — она рисуется всегда, а видно её или нет, зависит только от того, где она сейчас находится. По той же причине не проверяем, улетела ли пуля за край экрана, когда летит слишком долго: она просто продолжает лететь себе дальше, никому не мешая, а следующий клик всё равно поставит её на новое место.
Пуля тут одна на весь экран: если кликнуть снова, пока предыдущая ещё летит, она не добавляется рядом, а просто переставляется — старая позиция теряется. Хранить сразу несколько пуль означало бы помнить список из неизвестного заранее количества объектов, а с этим мы разберёмся в одном из следующих уроков, когда научимся создавать много объектов по клику. Пока что — одна пуля, и этого достаточно, чтобы разобраться, как вообще устроен выстрел.
Попробуй сам
Заголовок раздела «Попробуй сам»Подвигай мышь — глазик поворачивается вслед за ней. Кликни — из глазика вылетит жёлтая пуля и полетит по прямой, с той скоростью, что задана полем ниже:
use macroquad::prelude::*;
// Скорость одна и та же на каждом кадре, поэтому это константа// (как в уроке «Константы»), а не переменная внутри loop.const SPEED: f32 = 10.0;
#[macroquad::main("Турель стреляет")]async fn main() { // Направление глазика по умолчанию — ещё до первого движения мыши. let mut direction_x = 1.0; let mut direction_y = 0.0;
// Пуля стоит далеко за пределами экрана, пока не было ни одного // клика, — там её просто не видно. let mut bullet_x = -1000.0; let mut bullet_y = -1000.0; let mut bullet_direction_x = 0.0; let mut bullet_direction_y = 0.0;
loop { clear_background(BLACK);
// Турель стоит в центре экрана. let center_x = screen_width() / 2.0; let center_y = screen_height() / 2.0;
// Координаты мыши. let (mouse_x, mouse_y) = mouse_position();
// Направление от турели к мыши (как в уроке «Направление»). let dx = mouse_x - center_x; let dy = mouse_y - center_y; let distance = (dx * dx + dy * dy).sqrt();
// Мышь не точно в центре — можно обновить направление. if distance > 0.0 { direction_x = dx / distance; direction_y = dy / distance; }
// Глазик — точка плюс вектор (как в уроке «Точка плюс вектор»): // центр турели плюс направление, растянутое на 25 пикселей. let eye_x = center_x + direction_x * 25.0; let eye_y = center_y + direction_y * 25.0;
// Кнопка мыши только что нажата — новый выстрел. if is_mouse_button_pressed(MouseButton::Left) { // Переносим пулю в положение глазика. bullet_x = eye_x; bullet_y = eye_y; // Устанавливаем её направление в то, что вычислили выше. bullet_direction_x = direction_x; bullet_direction_y = direction_y; }
// Пуля летит своим зафиксированным направлением, не подстраиваясь // под мышь — в отличие от урока «Погоня за мышью». bullet_x += bullet_direction_x * SPEED; bullet_y += bullet_direction_y * SPEED;
// Тело турели. draw_circle(center_x, center_y, 40.0, BLUE); // Глазик — смотрит на мышь. draw_circle(eye_x, eye_y, 10.0, WHITE); // Пуля — рисуется всегда, просто до первого клика она далеко // за экраном и её не видно. draw_circle(bullet_x, bullet_y, 8.0, YELLOW);
next_frame().await; }}Что здесь происходит
Заголовок раздела «Что здесь происходит»direction_x,direction_y— направление турели на мышь, обновляется каждый кадр, пока мышь не в центре (та же защита от деления на ноль, что и в уроке «Деление на ноль»).eye_x,eye_y— глазик, всегда пересчитывается заново: центр турели плюс направление, растянутое до 25 пикселей.is_mouse_button_pressed(MouseButton::Left)— момент клика (как в уроке «Отрезок мышью»): именно в этот кадр пуля переставляется в глазик, а направление её полёта фиксируется.bullet_direction_x,bullet_direction_y— направление пули, в отличие отdirection_x/direction_yтурели, запоминается только один раз и после этого не пересчитывается — поэтому пуля летит прямо, а не довёрнутая на мышь.bullet_x,bullet_yначинаются далеко за экраном (-1000.0) — первый клик просто переставляет их в глазик, и дальше пуля летит оттуда со скоростьюSPEED.SPEED— скорость пули, задаётся полем ниже. Она одна и та же на каждом кадре, а не появляется заново из игры, какcenter_x, — поэтому этоconst, объявленная внеmain, как в уроке «Константы», а не обычная переменная внутриloop.