О чем этот пример
При создании игр часто возникает задача отрисовки динамических эффектов, например, следов от кисти или плавных теней. Встроенный механим интерполяции координат указателя мыши в 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 для создания сложных визуальных наложений; или сохранять получившуюся текстуру как изображение.
