О чем этот пример

Шейдеры открывают двери к созданию сложных визуальных эффектов в играх, от плавных переходов до динамических текстур. Однако статичный шейдер — это лишь половина дела. Настоящая магия начинается, когда вы оживляете эти эффекты с помощью анимации. В этой статье мы разберем, как загружать и применять шейдеры в 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 — это полноценные игровые объекты. Вы можете применять к ним трансформации (позиция, масштаб, вращение), анимировать эти трансформации с помощью системы твинов и комбинировать их с другими элементами сцены. Для экспериментов попробуйте

  1. Анимировать uniform-переменные шейдера (например, цвет или силу эффекта) напрямую через твин, изменяя их в update
  2. Связать трансформации шейдера с движением игрового персонажа
  3. Использовать несколько шейдеров с разными режимами наложения (blendMode) для создания сложных композиций