О чем этот пример
Управление ресурсами — ключевой навык в разработке игр. В Phaser вы можете динамически подгружать и выгружать текстуры, что критически важно для игр с большим количеством ассетов или пошаговой загрузкой уровней. В этой статье мы разберем практический пример, который показывает, как загрузить текстуру атласа, удалить её из кэша и загрузить новую версию, переключаясь между сценами. Это поможет вам оптимизировать потребление памяти и организовать поток ресурсов в вашем проекте.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class SceneA extends Phaser.Scene
{
constructor ()
{
super('SceneA');
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('megaset', 'assets/atlas/megaset-0.png', 'assets/atlas/megaset-0.json');
}
create ()
{
const atlasTexture = this.textures.get('megaset');
const frames = atlasTexture.getFrameNames();
for (let i = 0; i < frames.length; i++)
{
let x = Phaser.Math.Between(0, 800);
let y = Phaser.Math.Between(0, 600);
this.add.image(x, y, 'megaset', frames[i]);
}
this.input.once('pointerdown', pointer => {
this.textures.remove('megaset');
this.scene.start('SceneB');
});
}
}
class SceneB extends Phaser.Scene
{
constructor ()
{
super('SceneB');
}
preload ()
{
// this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('megaset', 'assets/atlas/megaset-1.png', 'assets/atlas/megaset-1.json');
}
create ()
{
const atlasTexture = this.textures.get('megaset');
const frames = atlasTexture.getFrameNames();
for (let i = 0; i < frames.length; i++)
{
let x = Phaser.Math.Between(0, 800);
let y = Phaser.Math.Between(0, 600);
this.add.image(x, y, 'megaset', frames[i]);
}
this.input.once('pointerdown', pointer => {
this.textures.remove('megaset');
this.scene.start('SceneA');
});
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
scene: [ SceneA, SceneB ]
};
const game = new Phaser.Game(config);
Загрузка атласа в первой сцене
Класс SceneA начинает работу с загрузки текстурного атласа в методе preload(). Мы используем метод this.load.atlas(), передавая ему ключ и пути к изображению и JSON-файлу с данными.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('megaset', 'assets/atlas/megaset-0.png', 'assets/atlas/megaset-0.json');
}
После загрузки в методе create() мы получаем доступ к текстуре по ключу 'megaset' через менеджер текстур this.textures. Метод getFrameNames() возвращает массив имён всех кадров (спрайтов) внутри этого атласа. Затем в цикле мы создаём множество изображений, используя эти кадры, и размещаем их в случайных позициях на экране.
const atlasTexture = this.textures.get('megaset');
const frames = atlasTexture.getFrameNames();
for (let i = 0; i < frames.length; i++)
{
let x = Phaser.Math.Between(0, 800);
let y = Phaser.Math.Between(0, 600);
this.add.image(x, y, 'megaset', frames[i]);
}
Удаление текстуры и переход
Ключевой момент примера — освобождение ресурсов перед переходом на другую сцену. По клику мыши срабатывает обработчик события pointerdown. Внутри него мы явно удаляем текстуру атласа из менеджера текстур с помощью метода this.textures.remove(), передавая ключ 'megaset'.
this.input.once('pointerdown', pointer => {
this.textures.remove('megaset');
this.scene.start('SceneB');
});
Метод remove() не только убирает текстуру из кэша (TextureManager), но и автоматически уничтожает все связанные с ней объекты Image или Sprite, которые были созданы с её использованием. Это предотвращает утечки памяти. После очистки мы запускаем SceneB с помощью this.scene.start().
Загрузка нового атласа во второй сцене
SceneB структурно зеркалит первую сцену, но загружает другой набор данных атласа (файлы megaset-1.png и megaset-1.json). Обратите внимание, что setBaseURL здесь закомментирован, так как базовый URL уже был установлен в SceneA и сохраняется для всей игры.
preload ()
{
// this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('megaset', 'assets/atlas/megaset-1.png', 'assets/atlas/megaset-1.json');
}
Несмотря на то что ключ 'megaset' совпадает с использованным ранее, предыдущая текстура была удалена. Поэтому Phaser загружает новый атлас и снова привязывает его к этому ключу. В create() происходит тот же процесс создания изображений из нового набора кадров. В конце, по клику, текстура снова удаляется, и игра возвращается к SceneA, создавая бесконечный цикл переключения и демонстрации работы с ресурсами.
Почему это важно? Практическое применение
1. **Управление памятью**: В больших играх нельзя держать все ресурсы загруженными одновременно. Удаление ненужных текстур (например, для пройденного уровня) освобождает оперативную память и видеопамять.
2. **Динамическая загрузка контента**: Вы можете подгружать разные графические наборы для разных персонажей, локаций или языковых пакетов по мере необходимости, используя один и тот же логический ключ.
3. **Перезапись ресурсов**: Метод remove() позволяет "перезаписать" ресурс по существующему ключу, что полезно для систем загрузки улучшенных текстур (HD) или модов.
Важно помнить, что this.textures.remove() — это деструктивная операция. Все спрайты, использовавшие эту текстуру, будут уничтожены. Если вам нужно просто остановить отображение, но сохранить текстуру в памяти, следует управлять самими игровыми объектами, а не их ресурсами.
Что попробовать дальше
Phaser предоставляет простой, но мощный API для контроля над жизненным циклом текстур. Используя связку load.atlas(), textures.get(), textures.remove() и scene.start(), вы можете создавать сложные сцены с динамической подгрузкой контента без утечек памяти. Для экспериментов попробуйте: загружать атласы по сети по требованию, реализовать прогрессивную загрузку уровней или создать систему смены графических тем (пиксель-арт / HD) в реальном времени с перезагрузкой текстур.
