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

При создании игр часто возникает задача отрисовки динамических эффектов, например, следов от кисти или плавных теней. Встроенный механим интерполяции координат указателя мыши в Phaser 3 позволяет создавать такие эффекты без разрывов и с постоянной частотой, независимо от FPS. В этой статье мы разберем, как использовать `Render Texture` в связке с `getInterpolatedPosition()` для рисования плавной разноцветной тени, которая следует за движением мыши. Этот подход полезен для создания инструментов рисования, магических следов или динамического визуального оформления.

Версия 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('brush', 'assets/sprites/brush2.png');
    }

    create ()
    {
        const rt = this.add.renderTexture(400, 300, 800, 600);

        const brush = this.textures.getFrame('brush');

        const hsv = Phaser.Display.Color.HSVColorWheel();
        let i = 0;

        this.input.on('pointermove', pointer =>
        {

            if (pointer.isDown)
            {
                const points = pointer.getInterpolatedPosition(30);
                let first = false;
                let color;

                points.forEach(p =>
                {

                    if (!first)
                    {
                        color = 0x000000;
                        first = true;
                    }
                    else
                    {
                        color = hsv[i].color;
                    }

                    rt.draw(brush, p.x - 16, p.y - 16, 1, color);

                });

                rt.render();

                i++;

                if (i === 360)
                {
                    i = 0;
                }
            }

        }, this);

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

            rt.draw(brush, pointer.x - 16, pointer.y - 16, 1, hsv[i].color).render();

            i++;

            if (i === 360)
            {
                i = 0;
            }

        }, this);

    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и создание Render Texture

В методе preload загружается одно изображение-кисть, которое будет использоваться как штамп для рисования. Основная магия происходит в create. Здесь создается объект Render Texture (RT) — это специальный текстурированный игровой объект, который можно использовать как холст для отрисовки других текстур или графики прямо во время выполнения.

const rt = this.add.renderTexture(400, 300, 800, 600);
const brush = this.textures.getFrame('brush');
const hsv = Phaser.Display.Color.HSVColorWheel();
let i = 0;

Render Texture размещается в центре сцены (координаты 400x300) с размером 800x600 пикселей. HSVColorWheel() генерирует массив из 360 цветов, представляющих полный цветовой круг, что позволит нам менять цвет кисти при каждом движении. Переменная `i` будет индексом для выбора цвета из этого массива.

Обработка движения с интерполяцией

Ключевой метод pointer.getInterpolatedPosition(30) вызывается при движении мыши с зажатой кнопкой. Он возвращает массив промежуточных координат между предыдущей и текущей позицией курсора, вычисленных с заданной частотой (30 точек в секунду). Это гарантирует плавную линию даже если частота кадров низкая или мышь движется очень быстро.

this.input.on('pointermove', pointer => {
    if (pointer.isDown) {
        const points = pointer.getInterpolatedPosition(30);
        let first = false;
        let color;
        points.forEach(p => {
            if (!first) {
                color = 0x000000;
                first = true;
            } else {
                color = hsv[i].color;
            }
            rt.draw(brush, p.x - 16, p.y - 16, 1, color);
        });
        rt.render();
        i++;
        if (i === 360) { i = 0; }
    }
}, this);

Для каждой точки в массиве points мы рисуем нашу текстуру кисти (brush) на Render Texture с помощью метода draw. Первая точка в каждом сегменте движения рисуется черным цветом (0x000000), создавая эффект тени или контура, а все последующие — текущим цветом из HSV-круга. Смещение на 16 пикселей (p.x - 16, p.y - 16) центрирует кисть относительно координат, так как размер кисти 32x32 пикселя. После отрисовки всех точек вызывается rt.render() для применения изменений.

Обработка клика и завершение цикла

Отдельно обрабатывается событие pointerdown — момент, когда кнопка мыши только что нажата. В этом случае интерполяция не нужна, и кисть ставится одним штампом в точку нажатия.

this.input.on('pointerdown', pointer => {
    rt.draw(brush, pointer.x - 16, pointer.y - 16, 1, hsv[i].color).render();
    i++;
    if (i === 360) { i = 0; }
}, this);

Обратите внимание, что здесь метод draw возвращает ссылку на Render Texture, что позволяет сразу вызвать render() через чейнинг. Индекс цвета `i` увеличивается после каждого события (движения или клика), и когда он достигает 360, сбрасывается на ноль, зацикливая палитру.

Настройка игры и визуальный результат

Конфигурация игры стандартна, но стоит обратить внимание на цвет фона (#cc9999), который подобран для контраста с рисованной линией.

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

В результате получается интерактивное полотно: при перемещении мыши с зажатой кнопкой рисуется плавная разноцветная линия с черным «началом» у каждого сегмента движения, создавая эффект объемной или подсвеченной тени. Без интерполяции линия состояла бы из отдельных, возможно, редких, штампов.

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

Использование Render Texture и getInterpolatedPosition() открывает простой путь для создания плавных динамических эффектов рисования в реальном времени. Для экспериментов попробуйте: изменить текстуру кисти на частицу или спрайт анимации; варьировать частоту интерполяции (например, 60 для более гладкой, но ресурсоемкой линии); применять режимы смешивания (blendMode) при отрисовке на Render Texture для создания сложных визуальных наложений; или сохранять получившуюся текстуру как изображение.