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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    iter = 0;
    blitter;
    tilesprite;
    quad;
    graphics;
    bitmaptext;
    particles;
    bunny;
    text;
    bob;
    rt;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('bunny', 'assets/sprites/bunny.png');
        this.load.image('pic', 'assets/pics/baal-loader.png');
        this.load.atlas('flares', 'assets/particles/flares.png', 'assets/particles/flares.json');
        this.load.bitmapFont('desyrel', 'assets/fonts/bitmap/desyrel.png', 'assets/fonts/bitmap/desyrel.xml');
        this.load.image('image', 'assets/pics/sure-shot-by-made.png');
        this.load.image('mushroom', 'assets/sprites/mushroom2.png');
        this.load.image('atari', 'assets/sprites/atari130xe.png');
    }

    create ()
    {
        this.bunny = this.textures.getFrame('bunny');
        this.text = this.add.text(0, 0, 'phaser 3?').setVisible(false);
        this.bob = this.add.image(0, 0, 'bunny').setName('bob').setVisible(false);
        this.particles = this.add.particles(200, 300, 'flares', {
            frame: 'blue',
            lifespan: 2000,
            speed: { min: 400, max: 600 },
            angle: 330,
            gravityY: 300,
            scale: { start: 0.4, end: 0 },
            quantity: 2,
            blendMode: 'ADD'
        }).setVisible(false);
        this.bitmaptext = this.add.bitmapText(0, 0, 'desyrel', 'PHASER 3\nRender Texture').setVisible(false);

        this.graphics = this.add.graphics().setVisible(false);

        this.graphics.fillStyle(0xffff00, 1);

        this.graphics.slice(400, 300, 200, Phaser.Math.DegToRad(340), Phaser.Math.DegToRad(20), true);

        this.graphics.fillPath();

        this.tilesprite = this.add.tileSprite(400, 300, 250, 250, 'mushroom').setVisible(false);

        this.blitter = this.add.blitter(0, 0, 'atari').setVisible(false);

        this.blitter.create(0, 0);

        this.rt = this.add.renderTexture(400, 300, 800, 600);
    }

    update ()
    {
        this.rt.camera.rotation -= 0.01;

        this.rt.clear();

        this.rt
        .draw(this.graphics, 0, 0)
        .draw(this.bob, 200, 200)
        .draw(this.tilesprite, 200, 200)
        .draw(this.blitter, 0, 0)
        .draw(this.text, 100, 100)
        .draw(this.bob, 300, 300)
        .draw(this.bob, 400, 400)
        .draw(this.text, 300, 200)
        .draw(this.particles, 300, 0)
        .draw(this.bitmaptext, 200, 100);

        this.rt.render();

        this.tilesprite.tilePositionX = Math.cos(-this.iter) * 400;
        this.tilesprite.tilePositionY = Math.sin(-this.iter) * 400;

        this.iter += 0.01;
    }
}

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

const game = new Phaser.Game(config);

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

RenderTexture — это специальный тип игрового объекта (Game Object), который представляет собой динамически создаваемый буфер для отрисовки. В него можно "записывать" другие игровые объекты с помощью метода .draw(). После отрисовки RenderTexture ведет себя как обычное изображение: ее можно перемещать, вращать, масштабировать.

Основные преимущества: * **Производительность:** Отрисовав сложную композицию один раз в текстуру, вы можете рендерить ее как единый спрайт, вместо того чтобы перерисовывать каждый объект отдельно в каждом кадре. * **Гибкость:** Вы можете применять трансформации (вращение, масштаб) ко всей собранной сцене через камеру самой RenderTexture. * **Создание эффектов:** Идеально подходит для реализации зеркал, мини-карт, снимков экрана или нестандартных систем частиц.

В примере мы создаем текстуру размером 800x600 пикселей в центре экрана.

Подготовка объектов для отрисовки

Перед использованием RenderTexture необходимо создать и настроить все объекты, которые мы хотим в нее нарисовать. Важный нюанс: сами эти объекты не должны отображаться на основной сцене. Для этого у каждого из них вызывается .setVisible(false).

В методе `create()` инициализируются:
*   `this.bob`: Спрайт (`Image`) с зайцем.
*   `this.text`: Текстовый объект (`Text`).
*   `this.particles`: Система частиц (`ParticleEmitter`).
*   `this.bitmaptext`: Растровый шрифт (`BitmapText`).
*   `this.graphics`: Графический объект (`Graphics`) с нарисованной "долькой".
*   `this.tilesprite`: `TileSprite` (плиточный спрайт) с изображением гриба.
*   `this.blitter`: `Blitter` — низкоуровневый объект для быстрой отрисовки спрайтов.
this.bob = this.add.image(0, 0, 'bunny').setName('bob').setVisible(false);
this.text = this.add.text(0, 0, 'phaser 3?').setVisible(false);
this.particles = this.add.particles(200, 300, 'flares', {
    frame: 'blue',
    lifespan: 2000,
    // ... другие настройки
}).setVisible(false);

После этого создается сама RenderTexture.

this.rt = this.add.renderTexture(400, 300, 800, 600);

Цикл отрисовки и анимация

Вся магия происходит в методе update(), который выполняется каждый кадр.

1. **Вращение камеры текстуры:** Мы вращаем не саму текстуру, а ее внутреннюю камеру. Это заставляет всю нарисованную сцену вращаться.

this.rt.camera.rotation -= 0.01;

2. **Очистка буфера:** Перед новой отрисовкой старый кадр нужно стереть.

this.rt.clear();

3. **Рисование объектов:** Метод .draw() принимает объект и координаты (относительно центра RenderTexture). Объекты можно рисовать несколько раз в разных местах.

this.rt
.draw(this.graphics, 0, 0)
.draw(this.bob, 200, 200)
.draw(this.bob, 300, 300) // Зайца рисуем повторно
.draw(this.text, 100, 100);

4. **Запрос на отрисовку:** После того как все объекты добавлены в очередь отрисовки, вызывается .render().

this.rt.render();

5. **Анимация TileSprite:** Отдельно анимируется смещение текстуры внутри TileSprite, чтобы создать эффект движения фона, который затем тоже рисуется в RenderTexture.

this.tilesprite.tilePositionX = Math.cos(-this.iter) * 400;
this.tilesprite.tilePositionY = Math.sin(-this.iter) * 400;

Ключевые методы API Render Texture

Работа с RenderTexture строится вокруг нескольких основных методов:

* clear(): Полностью очищает текстуру, делая ее прозрачной. Обязательно вызывайте этот метод в начале каждого кадра, если не хотите накапливать результат. * draw(gameObject, x, y): Рисует переданный игровой объект в текстуру по указанным локальным координатам (x и y относительно центра текстуры). Объект может быть любым: Image, Sprite, Text, Graphics, TileSprite, ParticleEmitter и т.д. * render(): Финализирует команды отрисовки за кадр. Хотя в некоторых случаях Phaser может вызвать этот метод автоматически, явный вызов является хорошей практикой. * camera: Свойство, предоставляющее доступ к камере, "смотрящей" на содержимое текстуры. Через нее можно управлять rotation, zoom и scroll всей отрендеренной сцены.

// Типичная последовательность в update()
this.rt.clear();
this.rt.draw(someObject, 100, 50);
this.rt.draw(anotherObject, -50, -100);
this.rt.camera.zoom = 1.5; // Приближаем всю сцену в текстуре
this.rt.render();

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

Render Texture — это ваш холст для создания динамических композиций прямо во время выполнения игры. Используя этот инструмент, вы можете значительно оптимизировать рендеринг статичных сложных сцен или реализовать уникальные визуальные эффекты. **Идеи для экспериментов:** 1. Попробуйте анимировать не rotation, а zoom камеры текстуры. 2. Создайте эффект "заморозки времени": нарисуйте в текстуру все объекты сцены в определенный момент, а затем отобразите эту текстуру как статичное изображение. 3. Используйте RenderTexture в качестве маски для другого объекта или для создания динамической мини-карты. 4. Поэкспериментируйте с порядком вызовов .draw() — последний нарисованный объект будет поверх остальных.