О чем этот пример
Создание игр с большим количеством визуальных объектов — это постоянная битва за производительность. Когда на экране нужно отобразить сотни или тысячи частиц, звёзд, пуль или тайлов, стандартное создание игровых объектов через `this.add.image` может привести к резкому падению FPS. В этой статье мы разберём пример использования `Blitter` — мощного и легковесного объекта Phaser, предназначенного для высокопроизводительного пакетного отображения спрайтов из одного атласа. Вы узнаете, как с его помощью можно мгновенно создавать тысячи спрайтов, реагирующих на действия игрока, и не беспокоиться о производительности.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
let total;
let blitter;
let text;
let frames = [];
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('blocks', 'assets/atlas/isoblocks.png', 'assets/atlas/isoblocks.json');
}
create ()
{
frames = this.textures.get('blocks').getFrameNames();
total = 230;
blitter = this.add.blitter(0, 0, 'blocks', 'block-000');
text = this.add.text(10, 0, 'Total: 230', { font: '16px Courier', fill: '#00ff00' });
for (var i = 0; i < 230; i++)
{
blitter.create(Phaser.Math.Between(0, 1020), Phaser.Math.Between(16, 760), frames[i]);
}
}
update ()
{
if (this.input.activePointer.isDown)
{
for (var i = 0; i < 230; i++)
{
blitter.create(Phaser.Math.Between(0, 1020), Phaser.Math.Between(16, 760), frames[i]);
}
total += 230;
text.setText('Total: ' + total);
}
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
batchSize: 8000,
scene: Example
};
const game = new Phaser.Game(config);
Что такое Blitter и зачем он нужен?
Blitter (от «bit blitter») — это низкоуровневый объект Phaser, предназначенный для максимально быстрого вывода на экран множества спрайтов (или их частей) из одного текстура-атласа. В отличие от обычных Image или Sprite, объекты Blitter не являются полноценными игровыми объектами с физикой, анимацией или сложной логикой обновления. Они представляют собой команды на отрисовку (бобы).
Их главная цель — эффективность. Blitter рисует все свои бобы за один проход рендерера (draw call), что критически важно для производительности при работе с тысячами элементов. Это идеальный инструмент для:
* Систем частиц (взрывы, дождь, звёзды).
* Статических или редко меняющихся скоплений объектов (тайловый фон, трава).
* Эффектов, где нужно быстро создавать и удалять много визуалов, как в нашем примере.
// Создание основного объекта Blitter в начале сцены
blitter = this.add.blitter(0, 0, 'blocks', 'block-000');
Вызов this.add.blitter(x, y, textureKey, frameKey) создаёт контейнер. Первые два аргумента — его начальная позиция. 'blocks' — ключ загруженного атласа, а 'block-000' — ключ фрейма, который будет использован по умолчанию, если не указать другой при создании боба.
Анализ кода: подготовка и начальное заполнение
Сцена начинается с загрузки текстуры-атласа isoblocks.png с его JSON-описанием. В методе create() происходит основная настройка.
// Получаем массив имён всех фреймов из атласа 'blocks'
frames = this.textures.get('blocks').getFrameNames();
Здесь this.textures.get('blocks') получает ссылку на загруженный атлас, а .getFrameNames() возвращает массив строк с именами всех доступных в нём фреймов (например, ['block-000', 'block-001', ...]). Этот массив понадобится для случайного выбора спрайтов.
// Инициализация: создаём 230 спрайтов в случайных местах
for (var i = 0; i < 230; i++)
{
blitter.create(Phaser.Math.Between(0, 1020), Phaser.Math.Between(16, 760), frames[i]);
}
Метод blitter.create(x, y, frame) — это сердце процесса. Он не создаёт игровой объект, а добавляет в Blitter новый «боб» — инструкцию «нарисуй этот кадр frame в координатах x, y». Phaser.Math.Between генерирует случайные координаты в пределах сцены (с небольшим отступом от верха для текста). Каждому новому бобу назначается следующий фрейм из массива frames.
Динамическое создание объектов по клику мыши
Вся магия динамического добавления происходит в методе update(), который вызывается каждый кадр.
if (this.input.activePointer.isDown)
{
for (var i = 0; i < 230; i++)
{
blitter.create(Phaser.Math.Between(0, 1020), Phaser.Math.Between(16, 760), frames[i]);
}
total += 230;
text.setText('Total: ' + total);
}
Проверка this.input.activePointer.isDown возвращает true, пока кнопка мыши (или касание) удерживается. Если это так, за один кадр создаётся сразу 230 новых бобов со случайными координатами.
**Ключевой момент производительности:** Несмотря на то, что за один клик может быть добавлено 10 000+ бобов, Blitter продолжит отрисовывать их все за один draw call. Счётчик total и текстовый объект text обновляются, чтобы визуализировать рост количества объектов. Отсутствие лагов демонстрирует эффективность подхода.
Важная настройка конфигурации: batchSize
Чтобы Blitter мог работать с тысячами объектов, нужно правильно настроить рендерер. Обратите внимание на поле batchSize в конфиге игры.
const config = {
type: Phaser.WEBGL, // Используется WebGL-рендерер
parent: 'phaser-example',
batchSize: 8000, // Критически важный параметр!
scene: Example
};
Параметр batchSize определяет размер одного пакета (батча) для отрисовки. По умолчанию он гораздо меньше. Значение 8000 говорит рендереру: «Ты можешь отрисовать до 8000 спрайтов за один раз, прежде чем потребуется начать новый пакет». Для сцен, где используется Blitter с огромным количеством бобов, увеличение этого значения необходимо для поддержания производительности. Без этого даже эффективный Blitter будет вынужден делать лишние операции.
Ограничения Blitter и когда его не стоит использовать
Blitter — это специализированный инструмент. Его скорость достигается за счёт отказа от многих возможностей обычных игровых объектов.
* **Нет трансформаций:** Бобы Blitter не поддерживают масштабирование (scale), вращение (rotation) или изменение прозрачности (alpha) на индивидуальной основе. Эти свойства задаются для всего объекта Blitter целиком.
* **Нет анимации:** Боб — это статичный снимок одного кадра. Для анимации вам пришлось бы вручную менять кадр у каждого боба в update(), что снижает преимущество.
* **Нет физики и коллизий:** С бобами не работает Arcade или Matter Physics. Они существуют только для отрисовки.
* **Один атлас:** Все бобы в одном Blitter должны использовать фреймы из одного и того же текстура-атласа.
Используйте Blitter для статичных или массовых визуальных эффектов. Для интерактивных, анимированных или сложных объектов по-прежнему нужны обычные Sprite или Image.
Что попробовать дальше
Blitter — это секретное оружие Phaser для задач, требующих отрисовки огромного количества однотипных спрайтов. Как показал пример, с его помощью можно мгновенно добавлять тысячи объектов, просто удерживая кнопку мыши, без потери в плавности кадров.
**Идеи для экспериментов:**
1. Замените атлас блоков на атлас со звёздами и создайте Blitter в create(), чтобы мгновенно сгенерировать фон ночного неба с тысячами звёзд.
2. Попробуйте управлять свойствами всего Blitter целиком: измените blitter.alpha для эффекта затухания или blitter.tint для цветного фильтра.
3. Реализуйте простую систему частиц: создавайте бобы в одной точке со случайным вектором скорости и в цикле update() двигайте их, изменяя координаты каждого боба через его свойство bob.x и bob.y, а «умершие» частицы удаляйте с помощью bob.destroy().
