О чем этот пример
Загрузка всех ресурсов игры при старте — простой, но не всегда оптимальный подход. Он может привести к долгому ожиданию для игрока и неэффективному использованию памяти, если некоторые ресурсы нужны только на определенных уровнях или экранах. В этой статье мы разберем, как реализовать отложенную (ленивую) загрузку ресурсов в Phaser 3 на примере загрузки изображений по клику на кнопку. Этот паттерн позволяет ускорить начальную загрузку игры, разделить ресурсы на части и загружать их только тогда, когда они действительно нужны.
Версия 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.image('buttonBG', 'assets/sprites/button-bg.png');
this.load.image('buttonText', 'assets/sprites/button-text.png');
}
create ()
{
const bg = this.add.image(0, 0, 'buttonBG').setInteractive();
const text = this.add.image(0, 0, 'buttonText');
const container = this.add.container(400, 300, [ bg, text ]);
bg.once('pointerup', this.loadImage, this);
}
loadImage ()
{
this.load.once('complete', this.addSprites, this);
this.load.image('pic', 'assets/pics/turkey-1985086.jpg');
this.load.image('titan', 'assets/pics/titan-mech.png');
this.load.start();
}
addSprites ()
{
this.add.image(400, 300, 'pic');
this.add.image(400, 300, 'titan');
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Базовый принцип: Загрузчик и события
В Phaser за загрузку ресурсов отвечает объект Loader (this.load в контексте сцены). Обычно загрузка происходит в методе preload(), и игра начинает работу только после её завершения.
Однако, загрузчик можно использовать и после старта игры, в любой момент. Ключевые методы для этого:
- `this.load.image(key, url)` — добавляет изображение в очередь загрузки.
- `this.load.start()` — запускает процесс загрузки добавленных ресурсов.
- `this.load.once('complete', callback)` — позволяет назначить обработчик, который сработает однократно после завершения текущей сессии загрузки.
this.load.once('complete', this.onLoadComplete, this);
this.load.image('newAsset', 'assets/new-image.png');
this.load.start();
Подготовка сцены и создание интерактивной кнопки
В нашем примере сцена начинает с загрузки двух обязательных ресурсов для кнопки: фона (buttonBG) и текста (buttonText). Это делается в стандартном preload().
В create() мы создаем кнопку, используя контейнер (Container). Контейнер позволяет группировать несколько игровых объектов (два изображения) и управлять ими как единым целым. Фоновому изображению назначается интерактивность (setInteractive()), чтобы оно могло реагировать на события мыши.
const bg = this.add.image(0, 0, 'buttonBG').setInteractive();
const text = this.add.image(0, 0, 'buttonText');
const container = this.add.container(400, 300, [ bg, text ]);
bg.once('pointerup', this.loadImage, this);
Обратите внимание на метод once('pointerup', ...). В отличие от on(), он подписывает обработчик на событие только один раз. После первого клика подписка автоматически удалится, что предотвращает множественные запуски загрузки.
Запуск динамической загрузки по событию
При клике на кнопку вызывается метод loadImage(). Его задача — настроить и запустить новую сессию загрузчика.
Сначала мы настраиваем слушатель события 'complete'. Это важно сделать *до* запуска загрузки (this.load.start()), чтобы гарантировать, что обработчик будет вызван после её окончания. Использование once() гарантирует, что обработчик не накопится при повторных вызовах.
Затем в очередь загрузчика добавляются два новых изображения с ключами 'pic' и 'titan'. Метод this.load.start() инициирует процесс.
loadImage ()
{
this.load.once('complete', this.addSprites, this);
this.load.image('pic', 'assets/pics/turkey-1985086.jpg');
this.load.image('titan', 'assets/pics/titan-mech.png');
this.load.start();
}
Пока загрузка идет, игра продолжает работать. Загрузчик по умолчанию не блокирует основной поток.
Обработка завершения загрузки
Как только загрузчик сообщит о событии 'complete', будет вызван метод addSprites(). Внутри него мы можем быть уверены, что текстуры 'pic' и 'titan' доступны в кэше текстур игры, и безопасно добавлять их на сцену.
addSprites ()
{
this.add.image(400, 300, 'pic');
this.add.image(400, 300, 'titan');
}
Важно: загруженные таким образом ресурсы становятся доступны глобально для всех сцен игры, как и те, что были загружены в preload().
Что попробовать дальше
Динамическая загрузка ресурсов — мощный инструмент для оптимизации игр на Phaser. Этот подход позволяет разбить ресурсы на пакеты и загружать их по требованию, например, при переходе на новый уровень, открытии меню или, как в нашем примере, по действию игрока.
**Идеи для экспериментов:**
1. Добавьте индикатор прогресса загрузки (событие 'progress' у this.load).
2. Загружайте не только изображения, но и аудио (this.load.audio) или данные JSON.
3. Реализуйте систему предзагрузки ресурсов для следующего уровня, пока игрок находится на текущем.
4. Добавьте обработку ошибок загрузки, подписавшись на событие 'loaderror'.
