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

Управление ресурсами — ключевой навык в разработке игр. В Phaser вы можете динамически подгружать и выгружать текстуры, что критически важно для игр с большим количеством ассетов или пошаговой загрузкой уровней. В этой статье мы разберем практический пример, который показывает, как загрузить текстуру атласа, удалить её из кэша и загрузить новую версию, переключаясь между сценами. Это поможет вам оптимизировать потребление памяти и организовать поток ресурсов в вашем проекте.

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

Живой запуск

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

Исходный код


class SceneA extends Phaser.Scene
{
    constructor ()
    {
        super('SceneA');
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.atlas('megaset', 'assets/atlas/megaset-0.png', 'assets/atlas/megaset-0.json');
    }

    create ()
    {
        const atlasTexture = this.textures.get('megaset');

        const frames = atlasTexture.getFrameNames();

        for (let i = 0; i < frames.length; i++)
        {
            let x = Phaser.Math.Between(0, 800);
            let y = Phaser.Math.Between(0, 600);

            this.add.image(x, y, 'megaset', frames[i]);
        }

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

            this.textures.remove('megaset');

            this.scene.start('SceneB');

        });
    }
}

class SceneB extends Phaser.Scene
{
    constructor ()
    {
        super('SceneB');
    }

    preload ()
    {
        // this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.atlas('megaset', 'assets/atlas/megaset-1.png', 'assets/atlas/megaset-1.json');
    }

    create ()
    {
        const atlasTexture = this.textures.get('megaset');

        const frames = atlasTexture.getFrameNames();

        for (let i = 0; i < frames.length; i++)
        {
            let x = Phaser.Math.Between(0, 800);
            let y = Phaser.Math.Between(0, 600);

            this.add.image(x, y, 'megaset', frames[i]);
        }

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

            this.textures.remove('megaset');

            this.scene.start('SceneA');

        });
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    scene: [ SceneA, SceneB ]
};

const game = new Phaser.Game(config);

Загрузка атласа в первой сцене

Класс SceneA начинает работу с загрузки текстурного атласа в методе preload(). Мы используем метод this.load.atlas(), передавая ему ключ и пути к изображению и JSON-файлу с данными.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.atlas('megaset', 'assets/atlas/megaset-0.png', 'assets/atlas/megaset-0.json');
}

После загрузки в методе create() мы получаем доступ к текстуре по ключу 'megaset' через менеджер текстур this.textures. Метод getFrameNames() возвращает массив имён всех кадров (спрайтов) внутри этого атласа. Затем в цикле мы создаём множество изображений, используя эти кадры, и размещаем их в случайных позициях на экране.

const atlasTexture = this.textures.get('megaset');
const frames = atlasTexture.getFrameNames();
for (let i = 0; i < frames.length; i++)
{
    let x = Phaser.Math.Between(0, 800);
    let y = Phaser.Math.Between(0, 600);
    this.add.image(x, y, 'megaset', frames[i]);
}

Удаление текстуры и переход

Ключевой момент примера — освобождение ресурсов перед переходом на другую сцену. По клику мыши срабатывает обработчик события pointerdown. Внутри него мы явно удаляем текстуру атласа из менеджера текстур с помощью метода this.textures.remove(), передавая ключ 'megaset'.

this.input.once('pointerdown', pointer => {
    this.textures.remove('megaset');
    this.scene.start('SceneB');
});

Метод remove() не только убирает текстуру из кэша (TextureManager), но и автоматически уничтожает все связанные с ней объекты Image или Sprite, которые были созданы с её использованием. Это предотвращает утечки памяти. После очистки мы запускаем SceneB с помощью this.scene.start().

Загрузка нового атласа во второй сцене

SceneB структурно зеркалит первую сцену, но загружает другой набор данных атласа (файлы megaset-1.png и megaset-1.json). Обратите внимание, что setBaseURL здесь закомментирован, так как базовый URL уже был установлен в SceneA и сохраняется для всей игры.

preload ()
{
    // this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.atlas('megaset', 'assets/atlas/megaset-1.png', 'assets/atlas/megaset-1.json');
}

Несмотря на то что ключ 'megaset' совпадает с использованным ранее, предыдущая текстура была удалена. Поэтому Phaser загружает новый атлас и снова привязывает его к этому ключу. В create() происходит тот же процесс создания изображений из нового набора кадров. В конце, по клику, текстура снова удаляется, и игра возвращается к SceneA, создавая бесконечный цикл переключения и демонстрации работы с ресурсами.

Почему это важно? Практическое применение

1. **Управление памятью**: В больших играх нельзя держать все ресурсы загруженными одновременно. Удаление ненужных текстур (например, для пройденного уровня) освобождает оперативную память и видеопамять. 2. **Динамическая загрузка контента**: Вы можете подгружать разные графические наборы для разных персонажей, локаций или языковых пакетов по мере необходимости, используя один и тот же логический ключ. 3. **Перезапись ресурсов**: Метод remove() позволяет "перезаписать" ресурс по существующему ключу, что полезно для систем загрузки улучшенных текстур (HD) или модов.

Важно помнить, что this.textures.remove() — это деструктивная операция. Все спрайты, использовавшие эту текстуру, будут уничтожены. Если вам нужно просто остановить отображение, но сохранить текстуру в памяти, следует управлять самими игровыми объектами, а не их ресурсами.

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

Phaser предоставляет простой, но мощный API для контроля над жизненным циклом текстур. Используя связку load.atlas(), textures.get(), textures.remove() и scene.start(), вы можете создавать сложные сцены с динамической подгрузкой контента без утечек памяти. Для экспериментов попробуйте: загружать атласы по сети по требованию, реализовать прогрессивную загрузку уровней или создать систему смены графических тем (пиксель-арт / HD) в реальном времени с перезагрузкой текстур.