О чем этот пример
Управление объектами на сцене — ключевой навык для оптимизации игр на Phaser. Когда враг повержен, бонус подобран или меню скрыто, объект должен быть корректно удалён, чтобы не тратить ресурсы на его отрисовку и обновление. В этом примере мы разберём метод `removeFromDisplayList()`. Это правильный способ убрать объект с экрана и из списка обновления, не уничтожая его данные полностью. Вы научитесь динамически управлять содержимым сцены и избежите типичных ошибок с "призрачными" спрайтами.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('beer', 'assets/sprites/beer.png');
this.load.image('watermelon', 'assets/sprites/watermelon.png');
this.load.image('cake', 'assets/sprites/cake.png');
}
create ()
{
this.add.text(10, 10, 'Click Sprite to remove it').setDepth(1);
const size = this.add.text(10, 32, 'Display List size: 34').setDepth(1);
for (let i = 0; i < 32; i++)
{
const x = Phaser.Math.Between(0, 800);
const y = Phaser.Math.Between(100, 600);
const sprite = this.add.sprite(x, y, 'beer');
sprite.setInteractive();
sprite.once('pointerdown', () => {
sprite.removeFromDisplayList();
size.setText('Display List size: ' + this.children.length);
});
}
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что такое Display List?
В Phaser 3 каждый Scene содержит так называемый Display List (список отображения). Это упорядоченный контейнер, в который автоматически попадают все игровые объекты, созданные через методы фабрики сцены, такие как this.add.sprite() или this.add.image().
Попадание в Display List означает две важные вещи:
1. Объект будет отрисован на экране каждый кадр.
2. Его методы preUpdate(), update() и postUpdate() (если они определены) будут вызываться автоматически.
Таким образом, удаление из этого списка — это способ сказать движку: "этот объект больше не нужно показывать и обновлять".
Проверить текущее количество объектов в списке сцены можно через свойство this.children.length.
Разбор примера: создание и удаление спрайтов
В примере создаётся 32 спрайта с изображением пива в случайных позициях. Каждому спрайту назначается обработчик события клика, который и удаляет его.
Код загрузки ассетов стандартен:
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('beer', 'assets/sprites/beer.png');
this.load.image('watermelon', 'assets/sprites/watermelon.png');
this.load.image('cake', 'assets/sprites/cake.png');
}
В методе create() создаются текстовые подсказки и массив спрайтов. Ключевой момент — назначение интерактивности и одноразового обработчика pointerdown:
const sprite = this.add.sprite(x, y, 'beer');
sprite.setInteractive();
sprite.once('pointerdown', () => {
sprite.removeFromDisplayList();
size.setText('Display List size: ' + this.children.length);
});
Метод once() гарантирует, что обработчик сработает лишь один раз, что логично для удаления. После вызова removeFromDisplayList() спрайт мгновенно исчезает с экрана, а обновляемый текст показывает уменьшение счётчика this.children.length.
`removeFromDisplayList()` vs `destroy()`
Важно не путать этот метод с полным уничтожением объекта.
* sprite.removeFromDisplayList(): Объект удаляется из списка отображения и обновления сцены (this.children), но остаётся в памяти. К нему всё ещё можно обращаться по переменной, изменять его свойства и, при необходимости, снова добавить на сцену через this.add.existing(sprite).
* sprite.destroy(): Объект полностью уничтожается — удаляется из Display List, все его обработчики событий очищаются, а память освобождается для сборщика мусора. После этого объект использовать нельзя.
Используйте removeFromDisplayList(), когда объект может понадобиться снова (например, пуля в пуле объектов для перестрелки). Используйте destroy(), когда объект больше не нужен (например, закрытое модальное окно).
Практическое применение и подводные камни
Этот подход идеально подходит для: * Сбора предметов. * Уничтожения врагов (с последующей переработкой объекта в пуле). * Динамического скрытия элементов интерфейса.
Однако помните о двух нюансах:
1. **Физические тела:** Если у спрайта есть физическое тело (добавленное через this.physics.add.sprite), его удаление из Display List **не останавливает** физические расчёты. Тело продолжит двигаться и сталкиваться. Для полного отключения нужно также отключить или уничтожить тело.
2. **Ссылки:** Удалённый объект продолжает занимать память. Если вы создаёте тысячи временных объектов, в долгой игре может произойти утечка памяти. В таких случаях используйте пулы объектов или периодически вызывайте destroy() для объектов, которые точно не понадобятся.
Пример проверки размера списка после удаления:
// В обработчике клика
sprite.removeFromDisplayList();
console.log('Спрайт удалён?', this.children.contains(sprite)); // Выведет: false
console.log('Размер списка:', this.children.length);
Что попробовать дальше
Метод removeFromDisplayList() — это ваш точный инструмент для управления видимостью и активностью объектов без их полного удаления. Он отлично подходит для оптимизации сцены, когда объекты нужно временно скрыть или подготовить к повторному использованию.
**Идеи для экспериментов:**
1. Создайте пул объектов (массив спрайтов). При клике удаляйте спрайт из Display List, а по нажатию клавиши R — возвращайте обратно на сцену через this.add.existing().
2. Добавьте спрайтам физические тела и убедитесь, что после removeFromDisplayList() они всё ещё сталкиваются с миром. Попробуйте отключить тело через sprite.body.setEnable(false).
3. Реализуйте счётчик очков, который увеличивается при клике (удалении) каждого спрайта, и выводите обновлённый счёт на экран.
