О чем этот пример
Производительность — ключевой фактор в разработке игр. Когда на сцене сотни или тысячи объектов, традиционные методы отрисовки могут стать узким местом. В этой статье мы разберем пример использования объекта `Blitter` в Phaser — мощного инструмента для массового вывода одинаковых спрайтов с минимальными накладными расходами. Вы узнаете, как эффективно управлять большим количеством графических элементов, отслеживать их количество в реальном времени и динамически добавлять новые по запросу пользователя, что особенно полезно для частиц, фоновых элементов или простых игр с огромным количеством сущностей.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
let total;
let blitter;
let text;
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('atlas', 'assets/atlas/megaset-0.png', 'assets/atlas/megaset-0.json');
}
create ()
{
total = 250;
blitter = this.add.blitter(0, 0, 'atlas', 'chunk');
text = this.add.text(10, 10, 'Total: 250', { font: '64px Courier', fill: '#00ff00' });
for (var i = 0; i < 250; ++i)
{
blitter.create(Phaser.Math.Between(0, 1020), Phaser.Math.Between(0, 764));
}
}
update ()
{
if (this.input.activePointer.isDown)
{
for (var i = 0; i < 250; ++i)
{
blitter.create(Phaser.Math.Between(0, 1020), Phaser.Math.Between(0, 764));
}
total += 250;
text.setText('Total: ' + total);
}
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что такое Blitter и зачем он нужен?
Blitter — это специальный игровой объект в Phaser, предназначенный для высокопроизводительного вывода множества копий одного спрайта (или кадра из атласа). В отличие от создания множества отдельных объектов Image или Sprite, которые несут накладные расходы на управление и рендеринг, Blitter работает по принципу пакетного рендеринга (batch rendering).
Он берет один источник (текстуру или область в атласе) и рисует его множество раз в разных позициях за один проход через графический конвейер. Это резко снижает количество вызовов отрисовки (draw calls) и повышает FPS, особенно в WebGL-режиме.
В нашем примере в качестве источника используется кадр с именем 'chunk' из загруженного атласа 'atlas'.
Структура сцены: Загрузка и инициализация
Пример построен как стандартная сцена Phaser с методами preload, create и update.
В preload загружается атлас текстур. Атлас — это большое изображение, содержащее множество мелких спрайтов, и JSON-файл с координатами этих спрайтов. Использование атласов также оптимизирует рендеринг, уменьшая переключения текстур.
this.load.atlas('atlas', 'assets/atlas/megaset-0.png', 'assets/atlas/megaset-0.json');
В методе create происходит ключевая настройка:
1. Создается объект Blitter в точке (0, 0), использующий атлас 'atlas' и кадр 'chunk'.
2. Создается текстовый объект для отображения счетчика.
3. Цикл создает начальную партию из 250 спрайтов в случайных позициях на экране.
blitter = this.add.blitter(0, 0, 'atlas', 'chunk');
text = this.add.text(10, 10, 'Total: 250', { font: '64px Courier', fill: '#00ff00' });
for (var i = 0; i < 250; ++i) {
blitter.create(Phaser.Math.Between(0, 1020), Phaser.Math.Between(0, 764));
}
Метод blitter.create(x, y) не создает новый независимый объект, а добавляет новую команду на отрисовку (Bob) в пул Blitter-а. Позиции задаются с помощью Phaser.Math.Between.
Интерактивность и динамическое добавление
Логика в методе update делает пример интерактивным. При каждом нажатии кнопки мыши (проверка через this.input.activePointer.isDown) выполняется блок кода.
if (this.input.activePointer.isDown) {
for (var i = 0; i < 250; ++i) {
blitter.create(Phaser.Math.Between(0, 1020), Phaser.Math.Between(0, 764));
}
total += 250;
text.setText('Total: ' + total);
}
Здесь важно понимать:
- Каждое нажатие добавляет сразу 250 новых спрайтов. Это демонстрирует эффективность Blitter для мгновенного создания большого количества объектов.
- Глобальная переменная total отслеживает общее количество созданных спрайтов.
- Текстовый объект text обновляется, чтобы отображать актуальный счетчик. Это дает визуальную обратную связь и позволяет наблюдать за ростом числа объектов в реальном времени.
- Несмотря на добавление тысяч спрайтов, производительность остается высокой, так как все они рендерятся одним объектом.
Конфигурация игры и WebGL
Ключевой момент конфигурации — использование Phaser.WEBGL в качестве рендерера.
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
scene: Example
};
Blitter максимально раскрывает свой потенциал именно в WebGL-режиме, где пакетная отрисовка реализована наиболее эффективно. В Canvas-режиме прирост производительности также будет, но не столь значительный. Указание parent связывает экземпляр игры с HTML-элементом на странице.
Практические выводы и ограничения
Blitter — идеальный выбор для:
- Систем частиц (звезды, снег, искры, брызги).
- Статических или редко меняющихся фоновых элементов (трава, камни, листья).
- Простых игр, где множество одинаковых объектов (например, «космические захватчики»).
**Важные ограничения:**
1. Все спрайты в одном Blitter используют один источник (текстуру или кадр атласа).
2. Отдельные спрайты (Bob) внутри Blitter не являются самостоятельными игровыми объектами. У них нет тела для физики, компонентов или глубокой индивидуальной анимации. Ими можно управлять только через методы Blitter (изменять позицию, фрейм, видимость, оттенок).
3. Для управления каждым спрайтом по отдельности (например, для проверки столкновений) нужно вести внешнюю логику и собственные структуры данных.
В примере спрайты создаются, но не удаляются. В реальном проекте для долгой работы нужно реализовать пулинг (переиспользование Bob через blitter.create и bob.visible) или их удаление.
Что попробовать дальше
Использование Blitter — это проверенный способ резко повысить производительность в Phaser-проектах, где требуется много однотипных спрайтов. Пример наглядно показывает, как легко создавать и управлять тысячами графических элементов с минимальным ударом по FPS.
**Идеи для экспериментов:**
1. Измените исходный кадр в blitter.create на другой из атласа.
2. Реализуйте удаление спрайтов (bob.destroy()) при клике на них или за пределами экрана.
3. Добавьте простую анимацию, циклически меняя свойство bob.frame для всех или некоторых спрайтов в update.
4. Создайте несколько разных Blitter-ов с разными текстурами и сравните производительность с одним, содержащим смесь спрайтов.
5. Проведите бенчмарк: сравните FPS при отрисовке 5000 объектов через Blitter и через 5000 отдельных объектов Sprite.
