О чем этот пример
Визуальные эффекты — это то, что заставляет игровой мир дышать. Статичные объекты могут быстро наскучить. В этой статье мы разберём, как создать динамическую верёвку в Phaser 3, текстура которой генерируется в реальном времени с помощью GLSL-шейдера. Этот приём позволяет создавать уникальные, живые объекты — от магических потоков энергии до качающихся на ветру канатов — без использования готовых спрайтов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
this.rope;
this.count = 0;
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.glsl('bundle', 'assets/rope/bundle6.glsl.js');
}
create ()
{
// Create our Shader
const shader = this.add.shader('RayTracer', 0, 0, 700, 500);
// Set it to render to a texture
shader.setRenderToTexture('shaderTexture');
// Create our Rope, using the shader texture
const rope = this.add.rope(400, 300, 'shaderTexture', null, 16);
// Some shaders require us to flipY, like this one:
rope.setFlipY(true);
this.rope = rope;
this.add.text(10, 10, 'Rope using a Shader for a texture', { font: '16px Courier', fill: '#ffffff' }).setShadow(1, 1);
}
update ()
{
this.count += 0.1;
let points = this.rope.points;
for (let i = 0; i < points.length; i++)
{
if (this.rope.horizontal)
{
points[i].y = Math.sin(i * 0.5 + this.count) * 10;
}
else
{
points[i].x = Math.sin(i * 0.5 + this.count) * 10;
}
}
this.rope.setDirty();
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
let game = new Phaser.Game(config);
Идея и структура примера
Основная идея этого примера — отделить визуальное наполнение объекта от его геометрии. Вместо загрузки статичной картинки мы используем шейдер как фабрику текстур. Каждый кадр шейдер рендерит новое изображение, которое натягивается на гибкую геометрию верёвки.
Класс сцены содержит два ключевых объекта: сам объект rope и счётчик count для анимации. Основная логика распределена по стандартным методам жизненного цикла Phaser: preload, create и update.
Загрузка шейдера и подготовка текстуры
Шейдер, написанный на языке GLSL, загружается как ресурс. Важно использовать метод load.glsl(), который корректно обрабатывает такой тип файлов.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.glsl('bundle', 'assets/rope/bundle6.glsl.js');
После загрузки в методе create() мы создаём объект шейдера. Ключевой шаг — указать ему рендерить результат не на экран, а в текстуру с помощью метода setRenderToTexture(). Эта текстура будет доступна в кэше по имени 'shaderTexture'.
const shader = this.add.shader('RayTracer', 0, 0, 700, 500);
shader.setRenderToTexture('shaderTexture');
Создание и настройка верёвки
Объект верёвки создаётся как обычный игровой объект, но в качестве ключа текстуры передаётся имя текстуры, сгенерированной шейдером. Последний аргумент — количество сегментов (16), из которых состоит верёвка.
Некоторые шейдеры (как в данном примере) могут рендерить изображение с перевёрнутой по вертикали системой координат. Чтобы текстура отображалась правильно, необходимо вызвать метод setFlipY(true) для объекта верёвки.
const rope = this.add.rope(400, 300, 'shaderTexture', null, 16);
rope.setFlipY(true);
this.rope = rope;
Анимация геометрии верёвки
Динамика достигается за счёт изменения позиций контрольных точек (points) верёвки в каждом кадре (метод update). Счётчик count обеспечивает плавное движение синусоидальной волны вдоль верёвки.
Массив points содержит объекты с координатами `xиyдля каждой точки. В зависимости от ориентации верёвки (this.rope.horizontal) анимируется либо координатаy, либоx`.
this.count += 0.1;
let points = this.rope.points;
for (let i = 0; i < points.length; i++) {
if (this.rope.horizontal) {
points[i].y = Math.sin(i * 0.5 + this.count) * 10;
} else {
points[i].x = Math.sin(i * 0.5 + this.count) * 10;
}
}
После изменения координат точек необходимо вручную пометить объект как "грязный", чтобы движок перерисовал его геометрию. Для этого вызывается метод setDirty().
this.rope.setDirty();
Что попробовать дальше
Комбинирование шейдерных текстур с гибкой геометрией объектов, таких как rope, открывает огромный простор для визуальных экспериментов. Попробуйте заменить шейдер RayTracer на любой другой из примеров Phaser, который рисует огонь, воду или звёздное небо. Измените форму волны в update() с синуса на косинус или добавьте случайные колебания — и ваша верёвка превратится в пляшущее пламя или бурлящий поток.
