О чем этот пример
При создании инструментов для рисования в играх или интерактивных приложениях часто возникает проблема: движение курсора мыши происходит дискретно. Это приводит к прерывистым, пунктирным линиям. В этой статье мы разберем пример из Phaser, который решает эту задачу, используя Render Texture и интерполяцию позиции указателя. Вы научитесь создавать плавные линии, которые идеально повторяют траекторию движения мыши, даже если она была очень быстрой.
Версия 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);
points.forEach(p =>
{
rt.draw(brush, p.x - 16, p.y - 16, 1, hsv[i].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);
rt.render();
i++;
if (i === 360)
{
i = 0;
}
}, this);
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Инициализация сцены и загрузка ресурсов
Вся логика примера находится в классе сцены. На этапе preload загружается одно изображение — текстура кисти.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('brush', 'assets/sprites/brush2.png');
}
Здесь используется метод setBaseURL для указания базового пути к ресурсам и load.image для загрузки спрайта кисти.
Создание холста и подготовка данных
В методе create мы подготавливаем все необходимое для рисования. Ключевой объект — RenderTexture, который выступает в роли нашего динамического холста.
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;
// ... обработчики событий
}
* rt — это RenderTexture размером 800x600, размещенный в центре (координаты 400, 300).
* brush — это кадр (Frame) загруженной текстуры, который мы будем многократно отрисовывать.
* hsv — массив из 360 цветов, представляющий полный цветовой круг HSV. Это позволяет легко менять цвет кисти при каждом новом штрихе.
* `i— индекс для перебора цветов из массиваhsv`.
Магия плавных линий: getInterpolatedPosition
Сердце примера — обработчик события движения указателя (pointermove). Вместо того чтобы рисовать кисть только в текущей позиции курсора, мы получаем массив интерполированных позиций.
this.input.on('pointermove', pointer =>
{
if (pointer.isDown)
{
const points = pointer.getInterpolatedPosition(30);
points.forEach(p =>
{
rt.draw(brush, p.x - 16, p.y - 16, 1, hsv[i].color);
});
rt.render();
i++;
if (i === 360) { i = 0; }
}
}, this);
Метод pointer.getInterpolatedPosition(30) возвращает массив из 30 точек, равномерно распределенных между предыдущей и текущей позицией курсора. Это сглаживает линию, заполняя пропуски, которые возникают при быстром движении. Каждая точка массива points используется для отрисовки кисти на RenderTexture с помощью метода rt.draw. Смещение на -16 пикселя по осям X и Y центрует кисть относительно курсора (предполагается, что размер кисти 32x32). После отрисовки всех точек вызывается rt.render() для обновления текстуры. Индекс цвета `i` увеличивается после каждого события движения.
Обработка начала рисования
Для корректного начала линии нужен отдельный обработчик события нажатия кнопки мыши (pointerdown).
this.input.on('pointerdown', pointer =>
{
rt.draw(brush, pointer.x - 16, pointer.y - 16, 1, hsv[i].color);
rt.render();
i++;
if (i === 360) { i = 0; }
}, this);
Он ставит первую точку кисти в месте клика. Без этого обработчика линия начиналась бы только с первого события pointermove, что создавало бы небольшую задержку и пропуск в начале рисования.
Что попробовать дальше
Использование RenderTexture в паре с getInterpolatedPosition — мощный и эффективный способ реализовать плавное рисование в Phaser. Этот подход можно адаптировать для создания кистей разного размера, формы или прозрачности, а также для реализации ластика. Для экспериментов попробуйте изменить параметр в getInterpolatedPosition (например, на 10 или 50), чтобы увидеть, как меняется плотность точек и плавность линии, или замените цветовой круг на палитру с градиентом.
