О чем этот пример
При создании игр часто возникает необходимость отображать сотни или тысячи объектов одновременно — это могут быть частицы, звёзды на фоне или армия противников. Рендеринг каждого такого объекта как отдельного спрайта крайне затратен для производительности. Класс `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. Используйте атлас с разными кадрами для создания разнообразной толпы или стаи существ, где каждый член слоя выглядит уникально, но все они анимируются по общим законам.
