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

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

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

Живой запуск

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

Исходный код


// Render a scene to a DynamicTexture.
class Example extends Phaser.Scene
{
    displayTexture;
    container;

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

    create ()
    {
        const sceneRenderTexture = this.add.renderTexture(640, 360, 1280, 720);

        // Texture to copy the scene render, preventing feedback loops.
        const displayTexture = this.add.renderTexture(0, 0, 1280, 720)
            .setScale(0.7)
            .setTint(0xf0f8ff);
        this.displayTexture = displayTexture;

        const monitor = this.add.image(0, 0, 'monitor');

        const container = this.add.container(
            640, 360,
            [ displayTexture, monitor ]
        );
        this.container = container;

        sceneRenderTexture
            .draw(container)
            .preserve(true)
            .setRenderMode('redraw')
            .saveTexture('sceneTexture');

        displayTexture
            .draw('sceneTexture', sceneRenderTexture.x, sceneRenderTexture.y)
            .preserve(true)
            .setRenderMode('all');
    }

    update (time, delta)
    {
        this.displayTexture
            .setRotation(Math.sin(time / 1000) * 0.1)
            .setPosition(Math.sin(time / 1111) * 10, Math.cos(time / 987) * 10)
            .setScale(0.7 + Math.sin(time / 1234) * 0.1);

        this.container
            .setRotation(Math.sin(time / 1010) * 0.1);
    }
}

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

const game = new Phaser.Game(config);

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

RenderTexture (или Phaser.GameObjects.RenderTexture) — это специальный игровой объект, который представляет собой холст в памяти. На этот холст можно отрисовать другие игровые объекты, группы или даже другую текстуру. Результат становится новой текстурой, которую можно применять к другим объектам.

Основная польза в оптимизации и создании сложных эффектов. Вместо того чтобы вручную управлять десятками объектов для создания эффекта «экрана», вы рендерите их все один раз в текстуру, а затем работаете уже с этой одной текстурой. Это особенно полезно для: * Создания мини-карт или порталов. * Реализации «матричных» или зеркальных эффектов. * Оптимизации отрисовки статичных или редко меняющихся композиций.

В нашем примере мы создадим текстуру, содержащую изображение монитора и другую, динамически меняющуюся текстуру внутри него.

Подготовка сцены и создание текстур

Первым делом загружаем спрайт монитора. В методе create() создаем две основные текстуры.

sceneRenderTexture — это наша целевая текстура, холст, на который будет рендериться финальная сцена. Мы размещаем её по центру экрана (640x360) с размерами 1280x720.

displayTexture — это текстура-контейнер, которая будет отображаться *внутри* монитора. Ей задается небольшой тинт (оттенок) для визуального отделения.

const sceneRenderTexture = this.add.renderTexture(640, 360, 1280, 720);
const displayTexture = this.add.renderTexture(0, 0, 1280, 720)
    .setScale(0.7)
    .setTint(0xf0f8ff);
this.displayTexture = displayTexture;

Композиция объектов и предотвращение петли обратной связи

Чтобы собрать монитор и текстуру внутри него в один управляемый объект, используем Container. Контейнеры в Phaser позволяют группировать объекты и применять трансформации (позиция, вращение, масштаб) ко всей группе сразу.

Ключевой момент — предотвращение петли обратной связи. Если бы мы рендерили sceneRenderTexture саму в себя, это привело бы к бесконечной рекурсии и ошибке. Алгоритм такой: 1. Создаем container, который содержит displayTexture и спрайт monitor. 2. Рендерим этот container в sceneRenderTexture и сохраняем результат под именем 'sceneTexture'. 3. Теперь displayTexture (та, что внутри монитора) берет и рисует на себе сохраненную 'sceneTexture'. Поскольку sceneTexture — это снимок контейнера *на предыдущем шаге*, мы избегаем рекурсии.

const container = this.add.container(640, 360, [ displayTexture, monitor ]);
this.container = container;

sceneRenderTexture
    .draw(container)
    .preserve(true)
    .setRenderMode('redraw')
    .saveTexture('sceneTexture');

displayTexture
    .draw('sceneTexture', sceneRenderTexture.x, sceneRenderTexture.y)
    .preserve(true)
    .setRenderMode('all');

Метод .preserve(true) указывает, что содержимое текстуры должно сохраняться между кадрами, а не очищаться. setRenderMode управляет тем, как происходит отрисовка ('redraw' — перерисовывать всё, 'all' — отрисовывать все объекты).

Анимация и "оживление" эффекта

Вся магия происходит в методе update(). Здесь мы анимируем обе текстуры, создавая иллюзию динамичной сцены внутри монитора.

this.displayTexture (та, что внутри) получает плавно меняющиеся вращение, позицию и масштаб на основе времени игры (time). Использование разных делителей для sin и cos создает сложные, не повторяющиеся траектории.

this.container также немного вращается, добавляя эффект дрожания или покачивания всего монитора.

this.displayTexture
    .setRotation(Math.sin(time / 1000) * 0.1)
    .setPosition(Math.sin(time / 1111) * 10, Math.cos(time / 987) * 10)
    .setScale(0.7 + Math.sin(time / 1234) * 0.1);

this.container
    .setRotation(Math.sin(time / 1010) * 0.1);

Поскольку displayTexture каждый кадр перерисовывает в себя sceneTexture (которая содержит контейнер с уже измененной displayTexture), мы получаем бесконечную рекурсивную анимацию с задержкой в один кадр. Это создает знаменитый «эффект Droste» или «картинка в картинке».

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

Использование RenderTexture для рендеринга сцены в текстуру открывает двери к созданию сложных визуальных эффектов с относительно простым кодом. Вы освоили базовый паттерн: создание целевой текстуры, композиция объектов, сохранение промежуточного результата и его повторное использование для избегания рекурсии. Для экспериментов попробуйте: 1. Добавить больше объектов в контейнер (частицы, текст, другие спрайты). 2. Изменить режим рендера (setRenderMode) на 'skip' или 'erase' для других визуальных результатов. 3. Использовать RenderTexture для создания мини-игры на игровом терминале или работающего телевизора в сцене. 4. Поэкспериментировать с setAlpha или setBlendMode для текстур, чтобы создать эффект призрачного отражения.