Уроки · 58
Стрелки
draw_line рисует просто отрезок, а на экране от него часто хочется
не просто линии, а именно стрелки — с наконечником на конце, чтобы
сразу было видно, куда она указывает. Готовой функции для этого в
macroquad нет, но все кусочки для неё мы уже проходили раньше. Свою
функцию draw_arrow сделаем так, чтобы наконечник можно было
настраивать — не просто рисовать стрелку, а рисовать её с нужными нам
размерами, — поэтому помимо точек «откуда» и «куда» она примет ещё два
числа, head_length и head_width, про которые дальше и пойдёт речь.
Возьмём точку, откуда стрелка летит, и точку, куда она летит. Разница
между ними — это направление, в точности как в уроке
«Направление»: вычитаем координаты, делим на длину.
Если длина — ноль, направлять некуда, и тут снова пригождается урок
«Деление на ноль» — в этом случае просто ничего
не рисуем. Проверяем это через == — знак сравнения на равенство, а не
присваивание: = в уроке «Переменные» означало
«запомни это значение», а == просто спрашивает, равны ли два числа, и
отвечает true или false, как </> в уроке «Радиус
обнаружения».
let dx = to.x - from.x;let dy = to.y - from.y;let length = (dx * dx + dy * dy).sqrt();
if length == 0.0 { return;}
let direction_x = dx / length;let direction_y = dy / length;Дальше — сам наконечник. Он должен быть треугольником, который
смотрит туда же, куда летит стрелка. Для этого отступим от самого
кончика немного назад, против направления — получится точка back,
где-то посередине между концом стрелки и её настоящим кончиком.
Насколько именно назад — решает не сама функция, а тот, кто её
вызывает: head_length — это параметр draw_arrow, обычное число,
которое передают снаружи, как и color. Чем оно больше, тем длиннее
получится наконечник. Сам шаг назад — обычное сложение точки и
вектора, как в уроке «Точка плюс вектор», только вектор
здесь — направление, развёрнутое в обратную сторону и растянутое на
head_length:
let back_x = to.x - direction_x * head_length;let back_y = to.y - direction_y * head_length;От back нужно разойтись в стороны — перпендикулярно направлению
стрелки, по одному шагу влево и вправо. Насколько широко разойтись,
задаёт второй параметр, head_width: чем он больше, тем шире
получится наконечник у основания. А сам перпендикуляр считается ровно
тем трюком, что в уроке «Поворот на 90°»: меняем x и
y местами и одному из них даём минус.
let perp_x = -direction_y;let perp_y = direction_x;
let wing1_x = back_x + perp_x * head_width;let wing1_y = back_y + perp_y * head_width;let wing2_x = back_x - perp_x * head_width;let wing2_y = back_y - perp_y * head_width;Три точки — сам кончик стрелки to и два «крыла», wing1 и
wing2, — и есть треугольник наконечника. А тело стрелки рисуем не до
самого кончика, а только до back, иначе линия вылезет из-под
треугольника и будет торчать наружу.
Попробуй сам
Заголовок раздела «Попробуй сам»Стрелка смотрит вертикально вверх — так направления видно сразу:
«назад» — это вниз, а крылья наконечника — влево и вправо. Кнопка
ниже проигрывает само построение: шаг назад до back,
потом крыло влево, потом вправо, а потом оба крыла соединяются с
кончиком — и получается треугольник.
Стрелка летит из центра экрана прямо в курсор мыши (клик по демо
переносит на него фокус). Поменяй head_length и head_width ниже:
use macroquad::prelude::*;
// Точка — как в уроке «Точка и прямоугольник».struct Point { x: f32, y: f32,}
// Рисует стрелку из from в to — линию с треугольным наконечником.fn draw_arrow(from: Point, to: Point, head_length: f32, head_width: f32, color: Color) { // Направление и длина — как в «Направлении». let dx = to.x - from.x; let dy = to.y - from.y; let length = (dx * dx + dy * dy).sqrt();
// Нулевой вектор не на что направить — как в «Делении на ноль». if length == 0.0 { return; }
let direction_x = dx / length; let direction_y = dy / length;
// Шаг назад от кончика вдоль направления. let back_x = to.x - direction_x * head_length; let back_y = to.y - direction_y * head_length;
// Перпендикуляр к направлению — поворот на 90°, как в «Повороте на 90°». let perp_x = -direction_y; let perp_y = direction_x;
// Два «крыла» наконечника — по разные стороны от back. Каждая // координата — своя переменная, чтобы было видно, как она // считается. let wing1_x = back_x + perp_x * head_width; let wing1_y = back_y + perp_y * head_width; let wing2_x = back_x - perp_x * head_width; let wing2_y = back_y - perp_y * head_width;
// Тело стрелки — до back, а не до самого кончика, чтобы не вылезать // из-под наконечника. draw_line(from.x, from.y, back_x, back_y, 4.0, color);
// draw_triangle() в macroquad хочет точки в виде Vec2, а не просто // x и y по отдельности — vec2() здесь просто упаковывает уже // готовые координаты. draw_triangle( vec2(to.x, to.y), vec2(wing1_x, wing1_y), vec2(wing2_x, wing2_y), color, );}
#[macroquad::main("Стрелки")]async fn main() { loop { clear_background(BLACK);
// Стрелка летит из центра экрана в курсор мыши. let center = Point { x: screen_width() / 2.0, y: screen_height() / 2.0 }; let (mouse_x, mouse_y) = mouse_position(); let cursor = Point { x: mouse_x, y: mouse_y };
draw_arrow(center, cursor, 24.0, 12.0, YELLOW);
next_frame().await; }}Что здесь происходит
Заголовок раздела «Что здесь происходит»direction_x/direction_y— направление стрелки, длина ровно 1, тот же приём, что в уроке «Направление».back— точка чуть позади кончика, на расстоянииhead_lengthпротив направления. Отсюда расходятся оба крыла наконечника.perp_x/perp_y— перпендикуляр к направлению, посчитанный поворотом на 90° из урока «Поворот на 90°».wing1иwing2— та же точкаback, сдвинутая наhead_widthв две противоположные перпендикулярные стороны.- Сам кончик
toи оба крыла — это и есть три вершины треугольника наконечника. head_lengthиhead_widthуправляют размером наконечника: первый — насколько он длинный вдоль стрелки, второй — насколько широко расходятся крылья.