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