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

Уроки · 66

Ловушка индекса

Индекс уже встречался в уроке «Разноцветные кружки» — это просто номер элемента по порядку, начиная с 0: COLORS[0] — первый элемент, COLORS[1] — второй, и так далее. Тот же приём работает и здесь: circles[0] — первый кружок в списке.

В уроке «Кружки исчезают» клик убирал кружок из списка, и список пересобирался заново, без него. А что, если нужно не просто убирать кружки, а ещё и запомнить один конкретный — скажем, турель держит его на прицеле, и это должен быть именно он, кадр за кадром, что бы ни происходило с остальными? Цвет и радиус каждого кружка тут ни при чём, так что для этого урока хватит одного фиксированного радиуса, одного цвета и обычных пар (f32, f32), как в уроке «Список кружков» — без отдельной структуры Circle.

Самый очевидный способ запомнить один конкретный кружок — запомнить его индекс:

// Кружок, который запомнили, — просто его номер в списке, четвёртый по счёту.
const TARGET_INDEX: usize = 3;

Чтобы сразу видеть, у какого кружка какой индекс, подпишем каждый его номером — тем же draw_text, что и в уроке «Текст на экране», переведя число в строку через .to_string(), как в уроке «Строки». Перед i.to_string() стоит & — та же ссылка, что и в уроке «Список кружков»: draw_text только читает строку, чтобы показать её на экране, а не забирает её себе навсегда, поэтому передаём не саму строку, а ссылку на неё. Заодно, прямо в этом же цикле, обведём кольцом кружок, который запомнили, как только дойдём до его индекса:

// Рисуем каждый кружок, подписываем его индексом и, если это и есть
// TARGET_INDEX, тут же обводим его кольцом.
for i in 0..circles.len() {
let (x, y) = circles[i];
draw_circle(x, y, RADIUS, YELLOW);
draw_text(&i.to_string(), x, y, 128.0, BLACK);
if i == TARGET_INDEX {
draw_circle_lines(x, y, RADIUS + 10.0, 4.0, WHITE);
}
}

i пробегает только существующие индексы, 0..circles.len() — так что если кружков станет меньше, чем TARGET_INDEX, условие i == TARGET_INDEX просто ни разу не сработает за весь цикл, и кольцо не нарисуется.

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

src/main.rs
use macroquad::prelude::*;
// Радиус один и тот же у всех кружков — фиксированный, как в уроке «Константы».
const RADIUS: f32 = 84.0;
// Сколько кружков рисуем и как их расставить в ряд — те же START_X и STEP,
// что и в уроке «Цикл for».
const COUNT: usize = 10;
const START_X: f32 = 100.0;
const STEP: f32 = 228.0;
// Своя функция: расстояние между двумя точками, как в уроке «Расстояние».
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()
}
// Кружок, который запомнили, — просто его номер в списке, четвёртый по счёту.
const TARGET_INDEX: usize = 3;
#[macroquad::main("Ловушка индекса")]
async fn main() {
// Начальный набор кружков — ровный ряд, одного радиуса и цвета.
let mut circles: Vec<(f32, f32)> = Vec::new();
for i in 0..COUNT {
circles.push((START_X + STEP * (i as f32), screen_height() / 2.0));
}
loop {
clear_background(BLACK);
// Клик только что случился — убираем кружки под курсором.
if is_mouse_button_pressed(MouseButton::Left) {
let (mouse_x, mouse_y) = mouse_position();
// Новый пустой список — специально для кружков, которые стоит оставить.
let mut kept_circles: Vec<(f32, f32)> = Vec::new();
// Забираем старый список circles в собственность.
for (x, y) in circles {
let clicked_inside = distance(mouse_x, mouse_y, x, y) < RADIUS;
if !clicked_inside {
kept_circles.push((x, y));
}
}
// На этот кадр circles становится тем, что мы только что собрали.
circles = kept_circles;
}
// Рисуем каждый кружок, подписываем его индексом и, если это и есть
// TARGET_INDEX, тут же обводим его кольцом.
for i in 0..circles.len() {
let (x, y) = circles[i];
draw_circle(x, y, RADIUS, YELLOW);
draw_text(&i.to_string(), x, y, 128.0, BLACK);
if i == TARGET_INDEX {
draw_circle_lines(x, y, RADIUS + 10.0, 4.0, WHITE);
}
}
next_frame().await;
}
}

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

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

Один из кружков обведён белым кольцом — это и есть TARGET_INDEX, и число на нём совпадает с номером кольца. Кликни по другому кружку, который на экране левее него (то есть раньше попал в список), чтобы убрать его, и посмотри, что станет с кольцом:

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

Кольцо же каждый кадр рисуется не вокруг конкретного кружка, а вокруг того, кто сейчас лежит в списке под номером TARGET_INDEX. Кружки стоят на месте, а вот то, что считается «третьим», меняется — поэтому кольцо и перескакивает на другой кружок, хотя сам он никуда не двигался.

  • for i in 0..circles.len() перебирает позиции списка, а не сами кружки — поэтому на каждом шаге под рукой и кружок (circles[i]), и его индекс (i).
  • Число на кружке — то же i, переведённое в строку через .to_string(), как в уроке «Строки».
  • Проверка и кольцо встроены прямо в цикл отрисовки: i пробегает только существующие индексы, и если среди них нет TARGET_INDEX, кольцо просто не нарисуется.

Индекс — это место в списке, а не личность кружка. В уроке «Идентификатор кружка» разберёмся, как запоминать именно кружок, а не его текущую позицию.