О чем этот пример

Управление объектами на сцене — ключевой навык для оптимизации игр на 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. Реализуйте счётчик очков, который увеличивается при клике (удалении) каждого спрайта, и выводите обновлённый счёт на экран.