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

Уроки · 49

Точка и прямоугольник

В уроке «Пули пропадают за краем» у inside_rectangle целых шесть параметров:

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
}

Уже тогда отметили: шесть — это точка (два числа) и прямоугольник (ещё четыре), просто разложенные по отдельности. Раз они всё равно всегда используются вместе, есть смысл завести под них свои типы — ту же идею мы уже применяли в уроке «Свои структуры».

struct Point {
x: f32,
y: f32,
}
struct Rect {
x: f32,
y: f32,
width: f32,
height: f32,
}

Назвал прямоугольник Rect, а не Rectangle — специально короче: это по-прежнему означает «прямоугольник», просто сокращённо, чтобы не путать с уже знакомым Rectangle из урока «Прямоугольные кнопки». У того Rectangle есть ещё поле color, для отрисовки, — а здесь нужна только сама геометрия: положение и размер, без цвета.

fn inside_rectangle(point: Point, rect: Rect) -> bool {
point.x >= rect.x
&& point.x <= rect.x + rect.width
&& point.y >= rect.y
&& point.y <= rect.y + rect.height
}

Сама проверка не изменилась ни на символ — просто теперь x, y, rect_x и остальные читаются через point. и rect.. Зато вызов стал короче и понятнее. Было — как в уроке «Настоящий край экрана»:

if inside_rectangle(bullet.x, bullet.y, 0.0, 0.0, screen_width(), screen_height()) {

Стало:

let bullet_point = Point { x: bullet.x, y: bullet.y };
let screen_rect = Rect { x: 0.0, y: 0.0, width: screen_width(), height: screen_height() };
if inside_rectangle(bullet_point, screen_rect) {

Шесть чисел подряд — уже сложно на глаз понять, что где, особенно если случайно переставить два соседних местами: компилятор такую ошибку не заметит, это же просто f32 за f32. С Point и Rect самих параметров всего два, и перепутать целую точку с целым прямоугольником уже гораздо сложнее — они просто разного типа.

distance раньше тоже принимала четыре отдельных числа — две точки, разложенные по x и y, как в уроке «Радиус мышью»:

fn distance(from_x: f32, from_y: f32, to_x: f32, to_y: f32) -> f32 {
let dx = to_x - from_x;
let dy = to_y - from_y;
(dx * dx + dy * dy).sqrt()
}

Делаем с ней то же самое, что и с inside_rectangle выше, — вместо четырёх чисел принимаем две Point:

fn distance(from: Point, to: Point) -> f32 {
let dx = to.x - from.x;
let dy = to.y - from.y;
(dx * dx + dy * dy).sqrt()
}

С direction то же самое на входе: было четыре отдельных числа, станет две Point. Но раньше она ещё и возвращала кортеж (f32, f32) (как в уроке «Свои функции») — теперь на выходе тоже Point:

fn direction(from: Point, to: Point) -> Point {
let dx = to.x - from.x;
let dy = to.y - from.y;
let distance = (dx * dx + dy * dy).sqrt();
if distance > 0.0 {
Point { x: dx / distance, y: dy / distance }
} else {
Point { x: 1.0, y: 0.0 }
}
}

Это не натяжка: в уроке «Точка плюс вектор» уже разбирали, что точка и направление — одни и те же два числа, просто с разным смыслом («место» или «движение»). Раз тип один и тот же, логично и назвать его одним и тем же именем — Point тут означает направление, а не место, но устроен ровно так же.

  • struct Point { x, y } — пара чисел с именем, ровно то, что раньше было двумя отдельными параметрами почти в каждой функции.
  • struct Rect { x, y, width, height } — то же самое для прямоугольника, но названо коротко и без поля color, чтобы не путать с Rectangle из урока «Прямоугольные кнопки».
  • inside_rectangle, distance, direction — устройство внутри не поменялось, только вход и выход: было 6, 4 и 4 параметра, стало 2, 2 и 2, а direction ещё и возвращает Point вместо кортежа.
  • Меньше параметров — меньше мест, где значения можно перепутать местами: Point и Rect — разные типы, и подсунуть одно вместо другого уже не выйдет так же незаметно, как с длинным списком голых f32.