О чем этот пример

Пример демонстрирует, как объект `Rope` в Phaser может выступать не только как визуальный элемент, но и как динамическая маска для другого изображения. Это открывает возможности для создания сложных анимаций и визуальных эффектов, где форма маски изменяется в реальном времени. Такой подход полезен для визуализации волн, лент, подвижных границ или "живых" рамок вокруг объектов в игре.

Версия 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('dragonball', 'assets/rope/dragonball.png');
        this.load.image('mha', 'assets/rope/mha.jpg');
    }

    create ()
    {
        const rope = this.add.rope(400, 300, 'dragonball', null, 8, false);

        this.rope = rope;

        const bg = this.add.image(300, 300, 'mha');

        bg.enableFilters().filters.external.addMask(rope);

        this.tweens.add({
            targets: bg,
            x: 500,
            ease: 'sine.inOut',
            yoyo: true,
            duration: 2000,
            repeat: -1
        });

        this.add.text(10, 10, 'Using a Rope as 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);

Инициализация сцены и загрузка ресурсов

В конструкторе класса сцены инициализируются свойства для хранения ссылки на объект Rope и счетчика для анимации.

В методе preload загружаются два изображения: одно для текстуры самой веревки (dragonball), другое — для фонового изображения (mha), которое будет маскироваться.

this.load.image('dragonball', 'assets/rope/dragonball.png');
this.load.image('mha', 'assets/rope/mha.jpg');

Создание объектов и назначение маски

В методе create создаются основные игровые объекты. Сначала создается объект Rope с помощью метода this.add.rope. Аргументы задают его позицию, текстуру и количество сегментов (8).

const rope = this.add.rope(400, 300, 'dragonball', null, 8, false);
this.rope = rope;

Затем добавляется фоновое изображение. Ключевой шаг — включение фильтров для этого изображения и назначение объекта rope в качестве внешней маски.

const bg = this.add.image(300, 300, 'mha');
bg.enableFilters().filters.external.addMask(rope);

Для наглядности к фоновому изображению добавляется твин, который перемещает его по горизонтали.

this.tweens.add({
    targets: bg,
    x: 500,
    ease: 'sine.inOut',
    yoyo: true,
    duration: 2000,
    repeat: -1
});

Анимация точек веревки (маски)

Магия происходит в методе update. На каждом кадре увеличивается счетчик, который влияет на математическую функцию синуса. Затем в цикле перебираются точки (points), из которых состоит Rope. В зависимости от ориентации веревки (horizontal) изменяется координата `yилиx` каждой точки по синусоидальному закону. Это создает эффект "волны".

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;
    }
}

После изменения координат точек необходимо вызвать метод setDirty(), чтобы Phaser знал, что геометрия объекта изменилась и его нужно перерисовать. Без этого вызова анимация маски не будет видна.

this.rope.setDirty();

Конфигурация игры

Стандартная конфигурация игры Phaser, в которой указывается тип рендерера, размеры холста, цвет фона, родительский контейнер в DOM и класс сцены для запуска.

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: Example
};
let game = new Phaser.Game(config);

Что попробовать дальше

Вы научились использовать Rope как динамическую маску, создавая живой, анимированный эффект обрезки изображения. Для экспериментов попробуйте изменить количество сегментов веревки, формулу анимации точек (например, использовать косинус или шум Перлина), применить маску к группе объектов или сделать так, чтобы форма маски реагировала на ввод игрока.