О чем этот пример
Загрузка ресурсов — ключевой этап в инициализации игры. Часто бывает нужно загрузить минимальный набор для старта, а остальные ресурсы подгружать позже. В Phaser есть мощный механизм событий загрузчика, который позволяет динамически добавлять новые файлы в очередь прямо во время процесса загрузки. Это особенно полезно для больших игр, где вы хотите быстро показать начальный экран, пока на фоне грузятся остальные ассеты, или для реализации прогрессивной загрузки, когда следующий пакет ресурсов зависит от завершения предыдущего.
Версия 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.setPath('assets/sprites/');
this.load.image('atari130xe');
this.load.once('filecomplete', this.addFiles, this);
}
create ()
{
const keys = this.textures.getTextureKeys();
for (let i = 0; i < keys.length; i++)
{
const x = Phaser.Math.Between(0, 800);
const y = Phaser.Math.Between(0, 600);
this.add.image(x, y, keys[i]);
}
}
addFiles ()
{
this.load.image('atari400');
this.load.image('atari800');
this.load.image('atari800xl');
this.load.image('128x128');
this.load.image('128x128-v2');
this.load.image('a');
this.load.image('advanced_wars_land');
this.load.image('advanced_wars_tank');
this.load.image('amiga-cursor');
this.load.image('aqua_ball');
this.load.image('arrow');
this.load.image('arrows');
this.load.image('asteroids_ship');
this.load.image('asteroids_ship_white');
this.load.image('asuna_by_vali233');
this.load.image('atari1200xl');
this.load.image('b');
this.load.image('baddie_cat_1');
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('bluemetal_32x32x4');
this.load.image('bobs-by-cleathley');
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('car');
this.load.image('carrot');
this.load.image('centroid');
this.load.image('chain');
this.load.image('checkerboard');
this.load.image('chick');
this.load.image('chunk');
this.load.image('clown');
this.load.image('coin');
this.load.image('cokecan');
this.load.image('columns-blue');
this.load.image('columns-orange');
this.load.image('columns-red');
this.load.image('copy-that-floppy');
this.load.image('crate');
this.load.image('crate32');
this.load.image('cursor-rotate');
this.load.image('darkwing_crazy');
this.load.image('default');
this.load.image('diamond');
this.load.image('dragcircle');
this.load.image('drawcursor');
this.load.image('dude');
this.load.image('eggplant');
this.load.image('elephant');
this.load.image('enemy-bullet');
this.load.image('exocet_spaceman');
this.load.image('explosion');
this.load.image('eyes');
this.load.image('firstaid');
this.load.image('fish-136x80');
this.load.image('flectrum');
this.load.image('flectrum2');
this.load.image('fork');
this.load.image('fruitnveg32wh37');
this.load.image('fruitnveg64wh37');
this.load.image('fuji');
this.load.image('gameboy_seize_color_40x60');
this.load.image('gem');
this.load.image('gem-blue-16x16x4');
this.load.image('gem-green-16x16x4');
this.load.image('gem-red-16x16x4');
this.load.image('ghost');
this.load.image('green_ball');
this.load.image('healthbar');
this.load.image('helix');
this.load.image('hello');
this.load.image('hotdog');
this.load.image('humstar');
this.load.image('ilkke');
this.load.image('interference_ball_48x48');
this.load.image('interference_tunnel');
this.load.image('jets');
this.load.image('kirito_by_vali233');
this.load.image('lemming');
this.load.image('longarrow');
this.load.image('longarrow-down');
this.load.image('longarrow-white');
this.load.image('longarrow2');
this.load.image('loop');
this.load.image('maggot');
this.load.image('magnify-glass-inside');
this.load.image('magnify-glass-outside');
this.load.image('mask1');
this.load.image('mask2');
this.load.image('master');
this.load.image('melon');
this.load.image('metalface78x92');
this.load.image('metalslug_monster39x40');
this.load.image('metalslug_mummy37x45');
this.load.image('mine');
this.load.image('mouse_jim_sachs');
this.load.image('mushroom');
this.load.image('mushroom2');
this.load.image('onion');
this.load.image('orange-cat1');
this.load.image('orange-cat2');
this.load.image('orb-blue');
this.load.image('orb-green');
this.load.image('orb-red');
this.load.image('oz_pov_melting_disk');
this.load.image('pacman_by_oz_28x28');
this.load.image('palm-tree-left');
this.load.image('palm-tree-right');
this.load.image('pangball');
this.load.image('parsec');
this.load.image('particle1');
this.load.image('pepper');
this.load.image('phaser');
this.load.image('phaser-dude');
this.load.image('phaser-large');
this.load.image('phaser-ship');
this.load.image('phaser_tiny');
this.load.image('phaser1');
this.load.image('phaser2');
this.load.image('pineapple');
this.load.image('plane');
this.load.image('platform');
this.load.image('player');
this.load.image('purple_ball');
this.load.image('ra_dont_crack_under_pressure');
this.load.image('rain');
this.load.image('red_ball');
this.load.image('rgblaser');
this.load.image('saw');
this.load.image('shinyball');
this.load.image('ship');
this.load.image('shmup-baddie');
this.load.image('shmup-baddie-bullet');
this.load.image('shmup-baddie2');
this.load.image('shmup-baddie3');
this.load.image('shmup-boom');
this.load.image('shmup-bullet');
this.load.image('shmup-ship');
this.load.image('shmup-ship2');
this.load.image('skull');
this.load.image('slime');
this.load.image('slimeeyes');
this.load.image('snake');
this.load.image('snowflake-pixel');
this.load.image('snowflakes');
this.load.image('snowflakes_large');
this.load.image('sonic');
this.load.image('sonic_havok_sanity');
this.load.image('soundtracker');
this.load.image('space-baddie');
this.load.image('space-baddie-purple');
this.load.image('spaceman');
this.load.image('speakers');
this.load.image('spikedball');
this.load.image('spinObj_01');
this.load.image('spinObj_02');
this.load.image('spinObj_03');
this.load.image('spinObj_04');
this.load.image('spinObj_05');
this.load.image('spinObj_06');
this.load.image('spinObj_07');
this.load.image('spinObj_08');
this.load.image('splat');
this.load.image('steelbox');
this.load.image('stormlord-dragon96x64');
this.load.image('strip1');
this.load.image('strip2');
this.load.image('tetrisblock1');
this.load.image('tetrisblock2');
this.load.image('tetrisblock3');
this.load.image('thrust_ship');
this.load.image('thrust_ship2');
this.load.image('tinycar');
this.load.image('tomato');
this.load.image('treasure_trap');
this.load.image('tree-european');
this.load.image('ufo');
this.load.image('ukko-fujilogy-texture-64x64');
this.load.image('vu');
this.load.image('wabbit');
this.load.image('wasp');
this.load.image('wizball');
this.load.image('x2kship');
this.load.image('xenon2_bomb');
this.load.image('xenon2_ship');
this.load.image('yellow_ball');
this.load.image('zelda-hearts');
this.load.image('zelda-life');
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Настройка загрузчика и первое событие
В методе preload сцены мы настраиваем базовый путь для загрузки и указываем первый ресурс — спрайт 'atari130xe'. Затем мы подписываемся на событие filecomplete загрузчика. Это событие сработает один раз, когда загрузка этого первого файла успешно завершится.
Ключевой момент — использование метода .once() вместо .on(). Это гарантирует, что обработчик addFiles вызовется только один раз, предотвращая потенциальные зацикливания.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.setPath('assets/sprites/');
this.load.image('atari130xe');
this.load.once('filecomplete', this.addFiles, this);
}
Обработчик события: добавление новой очереди
Метод addFiles является обработчиком события filecomplete. Когда первый спрайт загружен, этот метод вызывается и добавляет в очередь загрузчика огромное количество новых изображений (в примере их более 150).
Важно понимать, что загрузчик Phaser в этот момент всё ещё активен. Добавление новых файлов в очередь не останавливает текущий процесс, а просто расширяет его. Загрузка продолжается уже для нового списка файлов.
addFiles ()
{
this.load.image('atari400');
this.load.image('atari800');
// ... и так далее для всех остальных спрайтов
}
В реальном проекте здесь можно было бы загружать конфигурационный JSON-файл со списком ассетов для уровня или добавлять ресурсы, необходимые для следующей игровой сцены.
Создание сцены: использование всех загруженных текстур
Метод create выполняется после того, как загрузчик завершит работу над всей очередью, включая файлы, добавленные динамически. Здесь мы получаем ключи всех загруженных текстур с помощью this.textures.getTextureKeys().
Затем для каждого ключа (имени текстуры) создаётся спрайт и размещается в случайной позиции на экране. В результате мы видим калейдоскоп из всех спрайтов, загруженных в два этапа.
create ()
{
const keys = this.textures.getTextureKeys();
for (let i = 0; i < keys.length; i++)
{
const x = Phaser.Math.Between(0, 800);
const y = Phaser.Math.Between(0, 600);
this.add.image(x, y, keys[i]);
}
}
Использование getTextureKeys() — отличный способ перебрать все доступные ресурсы, особенно когда их список формируется динамически.
Практические сценарии использования
1. **Прогрессивная загрузка:** Загрузите критически важные для стартового меню ассеты (шрифты, кнопки, фон), а после этого события начните загрузку ресурсов для первого уровня игры.
2. **Загрузка на основе выбора игрока:** Игрок выбирает персонажа в меню. Событие filecomplete для загрузки меню может запустить загрузку уникальных анимаций и звуков именно для этого персонажа.
3. **Загрузка зависимостей:** Сначала загружается JSON-файл с описанием уровня (списком необходимых тайлов и спрайтов), а затем, по событию его завершения, начинается загрузка всех указанных в нём графических файлов.
Основное преимущество — вы не блокируете отображение начального экрана, параллельно подготавливая данные для дальнейшей игры.
Важные нюансы и события загрузчика
Помимо filecomplete, загрузчик Phaser предоставляет и другие полезные события:
- progress: Срабатывает при загрузке каждого файла, можно использовать для обновления индикатора прогресса.
- complete: Срабатывает, когда загружены ВСЕ файлы в очереди.
- fileprogress: Срабатывает во время загрузки отдельных файлов (например, для отображения прогресса загрузки большого файла).
Для динамического добавления файлов чаще всего используют именно filecomplete, как в примере, или complete, если нужно добавить новую очередь после полной загрузки предыдущего пакета.
// Пример: добавить файлы после полной загрузки всего первого пакета
this.load.once('complete', this.addFiles, this);
Всегда следите за тем, чтобы не подписаться на событие внутри обработчика этого же события без условия, это может привести к бесконечному циклу добавления файлов.
Что попробовать дальше
Динамическое добавление файлов в очередь загрузки через события — мощный паттерн в Phaser для создания гибкой и отзывчивой системы загрузки ресурсов. Он позволяет разбить загрузку на этапы, улучшая пользовательский опыт.
**Идеи для экспериментов:**
1. Попробуйте загрузить сначала JSON-файл со списком спрайтов, а в обработчике filecomplete для этого JSON-файла распарсить его и динамически добавить в очередь все указанные в нём изображения.
2. Создайте цепочку из нескольких событий, где каждый этап загружает ресурсы для определённой части игры (меню, уровень 1, уровень 2).
3. Скомбинируйте этот подход с индикатором прогресса, который будет обновляться даже при добавлении новых файлов в очередь.
