О чем этот пример
Визуальные эффекты с использованием масок и анимации вершин объекта Rope могут добавить игре глубины и динамики. Этот пример демонстрирует, как наложить движущуюся маску на текстурированную веревку (Rope) и анимировать ее точки, создавая эффект плавного волнообразного движения. Эти техники полезны для создания гибких интерфейсов, анимированных фонов или нестандартных игровых элементов, таких как щупальца, ленты или магические эффекты.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
this.rope;
this.count = 0;
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('patchouli', 'assets/rope/patchouli.png');
this.load.image('wwy', 'assets/rope/weathering-with-you.jpg');
}
create ()
{
const rope = this.add.rope(400, 300, 'wwy', null, 16).setScale(0.7);
const mask = this.make.image({ x: 200, y: 300, key: 'patchouli' }, false);
rope.enableFilters().filters.external.addMask(mask);
this.rope = rope;
this.tweens.add({
targets: mask,
x: 600,
ease: 'sine.inOut',
yoyo: true,
duration: 2000,
repeat: -1
});
this.add.text(10, 10, 'A Rope with a mask', { font: '16px Courier', fill: '#ffffff' }).setShadow(1, 1);
}
update ()
{
this.count += 0.1;
let points = this.rope.points;
for (let i = 0; i < points.length; i++)
{
if (this.rope.horizontal)
{
points[i].y = Math.sin(i * 0.5 + this.count) * 10;
}
else
{
points[i].x = Math.sin(i * 0.5 + this.count) * 20;
}
}
this.rope.setDirty();
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
let game = new Phaser.Game(config);
Подготовка сцены и загрузка ресурсов
В методе preload загружаются два изображения: одно для текстуры веревки ('wwy'), другое — для маски ('patchouli'). Базовый URL задается через this.load.setBaseURL, что удобно для примеров с общими ресурсами.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('patchouli', 'assets/rope/patchouli.png');
this.load.image('wwy', 'assets/rope/weathering-with-you.jpg');
}
Создание Rope и наложение маски
В create создается основной объект — this.add.rope. Его параметры: позиция (400, 300), ключ текстуры, массив точек (здесь null, что означает равномерное распределение) и количество сегментов (16). Масштаб уменьшен до 0.7.
Маска создается через this.make.image. Это объект Image, который не добавляется автоматически на дисплей. Ключевой момент: метод rope.enableFilters() активирует систему фильтров для этого Rope. Затем к внешним фильтрам (filters.external) добавляется маска с помощью .addMask(mask).
const rope = this.add.rope(400, 300, 'wwy', null, 16).setScale(0.7);
const mask = this.make.image({ x: 200, y: 300, key: 'patchouli' }, false);
rope.enableFilters().filters.external.addMask(mask);
this.rope = rope;
Анимация маски и текстовой подсказки
Чтобы маска двигалась, на нее добавляется твин. Он перемещает маску по оси X от 200 до 600 пикселей с плавным sine.inOut движением, повторяясь бесконечно (repeat: -1) и возвращаясь назад (yoyo: true). Также на сцену выводится поясняющий текст.
this.tweens.add({
targets: mask,
x: 600,
ease: 'sine.inOut',
yoyo: true,
duration: 2000,
repeat: -1
});
this.add.text(10, 10, 'A Rope with a mask', { font: '16px Courier', fill: '#ffffff' }).setShadow(1, 1);
Анимация вершин Rope (волновой эффект)
В методе update происходит главная магия. Счетчик this.count увеличивается каждому кадру, создавая основу для анимации. Мы получаем массив точек Rope через this.rope.points. Каждая точка — объект с координатами `xиy`.
Цикл проходит по всем точкам. В зависимости от флага this.rope.horizontal (который в данном примере не меняется и, вероятно, равен false по умолчанию, что означает вертикальную веревку), анимируется либо координата `x, либоy. Используется синусоидальная функцияMath.sin`, которая зависит от индекса точки и общего счетчика. Это создает бегущую волну.
После изменения координат необходимо вызвать this.rope.setDirty(), чтобы Phaser знал, что геометрия объекта изменилась и ее нужно перерисовать.
update ()
{
this.count += 0.1;
let points = this.rope.points;
for (let i = 0; i < points.length; i++)
{
if (this.rope.horizontal)
{
points[i].y = Math.sin(i * 0.5 + this.count) * 10;
}
else
{
points[i].x = Math.sin(i * 0.5 + this.count) * 20;
}
}
this.rope.setDirty();
}
Конфигурация игры
Стандартная конфигурация Phaser Game. Важно, что сцена (scene) указывается как класс Example. Фон задан темно-серым цветом для контраста.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
let game = new Phaser.Game(config);
Что попробовать дальше
Комбинируя объект Rope с динамической маской и анимацией вершин, можно создавать сложные и привлекательные визуальные эффекты. Для экспериментов попробуйте: изменить форму маски на геометрическую, использовать несколько масок одновременно, варьировать амплитуду и частоту волны в update, или привязать движение точек к физическому взаимодействию.
