О чем этот пример
Визуальные эффекты — мощный инструмент для создания атмосферы в игре. Шейдеры позволяют применять сложные пост-обработки, такие как свечение, искажение или стилизация, ко всему, что видит камера. Эта статья на практическом примере показывает, как загрузить GLSL-шейдер, применить его как фильтр к основной камере и динамически управлять его параметрами, чтобы оживить сцену. Вы научитесь работать с фрагментными шейдерами в Phaser, настраивать униформы (uniforms) для анимации эффекта и комбинировать шейдерный слой с обычными спрайтами. Этот подход открывает путь к созданию уникального визуального стиля для вашего проекта.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor()
{
super();
}
preload()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.glsl('machineshaman', 'assets/shaders/machine-shaman.frag');
this.load.image('logo', 'assets/sprites/phaser3-logo-small.png');
this.load.image('mask', 'assets/tests/camera/grunge-mask.png');
}
create()
{
this.cameras.main.filters.internal.addMask('mask');
this.add.shader({
name: 'machineshaman',
fragmentKey: 'machineshaman',
setupUniforms: (setUniform, drawingContext) => {
setUniform('time', this.game.loop.getDuration());
},
}, 400, 300, 800, 800);
this.add.image(400, 300, 'logo');
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Загрузка ресурсов: шейдер и изображения
Для работы с шейдером его исходный код нужно загрузить как текстовый ресурс. В Phaser для этого используется метод load.glsl(). Вместе с ним загружаются изображения, которые будут использоваться в сцене: логотип для отображения и маска для фильтра.
preload() {
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.glsl('machineshaman', 'assets/shaders/machine-shaman.frag');
this.load.image('logo', 'assets/sprites/phaser3-logo-small.png');
this.load.image('mask', 'assets/tests/camera/grunge-mask.png');
}
* load.setBaseURL() задаёт базовый URL для всех последующих загрузок, что удобно при работе с ресурсами из одного источника.
* load.glsl('machineshaman', ...) загружает файл с фрагментным шейдером и присваивает ему ключ 'machineshaman'. Этот ключ понадобится позже для создания шейдерного объекта.
* load.image() загружает два спрайта: логотип и маску.
Применение маски к фильтрам камеры
Перед созданием шейдера к основной камере применяется маска. Маска ограничивает область действия фильтров камеры, что позволяет создавать эффекты только на части экрана (например, виньетирование).
create() {
this.cameras.main.filters.internal.addMask('mask');
// ... остальной код
}
* this.cameras.main получает доступ к основной камере сцены.
* filters.internal.addMask('mask') применяет загруженное ранее изображение с ключом 'mask' в качестве маски для системы фильтров этой камеры. Всё, что отрендерит камера, будет обрезано или замаскировано согласно прозрачности этого изображения.
Создание и настройка шейдерного объекта
Сердце примера — добавление шейдера на сцену. Он создаётся как отдельный объект с заданными размером и позицией, поверх которого можно отрисовывать обычные спрайты.
this.add.shader({
name: 'machineshaman',
fragmentKey: 'machineshaman',
setupUniforms: (setUniform, drawingContext) => {
setUniform('time', this.game.loop.getDuration());
},
}, 400, 300, 800, 800);
* `this.add.shader()` создаёт новый объект шейдера и добавляет его на сцену.
* Конфигурационный объект:
* `fragmentKey: 'machineshaman'` указывает, какой загруженный шейдерный код использовать.
* `setupUniforms` — это коллбэк, который вызывается каждый кадр для обновления униформ шейдера (переменных, передаваемых из JavaScript в шейдер).
* Внутри коллбэка `setUniform('time', this.game.loop.getDuration())` передаёт в шейдер под именем `'time'` текущее время работы игры в миллисекундах. Это стандартный приём для анимации шейдерных эффектов (например, для движения волн или изменения цвета).
* Аргументы `400, 300, 800, 800` задают позицию (`x`, `y`) и размеры (`width`, `height`) шейдерного объекта на сцене.
Добавление графики поверх шейдера
Шейдерный объект рендерится как слой. Чтобы поверх него отображалась обычная 2D-графика (спрайты, текст), её нужно добавить после создания шейдера.
this.add.image(400, 300, 'logo');
* this.add.image() добавляет загруженное изображение логотипа в центр сцены (координаты 400, 300).
* Поскольку этот вызов идёт после add.shader(), логотип будет отрисован поверх шейдерного слоя, что позволяет комбинировать сложные фоновые эффекты с чёткими интерфейсными элементами.
Базовая конфигурация игры (WebGL)
Для работы с шейдерами и фильтрами камеры рендерер игры должен быть Phaser.WEBGL. В Canvas-режиме эти функции недоступны.
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
* type: Phaser.WEBGL — обязательный параметр, активирующий WebGL-рендерер.
* scene: Example указывает, что класс Example будет использоваться как начальная сцена.
Что попробовать дальше
Вы освоили базовый паттерн добавления анимированных шейдерных эффектов в Phaser 3: от загрузки кода до интеграции в игровую сцену. Теперь вы можете экспериментировать: попробуйте заменить шейдер на свой собственный из GLSL Sandbox, передавать в setupUniforms не только время, но и позицию мыши (this.input.activePointer), или применить фильтр не к фону, а к конкретному спрайту, используя sprite.setPipeline(). Сочетание таких техник поможет создать уникальную визуальную идентичность вашей игры.
