О чем этот пример
В игровой разработке часто требуется генерировать контент на лету. Динамические текстуры 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.image('apple', 'assets/sprites/apple.png');
this.load.glsl('tunnel', 'assets/shaders/tunnel.frag');
}
create ()
{
const texture = this.textures.addDynamicTexture('shaderTexture', 512, 512);
texture.fill(0x000066);
for (let i = 0; i < 64; i++)
{
texture.stamp('apple', null, Phaser.Math.Between(25, 487), Phaser.Math.Between(25, 487));
}
texture.render();
this.add.shader({
name: 'Tunnel',
fragmentKey: 'tunnel',
initialUniforms: {
resolution: [ 800, 600 ],
iChannel0: 0,
alpha: 1,
origin: 2
},
setupUniforms: (setUniform, drawingContext) => {
setUniform('time', this.game.loop.getDuration());
}
}, 400, 300, 800, 600, [ 'shaderTexture' ]);
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Подготовка ассетов и создание динамической текстуры
Код начинается с загрузки необходимых ресурсов: спрайта яблока и GLSL-шейдера из внешних файлов. Ключевой момент происходит в методе create.
Сначала мы создаем пустую текстуру в памяти размером 512x512 пикселя с помощью this.textures.addDynamicTexture. Ей сразу присваивается ID 'shaderTexture', чтобы позже к ней можно было обратиться.
const texture = this.textures.addDynamicTexture('shaderTexture', 512, 512);
Затем текстура заливается сплошным цветом, создавая фон.
texture.fill(0x000066);
Заполнение текстуры спрайтами с помощью метода `stamp`
Динамическая текстура — это холст для рисования. Метод stamp позволяет "штамповать" загруженные изображения прямо на нее. В цикле мы 64 раза размещаем спрайт 'apple' в случайных координатах.
for (let i = 0; i < 64; i++)
{
texture.stamp('apple', null, Phaser.Math.Between(25, 487), Phaser.Math.Between(25, 487));
}
Важно: после всех операций рисования необходимо явно вызвать texture.render(). Без этого вызова изменения не будут применены к текстуре. Этот метод финализирует все операции и делает текстуру готовой к использованию.
Создание шейдера и передача динамической текстуры
Теперь мы создаем объект шейдера с помощью this.add.shader. В его настройках (initialUniforms) мы указываем, что для сэмплера iChannel0 (это стандартное имя для текстуры в шейдере) будет использоваться текстура с индексом 0 из переданного массива.
this.add.shader({
name: 'Tunnel',
fragmentKey: 'tunnel',
initialUniforms: {
resolution: [ 800, 600 ],
iChannel0: 0, // Индекс текстуры в массиве textures
alpha: 1,
origin: 2
},
setupUniforms: (setUniform, drawingContext) => {
setUniform('time', this.game.loop.getDuration());
}
}, 400, 300, 800, 600, [ 'shaderTexture' ]);
Ключевой аргумент — последний: массив [ 'shaderTexture' ]. Phaser сопоставляет индекс в этом массиве (0) с uniform-переменной iChannel0 в шейдере. Таким образом, созданная нами динамическая текстура становится источником данных для шейдерного эффекта "туннеля". Функция setupUniforms обновляет uniform time каждый кадр, анимируя шейдер.
Конфигурация игры и важность WebGL
Для работы шейдеров и динамических текстур рендерер должен быть установлен в Phaser.WEBGL. Рендерер Phaser.CANVAS не поддерживает эти функции.
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
Именно этот конфиг передается в конструктор Phaser.Game, инициируя запуск сцены.
Что попробовать дальше
Связка динамических текстур и шейдеров — мощный инструмент для создания уникальных визуальных эффектов в Phaser. Динамическая текстура выступает в роли промежуточного буфера, который можно заполнить любым контентом: спрайтами, геометрией, текстом или даже результатом другого шейдера. Для экспериментов попробуйте менять параметры в цикле stamp (количество, масштаб, вращение спрайтов), использовать другую исходную текстуру вместо яблока или создать несколько динамических текстур для передачи в разные сэмплеры одного сложного шейдера.
