О чем этот пример
Шейдеры открывают двери к созданию сложных визуальных эффектов в играх, от плавных переходов до динамических текстур. Однако статичный шейдер — это лишь половина дела. Настоящая магия начинается, когда вы оживляете эти эффекты с помощью анимации. В этой статье мы разберем, как загружать и применять шейдеры в Phaser, а затем — как управлять ими, как обычными игровыми объектами, используя встроенную систему твинов для создания сложных трансформаций. Это позволит вам добавить в вашу игру уникальные, плавные визуальные эффекты, которые реагируют на действия игрока.
Версия 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.glsl('marble', 'assets/shaders/marble.frag');
this.load.image('bird', 'assets/pics/birdy.png');
}
create ()
{
this.add.image(400, 600, 'bird').setOrigin(0.5, 1);
const shader = this.add.shader({
name: 'marble',
fragmentKey: 'marble',
setupUniforms: (setUniform, drawingContext) =>
{
setUniform('time', this.game.loop.getDuration());
},
}, 400, 300, this.scale.width, this.scale.width);
this.input.once('pointerdown', function ()
{
this.tweens.add({
targets: shader,
props: {
scaleX: { value: 0.2, duration: 4000 },
scaleY: { value: 0.2, duration: 4000 },
angle: { value: 360, duration: 2000 },
y: { value: 100, duration: 1000 }
},
ease: 'Sine.easeInOut',
yoyo: true,
repeat: -1
});
}, this);
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#0972ff',
scene: Example
};
const game = new Phaser.Game(config);
Загрузка ресурсов: шейдер и изображение
Перед созданием эффектов необходимо загрузить ресурсы. В методе preload() мы используем два специальных метода загрузчика.
Метод this.load.glsl() загружает шейдерный код из файла. Первый аргумент — это ключ ('marble'), по которому мы будем обращаться к шейдеру позже. Второй — путь к файлу с фрагментным шейдером.
Метод this.load.image() загружает стандартное изображение для фона.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.glsl('marble', 'assets/shaders/marble.frag');
this.load.image('bird', 'assets/pics/birdy.png');
}
Создание сцены: фон и шейдер
В методе create() мы сначала размещаем фоновое изображение внизу сцены, устанавливая точку привязки (origin) в его низ.
Затем создаем основной объект шейдера с помощью this.add.shader(). Этот метод принимает объект конфигурации и параметры позиционирования.
В конфигурации указываем name (имя из загрузчика) и fragmentKey (ключ фрагментного шейдера). Ключевая функция setupUniforms позволяет передавать в шейдер динамические данные на каждом кадре. В данном примере мы передаем текущее время выполнения игры в uniform-переменную time, чтобы шейдер мог создавать анимированный эффект (например, "поток мрамора").
Последние четыре аргумента метода — это X, Y, ширина и высота области отрисовки шейдера.
create ()
{
this.add.image(400, 600, 'bird').setOrigin(0.5, 1);
const shader = this.add.shader({
name: 'marble',
fragmentKey: 'marble',
setupUniforms: (setUniform, drawingContext) =>
{
setUniform('time', this.game.loop.getDuration());
},
}, 400, 300, this.scale.width, this.scale.width);
// ... обработка клика и твины
}
Запуск анимации по клику
Чтобы запустить анимацию шейдера, мы вешаем одноразовый обработчик события клика (pointerdown) на входную систему Phaser (this.input).
Внутри обработчика создается твин с помощью this.tweens.add(). Целью твина (targets) является наш объект shader. Это демонстрирует важный принцип: объекты шейдеров в Phaser наследуют свойства от Phaser.GameObjects.Shader и, следовательно, могут быть анимированы как любые другие игровые объекты.
this.input.once('pointerdown', function ()
{
this.tweens.add({
targets: shader,
// ... свойства для анимации
});
}, this);
Настройка комплексного твина
В объекте конфигурации твина мы определяем несколько свойств для анимации в блоке props. Каждое свойство — это объект с параметрами value (конечное значение) и duration (длительность анимации в миллисекундах).
- scaleX и scaleY: Анимируют масштаб шейдера по осям. Длительность в 4000 мс делает сжатие плавным.
- angle: Вращает шейдер на 360 градусов за 2000 мс.
- `y`: Перемещает шейдер по вертикали.
Параметры yoyo: true и repeat: -1 заставляют твин проигрываться в прямом и обратном направлении бесконечно, создавая цикличную, живую анимацию.
props: {
scaleX: { value: 0.2, duration: 4000 },
scaleY: { value: 0.2, duration: 4000 },
angle: { value: 360, duration: 2000 },
y: { value: 100, duration: 1000 }
},
ease: 'Sine.easeInOut',
yoyo: true,
repeat: -1
Конфигурация игры
Для работы с шейдерами критически важно использовать контекст рендеринга WEBGL, а не CANVAS. Это указывается в объекте конфигурации игры.
const config = {
type: Phaser.WEBGL, // Обязательно WEBGL
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#0972ff',
scene: Example // Наша сцена
};
const game = new Phaser.Game(config);
Что попробовать дальше
Объекты шейдеров в Phaser — это полноценные игровые объекты. Вы можете применять к ним трансформации (позиция, масштаб, вращение), анимировать эти трансформации с помощью системы твинов и комбинировать их с другими элементами сцены. Для экспериментов попробуйте
- Анимировать uniform-переменные шейдера (например, цвет или силу эффекта) напрямую через твин, изменяя их в
update - Связать трансформации шейдера с движением игрового персонажа
- Использовать несколько шейдеров с разными режимами наложения (
blendMode) для создания сложных композиций
