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

Уроки · 35

Список кружков

В уроке «Сколько угодно кружков» число кружков задавалось полем — но код всё равно рисовал их сам, по формуле с равным шагом. А что, если каждый кружок должен появиться там, где сам решит игрок — например, в точке клика?

Одну точку клика хранить легко — это пара чисел (f32, f32), тот же кортеж, что уже возвращала mouse_position() в уроке «Координаты мыши». Но точек будет несколько, причём сколько именно — заранее неизвестно: зависит от того, сколько раз кликнет игрок. Нужна коллекция, которая умеет расти сама, пока работает программа.

Vec — это список, у которого нет фиксированной длины: он начинается пустым и увеличивается по одному элементу, когда нужно. Начать пустой список точек можно так:

let mut points: Vec<(f32, f32)> = Vec::new();

Vec сам по себе не говорит, что именно он хранит — это нужно указать отдельно, в угловых скобках сразу после имени: Vec<(f32, f32)> значит «список, где каждый элемент — пара чисел». Без этого Rust не знал бы, какого типа элементы туда класть.

Vec::new() создаёт пустой список. Записывается не через точку, как .push чуть ниже, а через двойное двоеточие: new — это не что-то, что делают с уже готовым списком, а то, что создаёт его с нуля, поэтому и вызывается от самого типа Vec, а не от значения.

А добавить в него новую точку — методом .push:

points.push(mouse_position());

Замени содержимое src/main.rs на это:

src/main.rs
use macroquad::prelude::*;
#[macroquad::main("Список кружков")]
async fn main() {
// Список точек, куда кликнул игрок — сначала он пустой.
let mut points: Vec<(f32, f32)> = Vec::new();
loop {
clear_background(BLACK);
// Кнопка мыши только что нажата — запоминаем точку клика.
if is_mouse_button_pressed(MouseButton::Left) {
points.push(mouse_position());
}
// Рисуем кружок в каждой точке, которую запомнили.
for &(x, y) in &points {
draw_circle(x, y, 20.0, YELLOW);
}
next_frame().await;
}
}

Запусти как обычно:

Окно терминала
cargo run

Кликай по экрану — в каждой точке остаётся кружок, а старые никуда не пропадают. Попробуй прямо здесь:

  • Vec<(f32, f32)> — растущий список кортежей: каждый элемент — пара координат одного кружка.
  • Vec::new() создаёт пустой список — в нём пока нет ни одной точки.
  • let mut points — список обязательно мутабельный (как в уроке «Изменяемые переменные»): .push добавляет в него новый элемент, а значит, изменяет сам список. Без mut это бы не скомпилировалось.
  • points.push(mouse_position()) добавляет в конец списка новую пару координат — ровно ту, что вернула mouse_position() в момент клика.
  • is_mouse_button_pressed — то же самое условие «кнопку только что нажали», что и в уроке «Турель стреляет»: срабатывает один раз на клик, а не на каждый кадр, пока кнопка зажата.
  • for &(x, y) in &points проходит по всем точкам списка, сколько бы их ни было, и на каждой раскладывает кортеж обратно в x и y — та же идея, что и let (x, y) = mouse_position();, только теперь внутри цикла.
  • &points — ссылка на список: одалживаем его, чтобы прочитать и нарисовать каждую точку, а не навсегда. Разберём это подробно в «Кружки исчезают» и «Владение и ссылки» — а пока достаточно знать: без & программа просто не собралась бы. loop выполняет этот код заново каждый кадр, а забрать один и тот же список в собственность дважды нельзя.