О чем этот пример
Работа с мультиатласами (multi-atlas) в Phaser 3 — отличный способ организовать игровые ассеты. Однако при использовании их с системами частиц (Particle Emitter) вы можете столкнуться с коварной ошибкой: частицы отображаются некорректно или вовсе не отображаются, если их кадр находится во втором или последующих файлах атласа. Эта статья разбирает конкретный пример такой ошибки, объясняет её причину и предлагает практические решения для её устранения. Понимание этой проблемы поможет вам избежать часов отладки и создавать стабильные визуальные эффекты.
Версия 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.path = 'assets/atlas/';
this.load.multiatlas('megaset', 'tp3-multi-atlas.json');
}
create ()
{
// Good - This frame from the 1st atlas would be displayed correctly
this.add.image(40, 60, 'megaset', 'diamond')
// Good - This frame from the 2nd atlas would be displayed correctly
this.add.image(80, 60, 'megaset', 'gem')
// Good - These particles of a frame from the 1st atlas would be displayed correctly
this.add.particles(40, 200, 'megaset', { frame: 'diamond' });
// Bad - These particles of a frame from the 2nd atlas would be displayed incorrectly
this.add.particles(80, 200, 'megaset', { frame: 'gem' });
}
}
new Phaser.Game({
type: Phaser.AUTO,
width: 800,
height: 600,
scene: Example,
parent: 'phaser-example'
});
Проблема: Частицы из второго атласа не работают
В примере загружается мультиатлас megaset, состоящий из нескольких файлов изображений (например, tp3-multi-atlas-0.png и tp3-multi-atlas-1.png) и одного JSON-файла с разметкой.
При создании сцены мы видим четыре объекта:
* Два статичных спрайта: один с кадром 'diamond' (из первого файла атласа), другой с кадром 'gem' (из второго файла атласа). Оба отображаются корректно.
* Два эмиттера частиц: первый использует кадр 'diamond', второй — кадр 'gem'.
Здесь и кроется проблема. Частицы с кадром 'gem' отображаются некорректно (например, как пустой прямоугольник или с искажённой текстурой), в то время как частицы с кадром 'diamond' работают как надо.
// Работает корректно
this.add.particles(40, 200, 'megaset', { frame: 'diamond' });
// Отображается с ошибкой!
this.add.particles(80, 200, 'megaset', { frame: 'gem' });
Причина: Механика работы Particle Emitter с текстурами
Корень проблемы лежит в том, как система частиц (Particle Emitter Manager) получает доступ к текстурам. В отличие от обычного спрайта, который может корректно разрешать кадры из любого файла внутри мультиатласа, эмиттер частиц на момент инициализации часто обращается только к *первому* изображению (текстуре) из набора.
Когда вы создаёте эмиттер и указываете кадр 'gem', система находит его метаданные в JSON (координаты, размер), но пытается взять пиксельные данные из первой загруженной текстуры (где этого кадра нет). Это приводит к ошибке выборки текстуры (texture sampling error) на GPU.
Проще говоря: this.add.image() умеет «заглядывать» во все файлы атласа, а this.add.particles() в определённых условиях — только в первый.
Решение 1: Предварительная настройка текстур (Рекомендуется)
Самое надёжное решение — явно указать системе частиц, какую текстуру использовать. Для этого нужно «подготовить» (prep) текстуру, связанную с нужным кадром, до создания эмиттера.
Шаги:
1. Получить ссылку на объект текстуры (Texture) атласа по его ключу.
2. Найти в этой текстуре конкретный источник (Source), который содержит нужный кадр.
3. Использовать метод prep, чтобы система частиц могла с этим источником работать.
create() {
const texture = this.textures.get('megaset');
const source = texture.getSourceImage('gem'); // Находим источник для кадра 'gem'
texture.prepare(source); // Критически важный шаг для Particle Emitter
// Теперь частицы с кадром 'gem' будут работать
this.add.particles(80, 200, 'megaset', { frame: 'gem' });
}
Метод getSourceImage автоматически находит, в каком из файлов мультиатласа лежит указанный кадр. Метод prep гарантирует, что этот источник данных будет корректно передан в систему рендеринга частиц.
Решение 2: Использование отдельных атласов
Если ваши эффекты используют кадры только из одного конкретного файла атласа, можно пойти по пути разделения. Вместо одного мультиатласа загрузите несколько обычных атласов под разными ключами.
preload() {
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.path = 'assets/atlas/';
// Загружаем два отдельных атласа
this.load.atlas('atlas1', 'tp3-multi-atlas-0.png', 'tp3-multi-atlas.json');
this.load.atlas('atlas2', 'tp3-multi-atlas-1.png', 'tp3-multi-atlas.json'); // Тот же JSON, но система выберет нужные фреймы
}
create() {
// Частицы берутся из гарантированно правильного источника
this.add.particles(40, 200, 'atlas1', { frame: 'diamond' });
this.add.particles(80, 200, 'atlas2', { frame: 'gem' });
}
Этот подход менее эффективен с точки зрения организации ассетов (больше ключей для запоминания), но полностью исключает проблему, так как каждый эмиттер привязан к однозначному источнику текстуры.
Проверка и отладка
Чтобы убедиться, в каком источнике находится ваш кадр, можно использовать методы объекта текстуры для отладки.
create() {
const texture = this.textures.get('megaset');
const frame = texture.getFrame('gem'); // Получаем объект кадра
const sourceIndex = frame.sourceIndex; // Индекс источника (0, 1, 2...)
console.log(`Кадр 'gem' находится в источнике с индексом ${sourceIndex}`);
}
Если sourceIndex больше 0, а вы не выполнили texture.prepare(), то с кадром 'gem' почти наверняка возникнут проблемы при использовании в частицах.
Что попробовать дальше
Ошибка с отображением частиц из мультиатласа — известный нюанс Phaser 3, связанный с оптимизацией работы текстур. Решение через texture.prepare() является каноническим и должно применяться для любого кадра, источник которого (изображение) отличается от источника первого кадра в атласе.
**Идеи для экспериментов:**
* Создайте эмиттер, который случайным образом выбирает кадры для частиц из разных источников мультиатласа, предварительно подготовив все необходимые текстуры.
* Проверьте, влияет ли эта проблема на другие объекты, использующие текстуры динамически, например, на Render Texture или Tile Sprites.
* Напишите вспомогательную функцию, которая автоматически готовит все источники текстуры при создании сцены, если используется мультиатлас.
