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

В процессе разработки игр часто возникает необходимость перезагрузить ресурсы: при перезапуске уровня, смене локации или для отладки. Но что произойдет, если попытаться загрузить файл, который уже находится в кэше загрузчика? Этот пример демонстрирует работу событий загрузчика Phaser в такой ситуации и показывает, как безопасно инициировать повторную загрузку уже имеющихся ассетов. Понимание этого механизма критически важно для создания надежных систем управления ресурсами, избегающих ошибок и утечек памяти.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    text;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.setPath('assets/sprites/');

        this.load.image('128x128');
        this.load.image('amiga-cursor');
        this.load.image('aqua_ball');
        this.load.image('arrow');
        this.load.image('asuna_by_vali233');
        this.load.image('atari130xe');
        this.load.image('atari400');
        this.load.image('atari800');
        this.load.image('atari800xl');
        this.load.image('atari1200xl');
        this.load.image('balls');
        this.load.image('beball1');
        this.load.image('bikkuriman');
        this.load.image('block');
        this.load.image('blue_ball');
        this.load.image('bluebar');
        this.load.image('bsquadron1');
        this.load.image('bsquadron2');
        this.load.image('bsquadron3');
        this.load.image('budbrain_chick');
        this.load.image('bullet');
        this.load.image('bunny');
        this.load.image('cakewalk');
        this.load.image('flectrum');
        this.load.image('fork');
    }

    create ()
    {
        const keys = this.textures.getTextureKeys();

        for (let i = 0; i < keys.length; i++)
        {
            const x = Phaser.Math.Between(100, 700);
            const y = Phaser.Math.Between(30, 300);

            this.add.image(x, y, keys[i]).setOrigin(0.5, 0);
        }

        this.text = this.add.text(10, 10, 'Click to start the loader', { font: '16px Courier', fill: '#00ff00' });

        this.input.once('pointerup', function ()
        {
      
            this.text.setText('Loading again ...');

            this.load.setPath('assets/sprites/');

            this.load.once('complete', this.subLoadCompleted, this);

            //  Load all the same files again to test we still get the complete event
            this.load.image('128x128');
            this.load.image('amiga-cursor');
            this.load.image('aqua_ball');
            this.load.image('arrow');
            this.load.image('asuna_by_vali233');
            this.load.image('atari130xe');
            this.load.image('atari400');
            this.load.image('atari800');
            this.load.image('atari800xl');
            this.load.image('atari1200xl');
            this.load.image('balls');
            this.load.image('beball1');
            this.load.image('bikkuriman');
            this.load.image('block');
            this.load.image('blue_ball');
            this.load.image('bluebar');
            this.load.image('bsquadron1');
            this.load.image('bsquadron2');
            this.load.image('bsquadron3');
            this.load.image('budbrain_chick');
            this.load.image('bullet');
            this.load.image('bunny');
            this.load.image('cakewalk');
            this.load.image('flectrum');
            this.load.image('fork');

            this.load.start();
        
        }, this);
    }

    subLoadCompleted ()
    {
        this.text.setText('Load Complete');
    }
}

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

const game = new Phaser.Game(config);

Инициализация и первичная загрузка

Сцена начинает работу с метода preload(). Здесь устанавливается базовый URL и путь для загрузки, после чего начинается загрузка большого списка изображений. Все эти файлы помещаются в очередь загрузчика.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.setPath('assets/sprites/');

this.load.image('128x128');
this.load.image('amiga-cursor');
// ... и так далее для всех спрайтов

После завершения этой загрузки все текстуры становятся доступны в менеджере текстур сцены. Метод create() вызывается автоматически, когда загрузка из preload() завершена.

Отображение загруженного и подготовка к повторной загрузке

В методе create() мы сначала получаем ключи всех уже загруженных текстур и случайным образом размещаем их спрайты на сцене. Это визуальное подтверждение успешной первичной загрузки.

const keys = this.textures.getTextureKeys();
for (let i = 0; i < keys.length; i++)
{
    const x = Phaser.Math.Between(100, 700);
    const y = Phaser.Math.Between(30, 300);
    this.add.image(x, y, keys[i]).setOrigin(0.5, 0);
}

Затем на сцене появляется текст с инструкцией. Устанавливается обработчик события клика (pointerup), который и запустит процесс повторной загрузки тех же самых файлов.

Механизм повторной загрузки и событие 'complete'

Когда игрок кликает по сцене, выполняется функция-обработчик. Это ключевой момент примера.

this.text.setText('Loading again ...');
this.load.setPath('assets/sprites/');
this.load.once('complete', this.subLoadCompleted, this);

//  Загружаем все те же файлы снова
this.load.image('128x128');
this.load.image('amiga-cursor');
// ... повторяем весь список

this.load.start();

Здесь важно: 1. Мы снова добавляем все те же самые файлы в очередь загрузчика, используя те же ключи. 2. Мы вручную запускаем загрузчик методом this.load.start(). 3. На событие 'complete' загрузчика подписывается метод subLoadCompleted.

Phaser не загружает файлы с диска повторно, если они уже находятся в его кэше (менеджере текстур). Однако загрузчик (LoaderPlugin) все равно обрабатывает свою очередь и, когда все файлы в ней пройдут проверку на существование, он испускает событие 'complete'. Это гарантирует, что логика, зависящая от этого события (например, запуск следующего уровня), сработает корректно, даже если физической загрузки не произошло.

Итоговый результат

Метод subLoadCompleted вызывается сразу после того, как загрузчик обработает свою очередь и поймет, что все запрошенные файлы уже доступны.

subLoadCompleted ()
{
    this.text.setText('Load Complete');
}

Текст на экране меняется, подтверждая, что событие 'complete' действительно было вызвано. На экране при этом не появляется новых спрайтов, так как текстуры с такими ключами уже существовали в this.textures.

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

Пример наглядно показывает, что загрузчик Phaser корректно и безопасно обрабатывает попытки повторной загрузки одних и тех же ресурсов. Это позволяет писать более гибкий и устойчивый код, например, при реализации перезапуска игровых сцен или динамической подгрузки контента. Для экспериментов попробуйте

  1. Загрузить после клика не все, а только часть исходных спрайтов и новый, не загруженный ранее файл — событие 'complete' сработает после загрузки нового
  2. Вместо 'complete' подписаться на событие 'filecomplete' для отслеживания обработки каждого конкретного файла в очереди