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

Загрузка игровых ассетов по одному — утомительный и чреватый ошибками процесс. Особенно когда речь идет о десятках изображений, атласов и звуков. Метод `load.pack()` в Phaser позволяет описать все необходимые ресурсы в одном JSON-файле и загрузить их одной командой. Это делает код чище, упрощает управление ассетами и ускоряет процесс разработки. В этой статье мы разберем, как работает загрузка пачками на конкретном примере, и покажем, как это применить в ваших проектах.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.pack('pack', 'assets/loader-tests/pack4.json');
    }

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

        const frames = atlasTexture.getFrameNames();

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

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

const config = {
    type: Phaser.AUTO,
    width: 1024,
    height: 768,
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Что такое pack-файл и зачем он нужен

Pack-файл в Phaser — это JSON-объект, который описывает список ресурсов для загрузки. Вместо того чтобы вызывать this.load.image() или this.load.atlas() для каждого отдельного ассета в методе preload(), вы создаете структурированный список в отдельном файле. Это разделяет логику загрузки (что грузить) и её конфигурацию (откуда и как грузить).

Такой подход особенно полезен для: * **Атласов спрайтов:** когда у вас один файл изображения и один файл данных (JSON или XML). * **Звуков:** загрузка нескольких форматов (mp3, ogg) для одного звукового эффекта. * **Больших проектов:** удобно разделять ассеты по уровням или сценам в разные pack-файлы.

В исходном примере pack-файл расположен по адресу assets/loader-tests/pack4.json относительно установленного базового URL.

Настройка базового URL и загрузка пачки

Первым делом в методе preload() сцены нужно настроить базовый путь для всех последующих загрузок. Это делается с помощью метода load.setBaseURL(). После этого можно загрузить сам pack-файл.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.pack('pack', 'assets/loader-tests/pack4.json');

Метод load.pack() принимает два ключевых аргумента: 1. **Ключ (key):** 'pack' — произвольная строка-идентификатор для этой конкретной пачки. Он может пригодиться, если вы используете несколько pack-файлов и события для отслеживания их загрузки. 2. **URL:** 'assets/loader-tests/pack4.json' — путь к JSON-файлу с описанием ресурсов. Этот путь строится относительно базового URL, установленного ранее.

После вызова этого метода Phaser асинхронно загрузит указанный JSON, распарсит его и начнет загрузку всех описанных внутри ресурсов.

Использование загруженных ресурсов в create()

Как только все ресурсы из пачки загружены, Phaser автоматически переходит к методу create() сцены. В этот момент текстуры, звуки и другие ассеты уже доступны в соответствующих кэшах (this.textures, this.cache и т.д.).

В нашем примере pack-файл содержал описание для текстуры атласа с ключом 'megaset'. Чтобы получить доступ к этой текстуре, используется менеджер текстур:

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

Далее, чтобы отобразить каждый отдельный кадр (спрайт) из этого атласа, нам нужно получить список их имен. Это делает метод getFrameNames():

const frames = atlasTexture.getFrameNames();

Теперь, имея массив имен кадров, можно создать множество изображений на сцене. В примере это делается в цикле: для каждого имени кадра генерируются случайные координаты, и добавляется изображение.

for (let i = 0; i < frames.length; i++)
{
    const x = Phaser.Math.Between(0, 1024);
    const y = Phaser.Math.Between(0, 768);
    this.add.image(x, y, 'megaset', frames[i]);
}

Ключевые моменты вызова this.add.image(): * **Первый аргумент ('megaset'):** это ключ самой текстуры атласа, который мы использовали в textures.get(). * **Второй аргумент (frames[i]):** это имя конкретного кадра внутри этого атласа, которое мы хотим отобразить.

Структура pack-файла (предположительная)

Хотя в примере не показано содержимое pack4.json, мы можем предположить его структуру, основанную на официальной документации Phaser и итоговому результату (загрузка атласа megaset).

Типичный pack-файл для загрузки атласа в формате JSON Hash (стандартный для Texture Packer) мог бы выглядеть так:

{
    "megaset": {
        "type": "atlasJSONHash",
        "textureURL": "assets/megaset-0.png",
        "atlasURL": "assets/megaset-0.json"
    }
}

Или, если бы мы загружали несколько ресурсов, структура была бы массивом объектов:

{
    "files": [
        {
            "type": "atlasJSONHash",
            "key": "megaset",
            "textureURL": "assets/megaset-0.png",
            "atlasURL": "assets/megaset-0.json"
        },
        {
            "type": "image",
            "key": "background",
            "url": "assets/sky.png"
        }
    ]
}

Обратите внимание на поле "key". Именно это значение ('megaset') потом используется для доступа к ресурсу через this.textures.get().

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

Использование load.pack() — это мощный шаг к поддержанию порядка в растущем проекте. Он централизует конфигурацию ресурсов, уменьшает объем кода в сценах и упрощает добавление новых ассетов. **Идеи для экспериментов:** 1. Создайте несколько pack-файлов для разных игровых уровней и загружайте их по мере необходимости, используя ключ пачки для отслеживания завершения загрузки. 2. Попробуйте описать в одном pack-файле не только атласы, но и изображения, аудио, bitmap-шрифты и данные (JSON). 3. Исследуйте событие 'filecomplete-pack' для выполнения кастомной логики сразу после загрузки всей пачки.