О чем этот пример
Render Texture — это мощный инструмент в Phaser 3 для создания динамических текстур, на которые можно рисовать другие игровые объекты, как на холст. Эта техника часто используется для создания эффектов, мини-карт, динамических масок или сложных композиций, которые требуют частого обновления. В этой статье мы разберем, как не только создать такую текстуру и наполнить её разными типами объектов, но и как динамически изменять её размер во время выполнения игры, что открывает возможности для адаптивных интерфейсов или спецэффектов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
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',
advance: 1000
}).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);
this.rt.draw(this.graphics, 0, 0);
this.rt.draw(this.bob, 200, 200);
this.rt.draw(this.tilesprite, 200, 200);
this.rt.draw(this.blitter, 0, 0);
this.rt.draw(this.text, 100, 100);
this.rt.draw(this.bob, 300, 300);
this.rt.draw(this.bob, 400, 400);
this.rt.draw(this.text, 300, 200);
this.rt.draw(this.particles, 300, 100);
this.rt.draw(this.bitmaptext, 200, 100);
this.rt.render();
this.input.on('pointerdown', () =>
{
this.rt.resize(400, 300);
this.rt.draw(this.graphics, 0, 0);
this.rt.draw(this.bob, 200, 200);
this.rt.draw(this.tilesprite, 200, 200);
this.rt.draw(this.blitter, 0, 0);
this.rt.draw(this.text, 100, 100);
this.rt.draw(this.bob, 300, 300);
this.rt.draw(this.bob, 400, 400);
this.rt.draw(this.text, 300, 200);
this.rt.draw(this.particles, 300, 100);
this.rt.draw(this.bitmaptext, 200, 100);
this.rt.render();
});
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Что такое Render Texture и зачем она нужна
Объект RenderTexture (или RenderTexture в более новых версиях Phaser) представляет собой особый тип текстуры, которая действует как буфер для рендеринга. Вы можете «рисовать» на ней другие игровые объекты, используя метод draw(). Результат сохраняется в текстуре, которую затем можно использовать как изображение для спрайта или отображать самостоятельно.
Этот подход эффективен, когда нужно объединить несколько объектов в один статичный или редко меняющийся кадр, или когда необходимо применить фильтры или трансформации ко всей группе объектов сразу. В нашем примере мы создаем текстуру, а затем динамически меняем её размер по клику мыши.
Подготовка и создание объектов для отрисовки
Перед созданием RenderTexture необходимо подготовить объекты, которые будут на неё нарисованы. В методе create() сцены создаются различные типы объектов Phaser, но с важным нюансом: они изначально невидимы (setVisible(false)). Это нужно потому, что мы не хотим, чтобы они отображались на основном экране сами по себе — их конечная цель быть отрендеренными внутрь текстуры.
Создаются следующие объекты:
- Текстовый объект (`this.add.text`).
- Изображение (`this.add.image`).
- Система частиц (`this.add.particles`).
- Bitmap-текст (`this.add.bitmapText`).
- Графический объект (`this.add.graphics`) с нарисованной фигурой.
- TileSprite.
- Blitter-объект.
this.bob = this.add.image(0, 0, 'bunny').setName('bob').setVisible(false);
this.graphics = this.add.graphics().setVisible(false);
this.bitmaptext = this.add.bitmapText(0, 0, 'desyrel', 'PHASER 3\nRender Texture').setVisible(false);
Создание Render Texture и первоначальная отрисовка
После подготовки объектов создается сама RenderTexture. Её конструктор принимает координаты (x, y), ширину и высоту. В данном случае текстура создается размером 800x600 пикселей в центре экрана (координаты 400, 300).
Затем, используя метод draw(), мы помещаем в текстуру все подготовленные объекты. Важно понимать, что координаты внутри метода draw() отсчитываются от верхнего левого угла самой RenderTexture, а не от глобальных координат сцены. Это позволяет точно позиционировать объекты внутри текстуры.
Метод render() вызывается для того, чтобы все нарисованные команды были применены и текстура стала видимой на экране.
this.rt = this.add.renderTexture(400, 300, 800, 600);
this.rt.draw(this.graphics, 0, 0);
this.rt.draw(this.bob, 200, 200);
// ... другие вызовы draw
this.rt.render();
Динамическое изменение размера текстуры
Самая интересная часть примера — это изменение размера RenderTexture «на лету». По клику мыши (событие pointerdown) вызывается метод resize() у объекта this.rt. Этот метод принимает новую ширину и высоту. В примере размер уменьшается с 800x600 до 400x300 пикселей.
**Важный момент:** После изменения размера текстура очищается. Все ранее нарисованные объекты пропадают. Поэтому сразу после вызова resize() необходимо заново выполнить всю последовательность вызовов draw() для заполнения текстуры актуальным содержимым, а затем снова вызвать render(). Это поведение отличает resize() от простого масштабирования — происходит пересоздание внутреннего буфера текстуры.
this.input.on('pointerdown', () => {
this.rt.resize(400, 300); // Изменяем размер буфера
// Повторяем все команды рисования для нового размера
this.rt.draw(this.graphics, 0, 0);
this.rt.draw(this.bob, 200, 200);
// ...
this.rt.render(); // Применяем изменения
});
Практические применения и особенности
Динамический resize() может быть полезен в нескольких сценариях:
1. **Создание адаптивных элементов интерфейса**, размер которых зависит от действий игрока или размера окна.
2. **Эффекты перехода или трансформации**, где текстура должна плавно сжиматься или растягиваться.
3. **Оптимизация:** можно уменьшить размер текстуры для рендеринга менее детализированных версий сложных композиций (например, для мини-карты в углу экрана).
**На что обратить внимание:**
- После resize() требуется полная перерисовка содержимого.
- Координаты для draw() после ресайза должны быть пересчитаны, если нужна относительная привязка. В примере используются абсолютные значения, поэтому при уменьшении текстуры в 2 раза объекты будут нарисованы в тех же координатах относительно нового, меньшего холста, что визуально «приблизит» и сместит композицию.
- Изменение размера — операция, требующая пересоздания внутреннего WebGL-текстура, поэтому частые вызовы resize() могут сказаться на производительности.
Что попробовать дальше
Render Texture в Phaser 3 — это гибкий инструмент для композитинга, а возможность динамического изменения её размера методом resize() добавляет новый уровень контроля. Помните, что после изменения размера текстура очищается, и её содержимое нужно перерисовать.
**Идеи для экспериментов:**
1. Свяжите размер RenderTexture с движением мыши или жестами на сенсорном экране.
2. Реализуйте плавную анимацию изменения размера с помощью Tween, вызывая resize() на каждом кадре с новыми значениями.
3. Используйте текстуру с измененным размером в качестве маски для другого объекта.
4. Попробуйте не перерисовывать все объекты после resize(), а сохранить оригинальную текстуру в другой RenderTexture и нарисовать её с масштабированием в новую, меньшую текстуру, используя параметры draw() для масштаба.
