О чем этот пример
Динамические текстуры в Phaser — это мощный инструмент для генерации графики на лету. Вместо того чтобы загружать готовые изображения, вы можете создавать уникальные визуальные элементы прямо во время выполнения игры. Это особенно полезно для персонализации аватаров игроков, создания случайных знамён, гербов или интерфейсов, которые должны меняться в зависимости от игрового процесса. В этой статье мы разберём пример, где из частей атласа собирается уникальный баннер для игрока. Вы научитесь создавать динамическую текстуру, наносить на неё штампы с разными эффектами и использовать результат в качестве спрайта.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('banner', 'assets/atlas/banners.png', 'assets/atlas/banners.json');
}
create ()
{
const GetRandom = Phaser.Utils.Array.GetRandom;
// Create our Dynamic Texture which is 512x512 in size
const banner = this.textures.addDynamicTexture('playerBanner', 512, 512);
// Draw a flag to our texture
banner.stamp('banner', 'flag_02_green', 256, 256);
// Draw a random crest - there are 10 available, from Banner_01 to Banner_10
const crests = [ '01', '02', '03', '04', '05', '06', '07', '08', '09', '10' ];
banner.stamp('banner', `Banner_${GetRandom(crests)}`, 256, 256, { alpha: 0.3, blendMode: Phaser.BlendModes.ADD });
// Draw 3 random runes across the flag - these are frames 'Badges_01' to 'Badges_24' in the atlas
const runes = [
'01', '02', '03', '04', '05', '06', '07', '08', '09', '10',
'11', '12', '13', '14', '15', '16', '17', '18', '19', '20',
'21', '22', '23', '24'
];
// By using the 'stamp' config we can scale and offset the frame
banner.stamp('banner', `Badges_${GetRandom(runes)}`, 256, 100, { scale: 0.5, originX: 1 });
banner.stamp('banner', `Badges_${GetRandom(runes)}`, 256, 100, { scale: 0.5, originX: 0.5 });
banner.stamp('banner', `Badges_${GetRandom(runes)}`, 256, 100, { scale: 0.5, originX: 0 });
banner.render();
// Now add the finished banner texture to a Sprite
this.add.sprite(400, 300, 'playerBanner');
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#2d2d6d',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка и создание динамической текстуры
Перед началом работы необходимо загрузить атлас текстур, который будет использоваться как источник графики. В методе preload это делается с помощью this.load.atlas.
Затем в методе create создаётся сама динамическая текстура. Ключевой метод — this.textures.addDynamicTexture. Он принимает уникальный ключ для текстуры и её размеры.
// Загрузка атласа с баннерами и значками
this.load.atlas('banner', 'assets/atlas/banners.png', 'assets/atlas/banners.json');
// Создание пустой динамической текстуры размером 512x512 пикселей
const banner = this.textures.addDynamicTexture('playerBanner', 512, 512);
После вызова addDynamicTexture в памяти создаётся пустое растровое изображение с заданным ключом 'playerBanner'. Объект banner — это экземпляр DynamicTexture, предоставляющий методы для рисования.
Метод stamp: нанесение графики на текстуру
Основной способ заполнить динамическую текстуру — использовать метод stamp. Он «штампует» указанный кадр (фрейм) из атласа или обычной текстуры в заданные координаты на динамической текстуре.
Первым аргументом передаётся ключ текстуры-источника, вторым — имя фрейма. Затем следуют координаты X и Y точки, куда будет помещён штамп. Координаты отсчитываются от левого верхнего угла динамической текстуры.
// Рисуем флаг в центр текстуры (координаты 256, 256)
banner.stamp('banner', 'flag_02_green', 256, 256);
Этот код помещает флаг flag_02_green из атласа 'banner' точно в центр нашей заготовки, так как её размеры 512x512.
Использование конфигурации stamp: прозрачность и режимы наложения
Метод stamp может принимать пятым аргументом объект конфигурации. Он позволяет тонко управлять тем, как штамп будет наложен на текстуру.
В примере для герба используется настройка alpha для полупрозрачности и blendMode для режима смешивания цветов. Phaser.BlendModes.ADD складывает значения цветов штампа и фона, создавая эффект свечения.
// Получаем случайный элемент из массива
const GetRandom = Phaser.Utils.Array.GetRandom;
const crests = [ '01', '02', '03', '04', '05', '06', '07', '08', '09', '10' ];
// Штампуем случайный герб с прозрачностью 0.3 и режимом наложения ADD
banner.stamp('banner', `Banner_${GetRandom(crests)}`, 256, 256, { alpha: 0.3, blendMode: Phaser.BlendModes.ADD });
Важно отметить, что GetRandom — это удобная утилита Phaser для выбора случайного элемента массива. Герб рисуется поверх уже нанесённого флага, но из-за полупрозрачности и режима смешивания они визуально объединяются.
Трансформация штампа: масштаб и точка привязки
Конфигурационный объект stamp также позволяет масштабировать штамп и менять его точку привязки (origin). Точка привязки определяет, какая часть штампа будет совмещена с заданными координатами (X, Y).
В примере три значка (руны) рисуются на одной горизонтальной линии (Y=100), но с разными значениями originX. Это смещает их относительно одной и той же координаты X=256.
const runes = [ '01', '02', '03', ... , '24' ];
// Руна с originX: 1 привязывается правым краем к X=256
banner.stamp('banner', `Badges_${GetRandom(runes)}`, 256, 100, { scale: 0.5, originX: 1 });
// Руна с originX: 0.5 привязывается центром к X=256
banner.stamp('banner', `Badges_${GetRandom(runes)}`, 256, 100, { scale: 0.5, originX: 0.5 });
// Руна с originX: 0 привязывается левым краем к X=256
banner.stamp('banner', `Badges_${GetRandom(runes)}`, 256, 100, { scale: 0.5, originX: 0 });
Параметр scale: 0.5 уменьшает размер значка вдвое. Благодаря разным originX, три значка выстраиваются в ряд, центрируясь вокруг одной точки.
Финализация текстуры и её использование
Динамическая текстура работает как холст: вы можете наносить на неё множество штампов, и они будут накапливаться в буфере. Однако чтобы эти изменения стали видимыми для рендерера Phaser, необходимо явно вызвать метод render().
После этого текстуру можно использовать как любую другую, загруженную через preload. Она доступна по ключу, который был указан при создании.
// Применяем все выполненные операции рисования
banner.render();
// Создаём спрайт, используя нашу готовую динамическую текстуру 'playerBanner'
this.add.sprite(400, 300, 'playerBanner');
Теперь спрайт с координатами (400, 300) отобразит собранный нами уникальный баннер. Если бы мы создали несколько таких текстур с разными ключами, то могли бы использовать их для разных игровых объектов.
Что попробовать дальше
Динамические текстуры открывают огромные возможности для процедурной генерации графики прямо в игре. Вы можете создавать уникальные комбинации из готовых элементов, применяя к ним трансформации и эффекты наложения.
Для экспериментов попробуйте:
1. Генерировать текстуры для тайловых карт или разрушаемого окружения.
2. Создавать динамические элементы интерфейса, которые меняются в реальном времени.
3. Комбинировать метод stamp с рисованием примитивов (линий, фигур) через графический контекст динамической текстуры.
4. Кэшировать созданные текстуры, чтобы использовать их повторно и оптимизировать производительность.
