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

Работа с графикой — ключевая часть разработки игр. Часто возникает необходимость создавать текстуры на лету, комбинируя несколько элементов, и затем использовать их как обычные спрайты. В Phaser для этого идеально подходит `Render Texture`. Эта статья покажет, как создать составную текстуру из примитива и изображения, сохранить её в кэш текстур и мгновенно генерировать десятки её копий, что особенно полезно для создания частиц, фоновых элементов или сложных составных объектов.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    bubbles = [];
    rt;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('dude', 'assets/sprites/phaser-dude.png');
    }

    create ()
    {
        this.rt = this.add.renderTexture(40, 40, 80, 80);

        const circle = this.add.circle(0, 0, 40, 0x6666ff).setAlpha(0.5).setVisible(false);

        this.rt.draw(circle, 40, 40);
        this.rt.draw('dude', 40, 40);
        this.rt.render();

        this.rt.saveTexture('bubbleboy');

        for (let i = 0; i < 50; i++)
        {
            const b = this.add.image(Phaser.Math.Between(100, 700), Phaser.Math.Between(100, 500), 'bubbleboy');

            this.bubbles.push(b);
        }

        this.input.on('pointerup', () =>
        {
            game.destroy(true);
        });
    }
}

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

const game = new Phaser.Game(config);

Что такое Render Texture и зачем она нужна

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

Основное преимущество — производительность. Если вам нужно множество одинаковых составных объектов (например, спрайт с нимбом или надписью), эффективнее один раз отрисовать эту композицию в Render Texture, сохранить её как текстуру, а затем создавать стандартные Image объекты из неё. Это снижает количество операций отрисовки (draw calls) на каждом кадре.

В нашем примере мы создадим текстуру, состоящую из полупрозрачного синего круга и спрайта "Phaser Dude" поверх него.

Инициализация и настройка сцены

Всё начинается с конфигурации игры и создания сцены. Класс Example расширяет Phaser.Scene.

В методе preload() загружается единственный внешний ресурс — спрайт. Обратите внимание, что setBaseURL задаёт базовый путь для удобства.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('dude', 'assets/sprites/phaser-dude.png');
}

Конфиг игры задаёт основные параметры: тип рендерера, родительский DOM-элемент, размеры окна и цвет фона. Важно, что в свойстве scene передаётся класс нашей сцены.

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

const game = new Phaser.Game(config);

Создание и наполнение Render Texture

Основная магия происходит в методе create(). Первым делом создаётся сам объект Render Texture с помощью this.add.renderTexture(x, y, width, height). Он позиционируется на сцене, но в данном примере это не важно, так как он служит лишь промежуточным холстом.

this.rt = this.add.renderTexture(40, 40, 80, 80);

Далее создаётся графический примитив — круг. Ключевые моменты: - setAlpha(0.5) делает его полупрозрачным. - setVisible(false) скрывает его как самостоятельный объект на сцене, потому что нам нужен только его отпечаток на текстуре.

const circle = this.add.circle(0, 0, 40, 0x6666ff).setAlpha(0.5).setVisible(false);

Метод draw() объекта rt помещает указанный объект (или текстуру по ключу) в его буфер отрисовки по заданным координатам. Координаты (40, 40) здесь — это центр нашей Render Texture размером 80x80 пикселей.

this.rt.draw(circle, 40, 40);
this.rt.draw('dude', 40, 40);

После того как все элементы отрисованы, вызывается this.rt.render(). Этот метод финализирует отрисовку на внутреннем холсте текстуры. Следующий шаг — сохранение результата в глобальный кэш текстур игры с помощью saveTexture(). Теперь к текстуре можно обращаться по ключу 'bubbleboy', как к любой загруженной картинке.

this.rt.render();
this.rt.saveTexture('bubbleboy');

Массовое создание объектов из новой текстуры

После того как текстура 'bubbleboy' сохранена, её можно использовать бесконечное количество раз. В цикле создаётся 50 стандартных объектов Image. Каждый из них использует нашу новую, динамически созданную текстуру.

Phaser.Math.Between задаёт случайные координаты для каждого спрайта в пределах игрового поля. Все созданные объекты сохраняются в массив bubbles (хотя в данном примере он далее не используется).

for (let i = 0; i < 50; i++)
{
    const b = this.add.image(Phaser.Math.Between(100, 700), Phaser.Math.Between(100, 500), 'bubbleboy');
    this.bubbles.push(b);
}

Финальный обработчик события pointerup демонстрирует, как можно полностью уничтожить экземпляр игры.

this.input.on('pointerup', () =>
{
    game.destroy(true);
});

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

Render Texture — это мощный инструмент для динамической генерации графики в Phaser. Он позволяет комбинировать различные элементы в единую текстуру, которую затем можно использовать для оптимизации или создания сложных визуальных эффектов. Для экспериментов попробуйте: анимировать элементы перед отрисовкой на текстуру, использовать Render Texture для создания мини-карты (мапы), менять свойства (например, tint) уже созданных объектов 'bubbleboy' или реализовать систему следов/отпечатков, рисуя на одной Render Texture каждый кадр.