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

Render Texture в 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.image('brush', 'assets/sprites/brush3.png');
    }

    create ()
    {
        const rt = this.add.renderTexture(64, 64, 128, 128).setInteractive().setDepth(1001);

        this.add.graphics().fillStyle(0x000000).lineStyle(1, 0xffffff).fillRect(0, 0, 128, 128).strokeRect(0, 0, 128, 128).setDepth(1000);

        this.add.text(136, 8, '<- draw in here\n   press SPACE to clear');

        const hsv = Phaser.Display.Color.HSVColorWheel();

        let i = 0;

        this.input.keyboard.on('keydown_SPACE', () =>
        {

            rt.clear().render();

        });

        rt.on('pointerdown', function (pointer)
        {

            this.draw('brush', pointer.x - 8, pointer.y - 8, 1, hsv[i].color).render();

        });

        rt.on('pointermove', function (pointer)
        {

            if (pointer.isDown)
            {
                this.draw('brush', pointer.x - 8, pointer.y - 8, 1, hsv[i].color).render();

                i = Phaser.Math.Wrap(i + 1, 0, 360);
            }

        });

        const tt = rt.saveTexture('doodle');

        const blocks = this.add.group({ key: 'doodle', repeat: 48, setScale: { x: 0.2, y: 0.1 } });

        Phaser.Actions.GridAlign(blocks.getChildren(), {
            width: 8,
            height: 6,
            cellWidth: 128,
            cellHeight: 128
        });

        blocks.children.forEach(function (child)
        {
            this.tweens.add({
                targets: child,
                scaleX: 1,
                scaleY: 1,
                ease: 'Sine.easeInOut',
                duration: 400,
                delay: i * 50,
                repeat: -1,
                yoyo: true
            });

            i++;

            if (i % 14 === 0)
            {
                i = 0;
            }

        }, this);
    }
}

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    width: 1024,
    height: 768,
    backgroundColor: '#2d2d88',
    scene: Example
};

const game = new Phaser.Game(config);

Создание холста: Render Texture и Graphics

В основе примера лежит объект Render Texture. Это специальный игровой объект, который действует как динамический буфер для рисования — своего рода холст в памяти.

const rt = this.add.renderTexture(64, 64, 128, 128).setInteractive().setDepth(1001);

Здесь создается текстура размером 128x128 пикселей с координатами (64, 64) на сцене. Метод setInteractive() делает ее чувствительной к событиям мыши/касания, а setDepth(1001) помещает ее поверх других элементов.

Чтобы визуально обозначить границы нашего холста, поверх него (но под самой текстурой из-за глубины 1000) рисуется черный прямоугольник с белой рамкой с помощью объекта Graphics.

this.add.graphics().fillStyle(0x000000).lineStyle(1, 0xffffff).fillRect(0, 0, 128, 128).strokeRect(0, 0, 128, 128).setDepth(1000);

Интерактивное рисование и управление цветом

Логика рисования привязана к событиям указателя на Render Texture. Для плавной смены цвета используется Phaser.Display.Color.HSVColorWheel(), который возвращает массив из 360 цветов, представляющих полный цветовой круг HSV.

const hsv = Phaser.Display.Color.HSVColorWheel();
let i = 0;

При нажатии (pointerdown) и перемещении с зажатой кнопкой (pointermove) мыши на текстуре вызывается метод draw().

rt.on('pointermove', function (pointer) {
    if (pointer.isDown) {
        this.draw('brush', pointer.x - 8, pointer.y - 8, 1, hsv[i].color).render();
        i = Phaser.Math.Wrap(i + 1, 0, 360);
    }
});

Метод draw() принимает ключ изображения ('brush'), координаты (с корректировкой -8 для центрирования кисти), масштаб и цвет (взятый из цветового круга). После каждого вызова draw() необходимо вызвать .render(), чтобы изменения отобразились на текстуре. Индекс цвета `iувеличивается и заворачивается в диапазон от 0 до 360 с помощьюPhaser.Math.Wrap`, обеспечивая плавный переход через все цвета радуги.

Очистить холст можно, нажав пробел. Обработчик события keydown_SPACE вызывает метод clear() и render().

this.input.keyboard.on('keydown_SPACE', () => {
    rt.clear().render();
});

Сохранение текстуры и создание группы спрайтов

Самая интересная часть — это возможность превратить нарисованную текстуру в переиспользуемый ресурс.

const tt = rt.saveTexture('doodle');

Метод saveTexture() сохраняет текущее содержимое Render Texture в текстуру кэша с ключом 'doodle'. Теперь к этой текстуре можно обращаться как к обычному загруженному изображению.

Сразу после сохранения создается группа (Group) из 48 спрайтов, каждый из которых использует нашу свежесозданную текстуру 'doodle'.

const blocks = this.add.group({ key: 'doodle', repeat: 48, setScale: { x: 0.2, y: 0.1 } });

Параметр setScale инициализирует все спрайты в группе с уменьшенным масштабом (0.2 по X, 0.1 по Y). Затем с помощью Phaser.Actions.GridAlign все спрайты выравниваются в сетку 8x6.

Phaser.Actions.GridAlign(blocks.getChildren(), {
    width: 8,
    height: 6,
    cellWidth: 128,
    cellHeight: 128
});

Анимация спрайтов на основе текстуры

Завершающий штрих — добавление пульсирующей анимации к каждому спрайту в группе с помощью твинов.

blocks.children.forEach(function (child) {
    this.tweens.add({
        targets: child,
        scaleX: 1,
        scaleY: 1,
        ease: 'Sine.easeInOut',
        duration: 400,
        delay: i * 50,
        repeat: -1,
        yoyo: true
    });
    i++;
    if (i % 14 === 0) { i = 0; }
}, this);

Для каждого дочернего спрайта создается твин, который анимирует его масштаб от начального маленького значения до 1 (полный размер). Параметры ease: 'Sine.easeInOut' и yoyo: true создают плавное пульсирующее движение. Задержка (delay: i * 50) для каждого следующего спрайта создает волнообразный эффект анимации по всей сетке. Счетчик `i` сбрасывается каждые 14 спрайтов, чтобы паттерн задержек повторялся.

Что попробовать дальше

Render Texture открывает путь к динамическому созданию контента в Phaser. Вы можете использовать этот подход не только для рисования, но и для компоновки сложных спрайтов из частей, создания динамических мини-карт, масок или уникальных эффектов повреждений. Попробуйте экспериментировать: сохраняйте несколько разных текстур в течение сессии, комбинируйте рисование с физическими телами или используйте содержимое текстуры как источник для шейдеров.