О чем этот пример
Визуальные эффекты часто оживляют игровой мир. Одним из таких эффектов является динамическое отражение с водной рябью, которое можно реализовать в Phaser 3 с помощью генератора клеточного шума и фильтров. Эта статья показывает, как создать эффект отражения персонажа в "воде" с анимированными волнами, используя кадры захвата и фильтр смещения на основе нормальной карты шума.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
bg;
trees;
noise;
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('walker', 'assets/animations/walker.png', 'assets/animations/walker.json');
this.load.image('sky', 'assets/skies/ms3-sky.png');
this.load.image('trees', 'assets/skies/ms3-trees.png');
}
create ()
{
this.bg = this.add.tileSprite(0, 38, 800, 296, 'sky').setOrigin(0, 0);
this.trees = this.add.tileSprite(0, 280, 800, 320, 'trees').setOrigin(0, 0);
const animConfig = {
key: 'walk',
frames: 'walker',
frameRate: 60,
repeat: -1
};
this.anims.create(animConfig);
const sprite = this.add.sprite(400, 484, 'walker', 'frame_0000');
sprite.play('walk');
// Move the scene up to make room for a reflection.
this.bg.y -= 200;
this.trees.y -= 200;
sprite.y -= 200;
// Capture the current frame.
this.cameras.main.forceComposite = true;
this.add.captureFrame('capture');
// Create animated normal map for ripples.
this.noise = this.add.noisecell4d({
noiseCells: [ 4, 32, 4, 4 ],
noiseIterations: 4,
noiseNormalMap: true,
noiseNormalScale: 1
}, 0, 0, this.scale.width, this.scale.height)
.setRenderToTexture('noise-normal');
// Show reflection.
this.add.image(400, 500, 'capture')
.setFlipY(true)
.setTint(0xaabbcc)
.enableFilters().filters.external.addDisplacement('noise-normal', 1, 1);
}
update ()
{
this.bg.tilePositionX -= 2;
this.trees.tilePositionX -= 6;
this.noise.noiseOffset = [
this.noise.noiseOffset[0] - 6 / 800, // Match background scroll.
0,
Math.cos(this.noise.noiseOffset[0] / 10), // Evolve noise field.
Math.sin(this.noise.noiseOffset[0] / 10),
];
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#301828',
scene: Example
};
const game = new Phaser.Game(config);
Создание базовой анимации и захват кадра
В примере создаётся сцена с параллаксным фоном (sky и trees) и анимированным персонажем (walker). Чтобы создать отражение, нужно сначала получить изображение текущего состояния сцены.
this.cameras.main.forceComposite = true;
this.add.captureFrame('capture');
Вызов captureFrame с ключом 'capture' создаёт текстуру, содержащую снимок текущего кадра с камеры. Это изображение будет использоваться как основа для отражения. Параметр forceComposite гарантирует корректный захват всех элементов.
Генерация нормальной карты шума для ряби
Для эффекта волн используется генератор клеточного шума (noisecell4d), настроенный на создание нормальной карты.
this.noise = this.add.noisecell4d({
noiseCells: [ 4, 32, 4, 4 ],
noiseIterations: 4,
noiseNormalMap: true,
noiseNormalScale: 1
}, 0, 0, this.scale.width, this.scale.height)
.setRenderToTexture('noise-normal');
Конфигурация объекта шума:
- noiseCells: определяет размеры клеточного шума в четырёх измерениях.
- noiseIterations: количество итераций для создания детализации.
- noiseNormalMap: флаг, указывающий генератору создавать нормальную карту (карту векторов), идеальную для эффектов смещения.
- noiseNormalScale: масштаб нормалей.
Метод setRenderToTexture рендерит шум в отдельную текстуру с именем 'noise-normal', которую затем можно использовать как источник для фильтра.
Применение фильтра смещения для отражения
Отражение создается как зеркальная копия захваченного кадра с применением фильтра смещения.
this.add.image(400, 500, 'capture')
.setFlipY(true)
.setTint(0xaabbcc)
.enableFilters().filters.external.addDisplacement('noise-normal', 1, 1);
Ключевые шаги:
1. setFlipY(true) – зеркально отражает изображение по вертикали, создавая эффект отражения.
2. setTint – применяет цветовой оттенок для имитации водной поверхности.
3. enableFilters() – активирует систему фильтров для объекта.
4. filters.external.addDisplacement – добавляет фильтр смещения (DisplacementFilter), который использует текстуру 'noise-normal' как карту смещения. Параметры (1, 1) определяют масштаб смещения по X и Y.
Анимация параллакса и шума
В функции update происходит движение фона и динамическое изменение шума для создания "живой" ряби.
this.bg.tilePositionX -= 2;
this.trees.tilePositionX -= 6;
this.noise.noiseOffset = [
this.noise.noiseOffset[0] - 6 / 800,
0,
Math.cos(this.noise.noiseOffset[0] / 10),
Math.sin(this.noise.noiseOffset[0] / 10),
];
Сдвиг tilePositionX создаёт параллаксное движение фона. Обновление массива noiseOffset изменяет позицию шума в четырёхмерном пространстве:
- Первый элемент смещается пропорционально движению деревьев (- 6 / 800), чтобы рябь двигалась синхронно с фоном.
- Третий и четвертый элементы вычисляются через Math.cos и Math.sin, создавая плавную, органичную эволюцию поля шума, имитирующую естественное движение воды.
Конфигурация игры и запуск
Пример завершается стандартной конфигурацией игры Phaser.
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#301828',
scene: Example
};
const game = new Phaser.Game(config);
Эта конфигурация создаёт игровой экран размером 800x600 с заданным цветом фона и устанавливает наш класс Example как основную сцену.
Что попробовать дальше
Сочетание захвата кадра, генерации нормальной карты клеточного шума и фильтра смещения позволяет создавать сложные динамические эффекты, такие как водные отражения с рябью, без использования предварительно созданных ресурсов. Для экспериментов попробуйте:
- Изменить параметры noiseCells и noiseIterations для получения другой текстуры волн.
- Использовать другие фильтры (например, BlurFilter) вместе со смещением для эффекта рассеивания.
- Применить смещение не только к отражению, но и к основному фону для создания эффекта подводного мира.
