О чем этот пример
При создании игр часто возникает необходимость отображать сотни или тысячи объектов одновременно, например, частицы или фон из тайлов. Прямая отрисовка каждого спрайта может сильно нагружать производительность. В этой статье мы разберем мощный прием в Phaser, который позволяет превратить множество спрайтов в один статичный слой и затем манипулировать им как единым целым, значительно экономя ресурсы. Мы изучим пример, который создает 512 спрайтов, но отрисовывает их не напрямую на сцену, а на специальную текстуру. Это позволяет вращать всю массу объектов одной операцией, что идеально подходит для фонов, сложных частиц или статичных декораций.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
blitter;
rt;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('duck', 'assets/sprites/darkwing_crazy.png');
}
create ()
{
this.blitter = this.add.blitter(0, 0, 'duck').setVisible(false);
for (let i = 0; i < 512; i++)
{
const x = Phaser.Math.Between(0, 800);
const y = Phaser.Math.Between(0, 600);
this.blitter.create(x, y);
}
this.rt = this.add.renderTexture(400, 300, 800, 600);
}
update ()
{
const rt = this.rt;
rt.camera.rotation -= 0.01;
rt.clear()
.draw(this.blitter, 0, 0)
.render();
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и создание Blitter
Ключевым объектом для работы с множеством спрайтов является Blitter. В отличие от обычного Image, Blitter предназначен для высокопроизводительного отображения множества копий одного и того же изображения (спрайта).
В методе preload загружается текстура утки. В create мы создаем сам Blitter, позиционируя его в точке (0,0) и указывая ключ текстуры. Важный шаг — сразу же скрыть его с помощью .setVisible(false), так как мы не будем рисовать его на самой сцене.
this.blitter = this.add.blitter(0, 0, 'duck').setVisible(false);
Далее, в цикле создается 512 экземпляров (бобов) этого спрайта в случайных позициях на экране. Каждый вызов this.blitter.create(x, y) добавляет новый отображаемый боб в коллекцию.
for (let i = 0; i < 512; i++)
{
const x = Phaser.Math.Between(0, 800);
const y = Phaser.Math.Between(0, 600);
this.blitter.create(x, y);
}
Создание Render Texture
RenderTexture (RT) — это специальный тип текстуры, на который можно рисовать другие игровые объекты, как на холст. Её можно рассматривать как динамически создаваемое изображение.
Мы создаем RT размером 800x600 и центруем его на сцене (координаты 400, 300). Теперь у нас есть невидимый холст, готовый принять отрисовку.
this.rt = this.add.renderTexture(400, 300, 800, 600);
По умолчанию RenderTexture добавляется на сцену и видна. Весь последующий рендеринг будет происходить на эту текстуру, а она, в свою очередь, будет отображаться как единый объект.
Цикл рендеринга и манипуляции
Вся магия происходит в методе update. Вместо того чтобы обновлять 512 отдельных спрайтов, мы работаем только с одной RenderTexture.
Сначала мы вращаем виртуальную камеру самой RT. Это изменяет точку обзора для всего, что будет нарисовано на этой текстуре.
rt.camera.rotation -= 0.01;
Затем выполняется трехэтапный процесс:
1. rt.clear() — полностью очищает текстуру от предыдущего кадра.
2. rt.draw(this.blitter, 0, 0) — рисует все 512 бобов из Blitter на текстуру, используя её систему координат с учетом поворота камеры. Позиция (0,0) здесь — это левый верхний угол самой RT.
3. .render() — финализирует команды отрисовки и делает результат видимым.
rt.clear()
.draw(this.blitter, 0, 0)
.render();
Таким образом, каждый кадр мы заново рисуем всю сложную композицию спрайтов на текстуру, но делаем это одним вызовом, а вращение применяется ко всему слою сразу.
Конфигурация игры
Конфигурация игры стандартна для примера. Важно убедиться, что размеры холста (width, height) соответствуют или превышают размеры создаваемой RenderTexture, чтобы она поместилась на экране.
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Преимущества подхода
Использование связки Blitter + RenderTexture дает два ключевых преимущества:
* **Производительность:** Отрисовка 500+ объектов через прямое добавление на сцену создает большую нагрузку на рендерер. Blitter оптимизирован для батчинга одинаковых спрайтов, а RenderTexture сводит их отрисовку к одному вызову. После того как текстура создана, движок работает с ней как с одним изображением.
* **Групповые трансформации:** Вы можете применять такие эффекты, как вращение, масштабирование или альфа-прозрачность, ко всей группе спрайтов одновременно через свойства камеры или самой RenderTexture (например, rt.setAlpha()), что невозможно сделать эффективно для каждого спрайта по отдельности.
Что попробовать дальше
Render Texture — это мощный инструмент для оптимизации и создания сложных визуальных эффектов в Phaser. Он позволяет "запекать" множество объектов в одно изображение, над которым затем легко манипулировать.
**Идеи для экспериментов:**
1. Попробуйте двигать или масштабировать камеру rt.camera вместо вращения.
2. Нарисуйте на одну Render Texture не только Blitter, но и другие объекты, например, группы (this.add.group).
3. Используйте несколько Render Texture для создания многослойных эффектов с разной скоростью параллакса.
4. Поэкспериментируйте с методами rt.save() и rt.restore() для более сложных пайплайнов отрисовки.
