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

Интерактивные верёвки могут оживить любую игру, добавив физики или просто визуального интереса. В этом примере мы создадим инструмент для рисования верёвок мышью с плавным цветовым переходом, а затем заставим их изящно колыхаться. Вы научитесь работать с объектом `Rope`, управлять его точками, применять кастомные цвета и создавать простые анимации, не затрагивая физический движок.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();

        this.ropes = [];
        this.count = 0;
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('bg', 'assets/rope/background-chalk.jpg');
        this.load.image('block', 'assets/rope/6x6.png');
    }

    create ()
    {
        this.add.image(400, 300, 'bg');

        const debug = this.add.graphics().fillStyle(0xffffff);

        const distance = 6;
        const prev = new Phaser.Math.Vector2();

        const hsl = Phaser.Display.Color.HSVColorWheel();

        let colorIndex = 0;
        let points = [];
        let colors = [];

        this.input.on('pointerdown', (pointer) => {

            points = [];
            colors = [];
            colorIndex = 0;

            prev.x = pointer.x;
            prev.y = pointer.y;
    
            points.push(new Phaser.Math.Vector2(pointer.x, pointer.y));
            colors.push(hsl[0].color);

            debug.fillStyle(hsl[0].color);
            debug.fillRect(pointer.x - 1, pointer.y - 1, 3, 3);
    
        });
  
        this.input.on('pointermove', (pointer) => {

            if (pointer.isDown)
            {
                const x = pointer.x;
                const y = pointer.y;
    
                if (Phaser.Math.Distance.Between(x, y, prev.x, prev.y) > distance)
                {
                    prev.x = x;
                    prev.y = y;
    
                    points.push(new Phaser.Math.Vector2(pointer.x, pointer.y));
                    colors.push(hsl[colorIndex].color);
    
                    debug.fillStyle(hsl[colorIndex].color);
                    debug.fillRect(pointer.x, pointer.y, 2, 2);

                    colorIndex = Phaser.Math.Wrap(colorIndex + 2, 0, 359);
                }
            }
    
        });
    
        this.input.on('pointerup', () => {

            debug.clear();

            const rope = this.add.rope(0, 0, 'block', null, points, true, colors);

            this.ropes.push(rope);

        });

        this.add.text(10, 10, 'Draw with the mouse', { font: '16px Courier', fill: '#ffffff' }).setShadow(1, 1).setDepth(1);
    }

    update ()
    {
        this.count += 0.5;

        this.ropes.forEach((rope) => {

            let points = rope.points;

            for (let i = 0; i < points.length; i++)
            {
                points[i].x += Math.cos(i * 0.5 + this.count);
            }
    
            rope.setDirty();

        });
    }
}

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

let game = new Phaser.Game(config);

Подготовка сцены и ресурсов

Класс Example расширяет Phaser.Scene. В конструкторе инициализируются массивы ropes для хранения созданных верёвок и счётчик count для анимации.

В методе preload загружаются две текстуры: фон и маленькое изображение 6x6.png, которое будет использоваться как текстура для верёвки.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('bg', 'assets/rope/background-chalk.jpg');
    this.load.image('block', 'assets/rope/6x6.png');
}

Рисование линии с градиентом

В методе create мы настраиваем интерактивное рисование. Создаётся графический объект debug для визуализации процесса рисования. Также создаётся цветовое колесо hsl с 360 оттенками.

Основная логика разбита на три обработчика событий мыши: 1. pointerdown: Начинает новую линию. Создаётся первый вектор точки и её цвет из начала цветового круга. 2. pointermove: При движении с зажатой кнопкой новые точки добавляются только если расстояние от предыдущей превышает порог distance. Это сглаживает линию. Каждая новая точка получает следующий цвет из колеса, создавая градиент. 3. pointerup: Очищает графику отладки и создаёт финальный объект Rope.

this.input.on('pointermove', (pointer) => {
    if (pointer.isDown)
    {
        const x = pointer.x;
        const y = pointer.y;
        if (Phaser.Math.Distance.Between(x, y, prev.x, prev.y) > distance)
        {
            prev.x = x;
            prev.y = y;
            points.push(new Phaser.Math.Vector2(pointer.x, pointer.y));
            colors.push(hsl[colorIndex].color);
            colorIndex = Phaser.Math.Wrap(colorIndex + 2, 0, 359);
        }
    }
});

Создание и сохранение верёвки

В момент отпускания кнопки мыши (pointerup) массив нарисованных точек и их цветов готов. Именно здесь создаётся объект Rope.

Ключевые параметры конструктора: - Первые два аргумента (0, 0) — координаты опорной точки верёвки на сцене. - 'block' — ключ текстуры. - null — фрейм текстуры (не используется). - points — массив точек Vector2. - true — горизонтальное натяжение текстуры. - colors — массив цветов для каждой точки верёвки.

Созданная верёвка добавляется в массив this.ropes для дальнейшей анимации.

const rope = this.add.rope(0, 0, 'block', null, points, true, colors);
this.ropes.push(rope);

Анимация верёвки через update

Метод update выполняется каждый кадр и отвечает за оживление всех созданных верёвок. Глобальный счётчик this.count постоянно увеличивается.

Для каждой верёвки в цикле мы получаем доступ к её точкам (rope.points) и смещаем их координату `xпо функции косинуса. Значениеi * 0.5` обеспечивает сдвиг фазы для каждой точки, создавая волнообразный эффект.

После изменения координат точек необходимо вызвать rope.setDirty(). Это критически важный шаг, который сообщает рендереру, что геометрия объекта изменилась и её нужно перерисовать.

update ()
{
    this.count += 0.5;
    this.ropes.forEach((rope) => {
        let points = rope.points;
        for (let i = 0; i < points.length; i++)
        {
            points[i].x += Math.cos(i * 0.5 + this.count);
        }
        rope.setDirty();
    });
}

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

Вы создали интерактивный инструмент для рисования цветных динамических верёвок. Основные концепции: работа с Rope, создание градиента через цветовое колесо и анимация через прямое манипулирование точками с обязательным вызовом setDirty(). Для экспериментов попробуйте изменить текстуру на более длинную, анимировать координату `y` точек или привязать верёвку к физическому телу, создав визуально сложную цепь.