О чем этот пример
При разработке игр часто возникает необходимость загружать множество ассетов. Однако попытка загрузить тысячи файлов одновременно может привести к зависанию браузера или сбою. В этой статье мы разберем пример из официального репозитория Phaser, который демонстрирует проблему массовой загрузки и показывает, как организовать выбор количества файлов для тестирования. Вы узнаете, как безопасно подходить к загрузке большого объема ресурсов и какие методы использовать для контроля этого процесса.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class SetQuantity extends Phaser.Scene
{
constructor()
{
super();
}
create ()
{
this.add.text(10, 10, `Phaser v${Phaser.VERSION}\n\nSelect quantity\nof files to load`, { font: '16px Courier', fill: '#00ff00' });
const button1 = this.add.text(10, 100, '2500', { fontFamily: 'Arial', fontSize: '24px', color: '#ffffff', align: 'center', fixedWidth: 260, backgroundColor: '#0000cc' });
const button2 = this.add.text(10, 200, '5000', { fontFamily: 'Arial', fontSize: '24px', color: '#ffffff', align: 'center', fixedWidth: 260, backgroundColor: '#0000cc' });
const button3 = this.add.text(10, 300, '7500', { fontFamily: 'Arial', fontSize: '24px', color: '#ffffff', align: 'center', fixedWidth: 260, backgroundColor: '#0000cc' });
const button4 = this.add.text(10, 400, '10000', { fontFamily: 'Arial', fontSize: '24px', color: '#ffffff', align: 'center', fixedWidth: 260, backgroundColor: '#0000cc' });
button1.setPadding(16).setOrigin(0).setInteractive();
button2.setPadding(16).setOrigin(0).setInteractive();
button3.setPadding(16).setOrigin(0).setInteractive();
button4.setPadding(16).setOrigin(0).setInteractive();
button1.once('pointerdown', () => {
this.scene.start('BigLoad', { quantity: 2500 });
});
button2.once('pointerdown', () => {
this.scene.start('BigLoad', { quantity: 5000 });
});
button3.once('pointerdown', () => {
this.scene.start('BigLoad', { quantity: 7500 });
});
button4.once('pointerdown', () => {
this.scene.start('BigLoad', { quantity: 10000 });
});
if (Phaser.VERSION === '3.55.2')
{
const button5 = this.add.text(10, 500, 'Swap to 3.61', { fontFamily: 'Arial', fontSize: '24px', color: '#000000', align: 'center', fixedWidth: 260, backgroundColor: '#ffffff' });
button5.setPadding(16).setOrigin(0).setInteractive();
button5.once('pointerdown', () => {
window.location.href = 'https://labs.phaser.io/view.html?src=src/bugs/0000%20big%20load.js&v=live';
});
}
else
{
const button5 = this.add.text(10, 500, 'Swap to 3.55', { fontFamily: 'Arial', fontSize: '24px', color: '#000000', align: 'center', fixedWidth: 260, backgroundColor: '#ffffff' });
button5.setPadding(16).setOrigin(0).setInteractive();
button5.once('pointerdown', () => {
window.location.href = 'https://labs.phaser.io/view.html?src=src/bugs/0000%20big%20load.js&v=3.55.2';
});
}
}
}
class Demo extends Phaser.Scene
{
constructor()
{
super('BigLoad');
}
init (data)
{
this.quantity = data.quantity;
this.add.text(10, 10, `Loading ${this.quantity} files`, { font: '20px Courier', fill: '#00ff00' });
console.log('Quantity:', this.quantity);
}
preload ()
{
// this.load.setBaseURL('https://cdn.phaserfiles.com/v385');
const progress = this.add.graphics();
this.load.on('progress', value =>
{
progress.clear();
progress.fillStyle(0xffff00, 1);
progress.fillRect(0, 100, 375 * value, 60);
});
for (let i = 0; i < this.quantity; i++)
{
this.load.image(`block${i}`, 'https://labs.phaser.io/assets/sprites/128x128-v2.png');
}
}
create ()
{
const half = Math.floor(this.quantity / 2);
const last = this.quantity - 1;
this.add.sprite(100, 250, 'block0');
this.add.sprite(100, 350, `block${half}`);
this.add.sprite(100, 450, `block${last}`);
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 375,
height: 667,
backgroundColor: '#000000',
scene: [ SetQuantity, Demo ]
};
const game = new Phaser.Game(config);
Структура сцены выбора количества
Первая сцена SetQuantity служит меню для выбора количества файлов, которые будут загружены в следующей сцене. Это полезно для тестирования поведения игры при разной нагрузке.
В методе create() создается текстовое описание и четыре кнопки с вариантами количества: 2500, 5000, 7500 и 10000 файлов. Каждая кнопка — это объект текста, стилизованный под кнопку.
const button1 = this.add.text(10, 100, '2500', { fontFamily: 'Arial', fontSize: '24px', color: '#ffffff', align: 'center', fixedWidth: 260, backgroundColor: '#0000cc' });
После создания кнопкам задается отступ (setPadding), точка начала координат (setOrigin) и интерактивность (setInteractive). Обратите внимание, что обработчик события pointerdown использует метод once, а не on. Это гарантирует, что событие сработает только один раз, даже если пользователь будет кликать несколько раз.
button1.once('pointerdown', () => {
this.scene.start('BigLoad', { quantity: 2500 });
});
При клике запускается сцена BigLoad с передачей параметра quantity. Динамическая пятая кнопка позволяет переключиться между версиями Phaser 3.55.2 и 3.61 для сравнения поведения.
Передача данных между сценами и инициализация загрузки
Сцена Demo (с ключом BigLoad) получает переданное количество файлов через метод init(). Этот метод выполняется до preload() и идеально подходит для подготовки данных на основе параметров.
init (data)
{
this.quantity = data.quantity;
this.add.text(10, 10, `Loading ${this.quantity} files`, { font: '20px Courier', fill: '#00ff00' });
}
Переменная this.quantity сохраняется для использования в следующем методе preload(). Важно понимать, что init() вызывается только один раз при запуске сцены, что делает его надежным местом для инициализации параметров.
Организация массовой загрузки и отслеживание прогресса
В методе preload() происходит основная работа по загрузке файлов. Сначала создается графический объект progress для визуализации прогресса.
const progress = this.add.graphics();
Затем на событие progress менеджера загрузки this.load вешается обработчик, который отрисовывает желтый прямоугольник, ширина которого зависит от значения прогресса (от 0 до 1).
this.load.on('progress', value =>
{
progress.clear();
progress.fillStyle(0xffff00, 1);
progress.fillRect(0, 100, 375 * value, 60);
});
Цикл for используется для добавления в очередь загрузки указанного количества изображений. Все файлы имеют одинаковый URL, но разные ключи (block0, block1, ...). Это имитирует загрузку большого количества уникальных ассетов.
for (let i = 0; i < this.quantity; i++)
{
this.load.image(`block${i}`, 'https://labs.phaser.io/assets/sprites/128x128-v2.png');
}
Такой подход позволяет наглядно увидеть, как Phaser справляется с тысячами параллельных запросов, и является тестовым случаем для выявления потенциальных проблем с производительностью или лимитами браузера.
Использование загруженных ассетов
После завершения загрузки вызывается метод create(). В нем демонстрируется, что все файлы действительно загружены и доступны через свои ключи.
const half = Math.floor(this.quantity / 2);
const last = this.quantity - 1;
this.add.sprite(100, 250, 'block0');
this.add.sprite(100, 350, `block${half}`);
this.add.sprite(100, 450, `block${last}`);
Отображаются три спрайта: первый (block0), средний (например, block1250 при количестве 2500) и последний. Это подтверждает, что загрузка прошла успешно для всего диапазона ключей. На практике такой массовый подход к загрузке редко используется в рабочих проектах из-за высоких требований к сети и памяти.
Что попробовать дальше
Пример наглядно показывает механизм загрузки тысяч файлов в Phaser и потенциальные риски такого подхода. Для реальных проектов рекомендуется объединять ресурсы в атласы, использовать форматы вроде JSON Hash или Array для анимаций, и загружать ассеты порциями по мере необходимости. Вы можете поэкспериментировать с этим кодом: попробуйте добавить обработку ошибок загрузки, реализовать отмену загрузки при смене сцены или разделить загрузку на несколько этапов с помощью this.load.pack.
