О чем этот пример
Визуальные эффекты — ключевая часть игрового впечатления. Шейдеры в Phaser позволяют создавать сложные динамические текстуры и пост-обработку, но часто возникает потребность менять их параметры на лету. Эта статья покажет, как управлять текстурами, передаваемыми в шейдер, после его создания, что открывает возможности для интерактивных эффектов, смены времени суток или реакций на действия игрока. Вы научитесь загружать шейдеры, настраивать их и динамически переключать текстуры по клику.
Версия 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.image('metal', 'assets/textures/alien-metal.jpg');
this.load.image('grass', 'assets/textures/grass.png');
this.load.image('tiles', 'assets/textures/tiles.jpg');
this.load.image('logo', 'assets/sprites/phaser3-logo-small.png');
this.load.glsl('tunnel', 'assets/shaders/tunnel.frag');
}
create ()
{
// Phaser 4: Use config object for shader
const shader = this.add.shader({
name: 'TunnelShader',
fragmentKey: 'tunnel',
initialUniforms: {
alpha: 1,
origin: 2,
resolution: [ 800, 600 ],
iChannel0: 0 // texture unit 0
},
setupUniforms: (setUniform, drawingContext) =>
{
setUniform('time', this.game.loop.getDuration());
},
}, 400, 300, 800, 600, [ 'metal' ]);
shader.setInteractive();
shader.on('pointerdown', () =>
{
const currentTexture = shader.textures[0].key;
if (currentTexture === 'metal')
{
shader.setTextures([ 'grass' ]);
}
else if (currentTexture === 'grass')
{
shader.setTextures([ 'tiles' ]);
}
else
{
shader.setTextures([ 'metal' ]);
}
});
this.add.image(400, 300, 'logo');
this.add.text(10, 10, 'Click to change texture', { font: '16px Courier', fill: '#ffffff' }).setShadow(1, 1);
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Загрузка ресурсов: текстуры и шейдер
Перед созданием шейдера необходимо загрузить все необходимые ресурсы. В методе preload мы используем this.load.image для загрузки текстур и this.load.glsl для загрузки шейдерного кода из файла.
this.load.image('metal', 'assets/textures/alien-metal.jpg');
this.load.image('grass', 'assets/textures/grass.png');
this.load.image('tiles', 'assets/textures/tiles.jpg');
this.load.image('logo', 'assets/sprites/phaser3-logo-small.png');
this.load.glsl('tunnel', 'assets/shaders/tunnel.frag');
Метод load.glsl загружает фрагментный шейдер (отвечающий за цвет пикселя) и сохраняет его под ключом 'tunnel'. Ключи для текстур ('metal', 'grass', 'tiles', 'logo') будут использоваться позже для их передачи в шейдер и создания спрайтов.
Создание и настройка шейдерного объекта
В Phaser 4 для создания шейдера используется метод this.add.shader, принимающий объект конфигурации и параметры позиционирования. Это более структурированный подход по сравнению с прямым вызовом конструктора.
const shader = this.add.shader({
name: 'TunnelShader',
fragmentKey: 'tunnel',
initialUniforms: {
alpha: 1,
origin: 2,
resolution: [ 800, 600 ],
iChannel0: 0 // texture unit 0
},
setupUniforms: (setUniform, drawingContext) =>
{
setUniform('time', this.game.loop.getDuration());
},
}, 400, 300, 800, 600, [ 'metal' ]);
Разберем ключевые параметры конфигурации:
- fragmentKey: указывает на загруженный ранее шейдерный код ('tunnel').
- initialUniforms: задает начальные значения uniform-переменных шейдера. Например, iChannel0: 0 означает, что в первый тектурный юнит (слот) будет привязана текстура из первого элемента массива, переданного последним аргументом (в нашем случае 'metal').
- setupUniforms: функция-колбэк, вызываемая каждый кадр для обновления динамических uniform-переменных. Здесь мы обновляем time, передавая общее время работы игры, что необходимо для анимации эффекта туннеля.
Последний аргумент [ 'metal' ] — это массив ключей текстур, которые будут связаны с текстурными юнитами шейдера (iChannel0, iChannel1 и т.д.).
Интерактивность и динамическая смена текстур
Чтобы шейдер реагировал на ввод, его нужно сделать интерактивным с помощью setInteractive. После этого можно назначить обработчик события pointerdown.
shader.setInteractive();
shader.on('pointerdown', () =>
{
const currentTexture = shader.textures[0].key;
if (currentTexture === 'metal')
{
shader.setTextures([ 'grass' ]);
}
else if (currentTexture === 'grass')
{
shader.setTextures([ 'tiles' ]);
}
else
{
shader.setTextures([ 'metal' ]);
}
});
Логика обработчика построена на цикличной ротации текстур. Свойство shader.textures содержит массив объектов текстур, привязанных к шейдеру. Мы проверяем ключ первой текстуры (shader.textures[0].key) и, в зависимости от его значения, меняем весь набор привязанных текстур с помощью метода setTextures. Этот метод принимает новый массив ключей и автоматически перепривязывает их к соответствующим текстурным юнитам шейдера (iChannel0, iChannel1 и т.д.). В данном примере используется только один текстурный слот.
Добавление интерфейсных элементов
Для завершения сцены добавим статичный логотип и текстовую подсказку. Они создаются после шейдера и отрисовываются поверх него.
this.add.image(400, 300, 'logo');
this.add.text(10, 10, 'Click to change texture', { font: '16px Courier', fill: '#ffffff' }).setShadow(1, 1);
Метод this.add.image создает спрайт с логотипом Phaser в центре экрана. this.add.text создает текстовый объект с инструкцией в левом верхнем углу. Стиль задается объектом конфигурации, а setShadow добавляет легкую тень для лучшей читаемости.
Что попробовать дальше
Динамическое управление текстурами шейдера открывает широкие возможности для игрового дизайна. Вы можете использовать этот механизм не только по клику, но и по таймеру, при столкновениях или изменении состояния игрока. Для экспериментов попробуйте:
1. Загрузить и переключать между несколькими шейдерами, используя setFragmentShader.
2. Передавать в шейдер больше одной текстуры (например, [ 'metal', 'grass' ]) и менять их независимо.
3. Связать смену текстуры с игровыми событиями, например, менять фон шейдера при получении урона или сборе бонуса.
4. Анимировать изменение uniform-переменных (например, alpha для плавного появления/исчезновения эффекта).
