О чем этот пример
Визуальные эффекты (PostFX) и маски — мощные инструменты для создания стильной графики в играх на Phaser. Однако их совместное использование может привести к неочевидным багам рендеринга: объекты могут становиться невидимыми, эффекты — пропадать, а маски — игнорироваться. В этой статье мы разберем пример из репозитория Phaser, который демонстрирует типичные конфликты, и сформулируем практические правила совместимости. Это сэкономит вам часы отладки при работе со сложными визуальными композициями.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Demo extends Phaser.Scene
{
constructor()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('pic', 'assets/pics/neotokyo-ai.jpg');
this.load.image('logo', 'assets/sprites/phaser2.png');
this.load.image('card', 'assets/pics/slug.png');
}
create ()
{
const m1 = this.make.graphics();
m1.fillCircle(200, 300, 200);
m1.fillCircle(600, 300, 200);
const mask1 = m1.createGeometryMask();
const a = this.add.image(400, 300, 'pic');
const b = this.add.image(600, 300, 'logo');
const c = this.add.image(200, 300, 'card');
a.setMask(mask1);
// const area1 = this.textures.addDynamicTexture('area1', 128, 128);
// area1.fill(0x00ff00);
// area1.drawFrame('card');
// this.add.sprite(650, 100, 'area1');
// a.preFX.addPixelate(4);
// a.preFX.addBarrel(4);
// a.postFX.addBarrel(4.0);
// b.preFX.addBarrel(0.5);
// b.postFX.addBarrel(0.5);
// b.postFX.addPixelate(4);
// c.postFX.addBarrel(0.5);
// c.preFX.addBarrel(0.5);
// pre + pre + cam post = works
// post + pre + cam post = pre invisible
// pre + post + cam post = cam post fails
// post + pre + post + cam post = everything fails after first post (but cam post works)
const m = this.make.graphics();
// m.fillRectShape(new Phaser.Geom.Rectangle(50, 150, 700, 300));
m.fillCircle(400, 300, 300);
const mask = m.createGeometryMask();
// depth buffer true + camera mask and camera postFX = normal sprites work, postfx ones invisible
// depth buffer false + camera mask and camera postFX = mask ignored, fx works
// depth buffer false + sprite mask and sprite postFX = works
// depth buffer false + camera mask and sprite postFX = works
// depth buffer false + sprite mask and camera postFX = mask ignored, fx works
// depth buffer true + sprite mask and camera postFX = all works
// depth buffer true + sprite mask and sprite postFX = sprite invisible
// depth buffer true + camera mask and sprite postFX = sprite invisible
// depth buffer true + sprite postFX, camera empty = sprite works
// mask.invertAlpha = true;
this.cameras.main.setMask(mask);
// this.cameras.main.postFX.addPixelate(4);
// this.cameras.main.postFX.addGradient();
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#000000',
scene: Demo
};
const game = new Phaser.Game(config);
Базовый пример: маска для спрайта
В методе create создается простая сцена с тремя изображениями. Ключевой момент — применение геометрической маски к одному из них, изображению с ключом 'pic'.
Сначала создается графический объект m1 с помощью this.make.graphics(). На нем рисуются два круга.
const m1 = this.make.graphics();
m1.fillCircle(200, 300, 200);
m1.fillCircle(600, 300, 200);
const mask1 = m1.createGeometryMask();
Затем маска применяется к спрайту `aс помощью методаsetMask. В результате изображение 'pic' будет видно только в пределах нарисованных кругов. Другие спрайты (bиc`) маской не затрагиваются и отображаются полностью.
const a = this.add.image(400, 300, 'pic');
a.setMask(mask1);
Маски и эффекты на уровне камеры
Более сложный случай — использование маски и PostFX на основном cameras.main. В примере создается вторая маска в виде одного большого круга.
const m = this.make.graphics();
m.fillCircle(400, 300, 300);
const mask = m.createGeometryMask();
Эта маска устанавливается на камеру. Это значит, что ВСЕ, что видит эта камера (все объекты сцены), будет обрезано по форме круга.
this.cameras.main.setMask(mask);
В закомментированном коде также показано, как можно добавить эффекты на саму камеру, например, пикселизацию. Эффекты камеры применяются ко всему итоговому изображению, которое она рендерит, уже ПОСЛЕ применения ее маски.
// this.cameras.main.postFX.addPixelate(4);
Главный источник проблем: буфер глубины (depth buffer)
Конфликты возникают при одновременном использовании PostFX на отдельных спрайтах и масок на камере. Корень проблемы — состояние WebGL буфера глубины (depth buffer).
Когда depth buffer включен (по умолчанию в WebGL-рендерере Phaser), рендерер пытается корректно отсортировать объекты по глубине. Однако PostFX и маски изменяют порядок и способ отрисовки, что может сбить с толку механизм буфера глубины. В результате объекты с эффектами могут стать невидимыми.
Автор примера в комментариях перечислил множество комбинаций. Вот некоторые выводы:
- depth buffer: false часто позволяет эффектам работать, но может ломать маски камеры.
- depth buffer: true обеспечивает корректную работу масок, но может «прятать» спрайты с их собственными PostFX.
Настройка типа рендерера (и, следовательно, буфера глубины) задается в конфигурации игры. В данном примере используется Phaser.AUTO, который, скорее всего, выберет WebGL с включенным буфером глубины.
Практические правила совместимости
На основе исследования примера можно сформулировать рабочие подходы:
1. **Разделяй и властвуй.** Старайся не смешивать PostFX на спрайтах (sprite.postFX.add...) с масками на камере (camera.setMask). Это самая проблемная комбинация.
2. **Приоритет эффектам камеры.** Если нужны глобальные эффекты (на всю сцену) и обрезка по маске, применяй и то, и другое на уровне камеры. Это более стабильно.
3. **Отключай буфер глубины для сложных композиций.** Если тебе критически необходимы PostFX на отдельных объектах вместе с масками, попробуй отключить depth buffer в конфиге. Но будь готов к тому, что маски камеры могут перестать работать.
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#000000',
scene: Demo,
render: {
depthBuffer: false // Экспериментальная настройка
}
};
4. **Тестируй в целевых браузерах.** Поведение рендерера может отличаться в зависимости от реализации WebGL.
Что попробовать дальше
Совместное использование масок и PostFX в Phaser требует осознанного выбора архитектуры рендеринга. Самый надежный путь — применять эффекты и маски на одном уровне: либо на отдельных спрайтах (для локальных изменений), либо на камере (для глобальных). Экспериментируй с настройкой depthBuffer в конфиге и тестируй комбинации из закомментированного кода примера, чтобы найти рабочее решение для твоего конкретного визуального замысла.
