О чем этот пример

Создание игр с большим количеством визуальных объектов — это постоянная битва за производительность. Когда на экране нужно отобразить сотни или тысячи частиц, звёзд, пуль или тайлов, стандартное создание игровых объектов через `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().