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

Уроки · 48

Настоящий край экрана

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

inside_rectangle не изменилась ни на символ — как и раньше, она проверяет точку против прямоугольника из x, y, ширины и высоты. А сам экран — это просто ещё один прямоугольник, только особенный: левый верхний угол у него всегда (0, 0) (как в уроке «Координаты»), а ширина и высота — это screen_width() и screen_height().

if inside_rectangle(bullet.x, bullet.y, 0.0, 0.0, screen_width(), screen_height()) {
draw_circle(bullet.x, bullet.y, 8.0, YELLOW);
kept_bullets.push(bullet);
}

Никакого margin, никакого draw_rectangle_lines — только сама проверка. Пуля пропадает ровно тогда, когда должна: покидая настоящий край экрана. Просто теперь этого не видно — а мы точно знаем, что это происходит, потому что убедились в этом в прошлом уроке, на увеличенной, заметной рамке.

Кликай сколько угодно раз — пули летят и пропадают за краем экрана, теперь совсем без видимой рамки, ровно как в настоящей игре:

src/main.rs
use macroquad::prelude::*;
// Скорость одна и та же на каждом кадре, поэтому это константа
// (как в уроке «Константы»), а не переменная внутри loop.
const SPEED: f32 = 10.0;
// Одна пуля: положение и зафиксированное направление полёта.
struct Bullet {
x: f32,
y: f32,
direction_x: f32,
direction_y: f32,
}
// Своя функция: по двум точкам считает направление от первой ко второй,
// как в уроке «Свои функции».
fn direction(from_x: f32, from_y: f32, to_x: f32, to_y: f32) -> (f32, f32) {
let dx = to_x - from_x;
let dy = to_y - from_y;
let distance = (dx * dx + dy * dy).sqrt();
if distance > 0.0 {
(dx / distance, dy / distance)
} else {
(1.0, 0.0)
}
}
// Своя функция: точка (x, y) внутри прямоугольника (rect_x, rect_y,
// rect_width, rect_height)? Та же проверка, что и в уроке «Прямоугольные
// кнопки».
fn inside_rectangle(x: f32, y: f32, rect_x: f32, rect_y: f32, rect_width: f32, rect_height: f32) -> bool {
x >= rect_x && x <= rect_x + rect_width && y >= rect_y && y <= rect_y + rect_height
}
#[macroquad::main("Настоящий край экрана")]
async fn main() {
// Список пуль — сначала пустой.
let mut bullets: Vec<Bullet> = Vec::new();
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 (direction_x, direction_y) = direction(center_x, center_y, mouse_x, mouse_y);
// Глазик — центр турели плюс направление, растянутое на 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) {
bullets.push(Bullet {
x: eye_x,
y: eye_y,
direction_x,
direction_y,
});
}
// Тело турели.
draw_circle(center_x, center_y, 40.0, BLUE);
// Глазик — смотрит на мышь.
draw_circle(eye_x, eye_y, 10.0, WHITE);
// Новый пустой список — создаётся заново на каждом кадре, специально
// для пуль, которые стоит оставить.
let mut kept_bullets: Vec<Bullet> = Vec::new();
// Забираем старый список bullets в собственность — второй раз им
// воспользоваться уже не выйдет.
for mut bullet in bullets {
// Двигаем пулю в её направлении.
bullet.x += bullet.direction_x * SPEED;
bullet.y += bullet.direction_y * SPEED;
// Пуля ещё на экране — рисуем и переносим её в новый список.
// Сам экран и есть прямоугольник: от (0, 0) до (screen_width(),
// screen_height()).
if inside_rectangle(bullet.x, bullet.y, 0.0, 0.0, screen_width(), screen_height()) {
draw_circle(bullet.x, bullet.y, 8.0, YELLOW);
kept_bullets.push(bullet);
}
// Иначе пуля просто никуда не переносится и пропадает вместе
// со старым списком, когда цикл закончится.
}
// На следующий кадр bullets станет тем, что мы только что собрали.
bullets = kept_bullets;
next_frame().await;
}
}
  • inside_rectangle(bullet.x, bullet.y, 0.0, 0.0, screen_width(), screen_height()) — та же самая функция, что и в уроке «Пули пропадают за краем», просто вызванная с границами настоящего экрана вместо уменьшенной рамки.
  • 0.0, 0.0 — левый верхний угол экрана, он же начало координат.
  • screen_width(), screen_height() — ширина и высота экрана, вместо view_width/view_height из прошлого урока.
  • Список пуль по-прежнему пересобирается каждый кадр — for mut bullet in bullets, новый kept_bullets, bullets = kept_bullets в конце — устройство не поменялось, поменялась только сама граница.
  • Пропажу пули теперь не видно — она происходит за пределами того, что вообще можно разглядеть на экране, и в этом и есть разница с прошлым уроком.