Уроки · 67
Идентификатор кружка
В уроке «Ловушка индекса» кольцо скакало с кружка на
кружок, хотя сами кружки стояли на месте: TARGET_INDEX — это просто
место в списке, а место при удалении соседей меняется. Нужно что-то,
что остаётся при кружке навсегда, даже когда список перестраивается —
идентификатор, или просто ID.
ID вместо индекса
Заголовок раздела «ID вместо индекса»Дадим каждому кружку свой номер один раз, при рождении, и будем хранить его вместе с координатами в том же кортеже:
let mut circles: Vec<(u32, f32, f32)> = Vec::new();Первое число — ID кружка, дальше как раньше x и y.
Для ID годится u32 — ещё один целый тип из той же семьи, что и
usize из урока «Целые числа». Разница в смысле:
usize в Rust означает именно количество или порядковый номер в
чём-то — то, чем и был наш старый TARGET_INDEX. ID кружка — не
индекс и не количество, он никак не участвует в обращении к списку по
номеру, так что usize тут ни при чём. u32 — обычный целый тип без
этой особой роли, и вмещает больше четырёх миллиардов разных значений
— кружков столько никогда не будет.
При создании ряда просто пронумеруем кружки по порядку — i-й
кружок получает ID, равный i:
for i in 0..COUNT { circles.push((i as u32, START_X + STEP * (i as f32), screen_height() / 2.0));}Выглядит почти как старый TARGET_INDEX, но есть разница: индекс был
местом в списке и менялся вместе со списком, а ID — просто число,
однажды написанное на кружке, и список на него никак не влияет:
// Кружок, которого держим на прицеле — его ID, а не номер в списке.const TARGET_ID: u32 = 3;Ищем кружок по ID
Заголовок раздела «Ищем кружок по ID»По индексу можно было сразу написать circles[TARGET_INDEX]. С ID
так не выйдет — ID не говорит, под каким номером кружок лежит в
списке сейчас. Остаётся единственный способ — сравнить id каждого
кружка с TARGET_ID. Сделаем это прямо в цикле отрисовки — впишем
туда же проверку и кольцо:
for &(id, x, y) in &circles { draw_circle(x, y, RADIUS, YELLOW); draw_text(&id.to_string(), x, y, 128.0, BLACK);
if id == TARGET_ID { draw_circle_lines(x, y, RADIUS + 10.0, 4.0, WHITE); }}Если кружка с TARGET_ID в списке больше нет, условие id == TARGET_ID просто ни разу не сработает за весь цикл, и кольцо не
нарисуется.
Замени содержимое 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()}
// Кружок, которого держим на прицеле — его ID, а не номер в списке.const TARGET_ID: u32 = 3;
#[macroquad::main("Идентификатор кружка")]async fn main() { // Начальный набор кружков — ровный ряд; у каждого свой ID, присвоенный один раз. let mut circles: Vec<(u32, f32, f32)> = Vec::new(); for i in 0..COUNT { circles.push((i as u32, 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<(u32, f32, f32)> = Vec::new();
// Забираем старый список circles в собственность. for (id, x, y) in circles { let clicked_inside = distance(mouse_x, mouse_y, x, y) < RADIUS;
if !clicked_inside { kept_circles.push((id, x, y)); } }
// На этот кадр circles становится тем, что мы только что собрали. circles = kept_circles; }
// Рисуем каждый кружок, подписываем его ID и, если это и есть // TARGET_ID, тут же обводим его кольцом. for &(id, x, y) in &circles { draw_circle(x, y, RADIUS, YELLOW); draw_text(&id.to_string(), x, y, 128.0, BLACK);
if id == TARGET_ID { draw_circle_lines(x, y, RADIUS + 10.0, 4.0, WHITE); } }
next_frame().await; }}Запусти как обычно:
cargo runОдин из кружков обведён белым кольцом — его ID совпадает с числом внутри кольца. Кликни по любому другому кружку левее него, чтобы убрать его, и посмотри, что станет с кольцом на этот раз:
Кружки по-прежнему не двигаются при удалении — но теперь не меняются и их подписи: у каждого кружка как был свой ID с рождения, так и остался. Удаление просто выбрасывает один кортеж из списка — соседние ID никуда не сдвигаются, потому что они никак не связаны с положением в списке.
Кольцо ищет кружок с TARGET_ID заново каждый кадр — и раз ID не
меняются, оно каждый раз находит тот же самый кружок, где бы он
теперь ни лежал в списке.
Что здесь происходит
Заголовок раздела «Что здесь происходит»- У каждого кружка в кортеже теперь три числа вместо двух:
id,xиy. - ID присваивается один раз, при создании ряда, и дальше никогда не меняется — в отличие от индекса, который всего лишь текущее место в списке.
- Чтобы найти кружок по ID, приходится сравнивать
idкаждого кружка сTARGET_ID— обратиться напрямую, какcircles[TARGET_INDEX]в прошлом уроке, здесь не получится. Проверка и кольцо встроены прямо в цикл отрисовки. - Если убрать кружок с
TARGET_ID, условиеid == TARGET_IDни разу не сработает, и кольцо просто не нарисуется.
Индекс — это место в списке, а ID — то, что остаётся при кружке, где бы он в этом списке ни оказался.