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

При создании игр часто возникает необходимость отображать сотни или тысячи объектов одновременно — это могут быть частицы, звёзды на фоне или армия противников. Рендеринг каждого такого объекта как отдельного спрайта крайне затратен для производительности. Класс `SpriteGPULayer` в Phaser 3 решает эту проблему, позволяя создавать и анимировать тысячи спрайтов с минимальными вычислительными затратами, используя возможности GPU. Эта статья покажет, как использовать этот мощный инструмент для создания сложных визуальных эффектов и оптимизации вашей игры.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('sprites', 'assets/atlas/tp3test.png', 'assets/atlas/tp3test.json');
    }

    create ()
    {
        const count = 1024;

        const layer = this.add.spriteGPULayer('sprites', 1 + count);
        console.log(layer);

        const template = {
            frame: 'bunny.png',
            x: {
                base: 550,
                ease: 'Linear',
                amplitude: -100,
                duration: 500
            },
            y: {
                base: 450,
                ease: 'Quad.easeOut',
                amplitude: -100,
                duration: 250
            },
            rotation: {
                base: -0.25,
                ease: 'Linear',
                amplitude: 0.5,
                duration: 500
            },
            scaleX: {
                base: 1.1,
                ease: 'Cubic.easeOut',
                amplitude: -0.1,
                duration: 250
            },
            scaleY: {
                base: 0.8,
                ease: 'Cubic.easeOut',
                amplitude: 0.2,
                duration: 250
            },
            originY: 1
        };

        layer.addMember(template);

        const frameNames = layer.texture.getFrameNames();

        for (let i = 0; i < count; i++)
        {
            const phase = Math.random() * 1000;

            template.frame = Phaser.Utils.Array.GetRandom(frameNames);
            template.x.base = Math.random() * 800;
            template.y.base = 600 - Math.random() * 100;
            template.x.delay = phase;
            template.y.delay = phase;
            template.rotation.delay = phase;
            template.scaleX.delay = phase;
            template.scaleY.delay = phase;

            layer.addMember(template);
        }
    }
}

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example,
    backgroundColor: '#808080'
};

const game = new Phaser.Game(config);

Что такое SpriteGPULayer?

SpriteGPULayer — это специальный игровой объект, предназначенный для эффективного отображения множества спрайтов, использующих одну текстуру (или атлас). В отличие от обычных спрайтов, которые рендерятся по отдельности, все спрайты в слое отрисовываются за один draw call на GPU. Это кардинально снижает нагрузку на процессор и позволяет работать с тысячами объектов без падения FPS.

Слой создаётся с помощью метода this.add.spriteGPULayer(). Первый аргумент — ключ текстуры, второй — максимальное количество спрайтов, которые слой сможет содержать.

const layer = this.add.spriteGPULayer('sprites', 1025);

В примере мы создаём слой для текстуры 'sprites' с запасом на 1025 спрайтов (1 шаблонный + 1024 уникальных).

Создание шаблона для спрайтов

Все спрайты, добавляемые в слой, основаны на шаблоне — объекте, который описывает начальные свойства и анимации. Шаблон определяет не только статические параметры вроде кадра (frame) или точки опоры (originY), но и динамические трансформации.

Каждое свойство трансформации (например, `x,y,rotation`) может быть описано объектом, содержащим: - base: базовое значение. - amplitude: амплитуда изменения. - duration: длительность цикла анимации. - ease: функция плавности (easing). - delay: задержка перед началом анимации.

Вот шаблон из примера, который создаёт прыгающего зайца:

const template = {
    frame: 'bunny.png',
    x: {
        base: 550,
        ease: 'Linear',
        amplitude: -100,
        duration: 500
    },
    // ... другие свойства (y, rotation, scaleX, scaleY)
    originY: 1 // Точка опоры внизу спрайта
};

Свойство originY: 1 означает, что точка вращения и масштабирования находится в нижней части спрайта, что типично для объектов, стоящих на поверхности.

Заполнение слоя спрайтами

После создания шаблона и слоя, мы добавляем первый спрайт-член с помощью layer.addMember(template). Этот спрайт будет использовать исходный шаблон.

Затем, чтобы создать множество разнообразных спрайтов, мы в цикле модифицируем шаблон перед каждым вызовом addMember. Важно: метод addMember не копирует переданный объект, а читает его текущие значения. Поэтому мы можем переиспользовать один объект template, меняя его свойства на каждой итерации.

Ключевые моменты генерации: 1. Случайный выбор кадра из атласа. 2. Случайное позиционирование по X и Y. 3. Установка случайной фазы (phase) для задержки анимации каждого спрайта. Это делает анимацию несинхронной.

const frameNames = layer.texture.getFrameNames();

for (let i = 0; i < count; i++)
{
    const phase = Math.random() * 1000;
    template.frame = Phaser.Utils.Array.GetRandom(frameNames);
    template.x.base = Math.random() * 800;
    template.y.base = 600 - Math.random() * 100;
    template.x.delay = phase;
    template.y.delay = phase;
    // ... задержка для rotation, scaleX, scaleY
    layer.addMember(template);
}

Таким образом, мы получаем 1024 спрайта, каждый со своей позицией, внешним видом и индивидуальной фазой анимации.

Преимущества и ограничения подхода

**Преимущества:** - **Высокая производительность:** Тысячи спрайтов рендерятся с эффективностью частичной системы (particle system). - **Встроенная анимация:** Анимация свойств (позиция, вращение, масштаб) описывается декларативно и выполняется на GPU. - **Гибкость:** Можно использовать любой кадр из текстуры или атласа.

**Ограничения и важные детали:** - Все спрайты в слое должны использовать **одну и ту же текстуру**. Нельзя смешивать разные загруженные изображения. - Слой работает только в **рендерере WebGL** (тип Phaser.WEBGL в конфиге). - Анимация через шаблон управляется системой SpriteGPULayer автоматически. У вас нет прямого контроля над каждым спрайтом после его добавления (как у обычного Sprite). Этот инструмент идеален для фоновых элементов, частиц и массовок, но не для игровых персонажей, которыми нужно управлять в реальном времени.

Конфигурация игры для использования WebGL:

const config = {
    type: Phaser.WEBGL, // Обязательно WEBGL
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example,
    backgroundColor: '#808080'
};

Что попробовать дальше

SpriteGPULayer — это мощный инструмент Phaser 3 для задач, требующих отображения огромного количества графически похожих объектов. Он переносит вычислительную нагрузку с CPU на GPU, что является ключом к высокой производительности. **Идеи для экспериментов:** 1. Создайте "звёздное небо", где каждая звезда — спрайт с анимацией мерцания (изменение alpha или scale). 2. Смоделируйте падающие листья или снежинки со сложными траекториями, используя несколько свойств анимации одновременно. 3. Используйте атлас с разными кадрами для создания разнообразной толпы или стаи существ, где каждый член слоя выглядит уникально, но все они анимируются по общим законам.