О чем этот пример
Визуальный шлейф за курсором — это эффект, который добавляет игре динамики и стиля. В этой статье мы разберём, как реализовать такой эффект с использованием объекта `Graphics` в Phaser. Вы научитесь управлять графическими примитивами, интерполировать значения для плавных переходов и создавать систему частиц на основе точек, что пригодится не только для шлейфов, но и для других визуальных эффектов, например, следов от выстрелов или магических заклинаний.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
var config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
scene: {
create: create
,update: update
}
};
var Point = function (x, y, time) {
this.x = x;
this.y = y;
this.time = time;
};
var normalizeValue = function (value, min, max) {
return (value - min) / (max - min);
};
var linearInterpolation = function (norm, min, max) {
return (max - min) * norm + min;
};
var trail;
var points = [];
var head = {x: 0, y: 0};
var game = new Phaser.Game(config);
function create() {
trail = this.add.graphics();
this.input.on('pointermove', function (pointer) {
head.x = pointer.x;
head.y = pointer.y;
points.push(new Point(head.x, head.y, 4.0));
});
}
function update() {
trail.clear();
if (points.length > 4) {
trail.lineStyle(1, 0xFFFF00, 1.0);
trail.beginPath();
trail.lineStyle(0, 0xFFFF00, 1.0);
trail.moveTo(points[0].x, points[0].y);
for (var index = 1; index < points.length - 4; ++index)
{
var point = points[index];
trail.lineStyle(
linearInterpolation(index / (points.length - 4), 0, 20),
((0xFF&0x0ff)<<16)|(((linearInterpolation(index / points.length, 0x00, 0xFF)|0)&0x0ff)<<8)|(00&0x0ff),
0.5
);
trail.lineTo(point.x, point.y);
}
var count = 0;
for (var index = points.length - 4; index < points.length; ++index)
{
var point = points[index];
trail.lineStyle(
linearInterpolation(count++ / 4, 20, 0),
((0xFF&0x0ff)<<16)|(((linearInterpolation(index / points.length, 0x00, 0xFF)|0)&0x0ff)<<8)|(00&0x0ff),
1.0
);
trail.lineTo(point.x, point.y);
}
trail.strokePath();
trail.closePath();
}
for (var index = 0; index < points.length; ++index)
{
var point = points[index];
point.time -= 0.5;
if (point.time <= 0)
{
points.splice(index, 1);
index -= 1;
}
}
}
game.canvas.addEventListener('touchmove', function(event) {
event.preventDefault();
if (event.targetTouches.length == 1) {
var touch = event.targetTouches[0];
head.x = touch.pageX;
head.y = touch.pageY;
points.push(new Point(head.x, head.y, 4.0));
}
}, false);
Подготовка сцены и хранение точек
Для начала нам нужно настроить базовую сцену Phaser и создать структуру для хранения данных о каждой точке шлейфа. Точка будет хранить свои координаты и «время жизни». Это позволяет точкам постепенно исчезать.
var config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
scene: {
create: create,
update: update
}
};
Конструктор Point создаёт объект с координатами X, Y и временем жизни time. Массив points будет хранить все активные точки, а объект head — текущую позицию курсора.
var Point = function (x, y, time) {
this.x = x;
this.y = y;
this.time = time;
};
var points = [];
var head = {x: 0, y: 0};
Создание Graphics и отслеживание курсора
Объект Graphics в Phaser предназначен для программного рисования линий, фигур и контуров. Мы создадим его один раз в функции create. Событие pointermove будет фиксировать каждое движение мыши, обновлять позицию head и добавлять новую точку в массив.
function create() {
trail = this.add.graphics();
this.input.on('pointermove', function (pointer) {
head.x = pointer.x;
head.y = pointer.y;
points.push(new Point(head.x, head.y, 4.0));
});
}
Обратите внимание, что для сенсорных устройств добавлен отдельный обработчик события touchmove. Это необходимо для корректной работы на мобильных устройствах, так как Phaser может не обрабатывать касания так же, как движение мыши.
game.canvas.addEventListener('touchmove', function(event) {
event.preventDefault();
if (event.targetTouches.length == 1) {
var touch = event.targetTouches[0];
head.x = touch.pageX;
head.y = touch.pageY;
points.push(new Point(head.x, head.y, 4.0));
}
}, false);
Вспомогательные функции интерполяции
Чтобы шлейф плавно менял толщину и цвет от начала к концу, используются две математические функции. Они преобразуют значения из одного диапазона в другой, что является основой для плавных анимаций.
Функция normalizeValue приводит значение к нормализованному виду (от 0 до 1) в заданном диапазоне.
var normalizeValue = function (value, min, max) {
return (value - min) / (max - min);
};
Функция linearInterpolation выполняет обратную операцию: по нормализованному значению и целевому диапазону вычисляет итоговое значение. Именно она задаёт, например, толщину линии.
var linearInterpolation = function (norm, min, max) {
return (max - min) * norm + min;
};
Отрисовка и обновление шлейфа в update
Каждый кадр в функции update мы перерисовываем весь шлейф с нуля. Сначала очищаем старый рисунок с помощью trail.clear(). Если точек накоплено достаточно, начинаем рисовать ломаную линию через них.
Ключевой момент — изменение стиля линии для каждой её части. Для основной части шлейфа (от старой точки до предпоследних четырёх) толщина линии плавно увеличивается, а цвет меняет зелёную компоненту.
for (var index = 1; index < points.length - 4; ++index) {
var point = points[index];
trail.lineStyle(
linearInterpolation(index / (points.length - 4), 0, 20),
((0xFF&0x0ff)<<16)|(((linearInterpolation(index / points.length, 0x00, 0xFF)|0)&0x0ff)<<8)|(00&0x0ff),
0.5
);
trail.lineTo(point.x, point.y);
}
Для последних четырёх точек (самых свежих) толщина, наоборот, уменьшается от максимальной до нуля, создавая эффект «свечения» на кончике шлейфа.
var count = 0;
for (var index = points.length - 4; index < points.length; ++index) {
var point = points[index];
trail.lineStyle(
linearInterpolation(count++ / 4, 20, 0),
((0xFF&0x0ff)<<16)|(((linearInterpolation(index / points.length, 0x00, 0xFF)|0)&0x0ff)<<8)|(00&0x0ff),
1.0
);
trail.lineTo(point.x, point.y);
}
После отрисовки контура вызывается trail.strokePath(). В конце цикла обновления уменьшается «время жизни» каждой точки, и те, у кого оно стало меньше или равно нулю, удаляются из массива.
for (var index = 0; index < points.length; ++index) {
var point = points[index];
point.time -= 0.5;
if (point.time <= 0) {
points.splice(index, 1);
index -= 1;
}
}
Что попробовать дальше
Вы создали динамический визуальный эффект шлейфа, используя мощь Graphics и простую систему частиц на основе точек. Этот подход — отличная база для экспериментов. Попробуйте изменить формулу интерполяции цвета, чтобы шлейф переливался всеми цветами радуги, или добавьте частицам физику, привязав их к миру с помощью this.physics.add.image. Можно также сохранять точки не по движению мыши, а по позиции любого игрового объекта, создавая, например, шлейф за космическим кораблём.
