Уроки · 45
Кружки исчезают
До сих пор Vec только рос — кружки добавлялись по одному и
оставались в списке навсегда. А что, если кружок нужно убрать?
Список нельзя просто “дырявить”
Заголовок раздела «Список нельзя просто “дырявить”»У for уже было два знакомых вида: &circles — одолжить список
только для чтения, и &mut circles — одолжить для изменения на
месте. У обоих есть общая черта: сам список, сколько бы элементов в
нём ни было, остаётся тем же самым списком до и после цикла — их
количество через такой for не поменять.
Чтобы выбросить элемент, нужен третий вид — for circle in circles,
вовсе без &. Он не одалживает список, а забирает его в
собственность целиком: каждый элемент по очереди становится
собственностью переменной circle внутри цикла, а исходная переменная
circles после такого цикла больше не существует — воспользоваться ей
ещё раз нельзя.
Можно спросить: а почему бы просто не прочитать список через &circles
и не скопировать нужные кружки в новый, без всякого «отбирания»?
Технически можно — но это значит по-настоящему дублировать данные
каждого оставшегося кружка: создавать второй, независимый экземпляр
только затем, чтобы сразу выбросить первый. Владение решает это без
копирования: kept_circles.push(circle) ничего не дублирует, а просто
переносит те же самые данные в новое место — дешевле, и работает даже
для значений, которые копировать вообще не умеют.
План такой: забираем старый список целиком, для каждого кружка решаем, оставить его или нет, и если да — добавляем в новый, изначально пустой список. Кружки, которые не добавили никуда, просто забываются вместе со старым списком.
// Новый пустой список — специально для кружков, которые стоит оставить.let mut kept_circles: Vec<Circle> = Vec::new();
// Забираем старый список circles в собственность — второй раз им// воспользоваться уже не выйдет.for circle in circles { // Клик попал в этот кружок — не переносим его в новый список. let clicked_inside = distance(mouse_x, mouse_y, circle.x, circle.y) < circle.radius;
if !clicked_inside { kept_circles.push(circle); }}
// На следующий кадр circles станет тем, что мы только что собрали.circles = kept_circles;distance(...) < circle.radius — та же проверка «мышь внутри круга»,
что и в уроке «Круглые кнопки». Кружок, в который попал
клик, просто не переносится в kept_circles — и пропадает вместе со
старым списком, когда цикл закончится.
Замени содержимое src/main.rs на это:
use macroquad::prelude::*;
// Фиксированный список цветов, из которого выбираем случайный.const COLORS: [Color; 4] = [YELLOW, RED, GREEN, BLUE];
// Один кружок: положение, радиус и цвет вместе, под понятными именами.struct Circle { x: f32, y: f32, radius: f32, color: Color,}
// Своя функция: расстояние между двумя точками, как в уроке «Расстояние».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()}
#[macroquad::main("Кружки исчезают")]async fn main() { // Начальный набор кружков — случайные позиции, радиусы и цвета. let mut circles: Vec<Circle> = Vec::new(); // Номер шага нам не нужен, поэтому вместо имени стоит _. for _ in 0..10 { circles.push(Circle { x: rand::gen_range(40.0, screen_width() - 40.0), y: rand::gen_range(40.0, screen_height() - 40.0), radius: rand::gen_range(50.0, 100.0), color: COLORS[rand::gen_range(0, COLORS.len())], }); }
loop { clear_background(BLACK);
// Клик только что случился — убираем кружки под курсором. if is_mouse_button_pressed(MouseButton::Left) { let (mouse_x, mouse_y) = mouse_position();
// Новый пустой список — специально для кружков, которые стоит оставить. let mut kept_circles: Vec<Circle> = Vec::new();
// Забираем старый список circles в собственность. for circle in circles { let clicked_inside = distance(mouse_x, mouse_y, circle.x, circle.y) < circle.radius;
if !clicked_inside { kept_circles.push(circle); } }
// На этот кадр circles становится тем, что мы только что собрали. circles = kept_circles; }
// Рисуем всё, что осталось. for circle in &circles { draw_circle(circle.x, circle.y, circle.radius, circle.color); }
next_frame().await; }}Запусти как обычно:
cargo runНа экране — десять кружков в случайных местах. Кликни по любому — исчезнет он сам, а заодно и все остальные, которые тоже оказались под курсором. Попробуй прямо здесь:
Что здесь происходит
Заголовок раздела «Что здесь происходит»for _ in 0..10— тот жеfor, что и в уроке «Цикл for», только вместо имени счётчика стоит_. Здесь нам нужно просто повторить цикл десять раз, а само число на каждом шаге не используется. Убрать место для имени вforнельзя, оно должно быть. А если написать туда обычное имя и не пользоваться им, компилятор предупредит, что переменная не используется. Поэтому пишем_: это значит «здесь что-то стоит, но оно нам не нужно».for circle in circlesбез&забирает список в собственность: старая переменнаяcirclesпосле цикла недоступна, поэтому в конце ей присваивается новое значение —kept_circles.- Владение здесь выгоднее копирования:
kept_circles.push(circle)просто переносит уже готовые данные кружка в новый список, не создавая их дубликат только для того, чтобы сразу выбросить оригинал. - Кружок добавляется в
kept_circles, только если клик не попал внутрь него — те, что попали, никуда не переносятся и пропадают вместе со старым списком. !clicked_inside— отрицание через!, та же идея, что и в уроке «Порядок отрисовки»: переворачиваетboolна противоположный.- Если клик не попал ни в один кружок,
kept_circlesполучится точно такой же, как старыйcircles— ни один кружок не потеряется.