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

Работа с группами (Group) — основа управления множеством игровых объектов в Phaser 3. Однако их уничтожение может привести к неочевидным ошибкам, если не понимать внутреннюю логику движка. Этот пример наглядно демонстрирует, как метод `destroy()` работает с группой и её дочерними элементами, и почему это важно для предотвращения утечек памяти и корректного завершения работы сцены.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#000000',
    parent: 'phaser-example',
    scene: {
        preload: preload,
        create: create
    }
};

var game = new Phaser.Game(config);

function preload ()
{
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.spritesheet('invader2', 'assets/tests/invaders/invader2.png', { frameWidth: 44, frameHeight: 32 });
}

function create ()
{
    var invaders = this.add.group([
        { key: 'invader2', frame: 0, repeat: 10, setXY: { x: 32, y: 148, stepX: 52 } },
        { key: 'invader2', frame: 0, repeat: 10, setXY: { x: 32, y: 148 + 48, stepX: 52 } }
    ]);

    invaders.runChildUpdate = true;

    Phaser.Actions.IncX(invaders.getChildren(), 100);
    Phaser.Actions.SetTint(invaders.getChildren(), 0x00ff00);

    this.input.once('pointerdown', () => {

        console.log('Group destroyed');

        invaders.destroy();

    });
}

Создание группы инопланетных захватчиков

В примере создаётся группа (Group) спрайтов на основе одного листа спрайтов. Конфигурация группы позволяет сразу создать и расположить несколько объектов.

var invaders = this.add.group([
    { key: 'invader2', frame: 0, repeat: 10, setXY: { x: 32, y: 148, stepX: 52 } },
    { key: 'invader2', frame: 0, repeat: 10, setXY: { x: 32, y: 148 + 48, stepX: 52 } }
]);

Каждый объект в массиве конфигурации создаёт ряд спрайтов. Параметр repeat: 10 указывает, что нужно создать 11 спрайтов (оригинал + 10 повторов). setXY автоматически расставляет их с заданным шагом по X. В итоге группа invaders содержит 22 дочерних спрайта.

Модификация группы и её детей

После создания мы настраиваем группу и применяем действия ко всем её членам.

invaders.runChildUpdate = true;

Установка runChildUpdate в true гарантирует, что метод update() будет вызываться для каждого дочернего спрайта в группе на каждом кадре, даже если у них нет собственной логики обновления.

Phaser.Actions.IncX(invaders.getChildren(), 100);
Phaser.Actions.SetTint(invaders.getChildren(), 0x00ff00);

Phaser.Actions.IncX сдвигает всех детей группы на 100 пикселей по оси X. Phaser.Actions.SetTint применяет зелёный оттенок (0x00ff00) ко всем спрайтам. Метод invaders.getChildren() возвращает массив всех дочерних объектов, с которым и работают эти действия.

Критический момент: уничтожение группы

По клику мыши группа полностью уничтожается.

this.input.once('pointerdown', () => {
    console.log('Group destroyed');
    invaders.destroy();
});

Вызов invaders.destroy() — ключевой момент. Этот метод выполняет несколько важных действий: 1. Рекурсивно вызывает destroy() для каждого дочернего объекта в группе. Это освобождает текстуры, отключает физические тела, удаляет слушатели событий и убирает объекты из диспетчера обновления сцены. 2. Очищает внутренние массивы и ссылки внутри самой группы. 3. Удаляет группу из родительского контейнера (в данном случае из сцены).

Важно понимать, что после этого вызова группа и все её дети больше не существуют в игровом мире. Любые последующие попытки обратиться к invaders или её детям приведут к ошибкам.

Почему `destroy()`, а не `clear()` или `removeAll()`?

Phaser предоставляет несколько методов для работы с содержимым группы: - clear(): Удаляет всех детей из группы, но **не уничтожает их**. Объекты остаются в памяти и на сцене, просто перестают быть частью этой группы. - removeAll(): Аналогично clear(), но с возможностью передать параметр destroyChild. Если destroyChild равен false (по умолчанию), объекты лишь удаляются из группы. - destroy(): Полностью уничтожает саму группу и, что критично, **всех её детей**.

В данном примере цель — полная очистка, поэтому используется destroy(). Использование clear() без последующего уничтожения детей привело бы к утечке памяти, так как 22 спрайта остались бы висеть на сцене без возможности получить к ним ссылку для последующего удаления.

Что попробовать дальше

Метод destroy() для группы — это правильный способ полностью очистить ресурсы, когда группа объектов больше не нужна. Для экспериментов попробуйте заменить invaders.destroy() на invaders.clear() и проверьте, останутся ли спрайты на экране. Или используйте invaders.removeAll(true), где true заставит метод уничтожить детей, но сама группа останется жива и сможет быть повторно использована для добавления новых объектов.