О чем этот пример
Phaser предлагает мощный инструмент `CanvasTexture` для генерации текстур на лету. Это открывает двери для создания сложных визуальных эффектов, процедурной анимации и динамического интерфейса прямо в контексте вашей игры. Вместо загрузки статичных изображений вы можете программно рисовать кадр за кадром, создавая уникальную и живую графику, как в популярных демосценах Dwitter. В этой статье мы разберем реальный пример, превращающий математические формулы в гипнотическую анимацию.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
const T = Math.tan;
const C = Math.cos;
const S = Math.sin;
class Example extends Phaser.Scene
{
frame = 0;
time = 0;
x;
c;
create ()
{
const canvasTexture = this.textures.createCanvas('dwitter', 1920, 1080);
this.c = canvasTexture.getSourceImage();
this.x = this.c.getContext('2d');
this.add.image(0, 0, 'dwitter').setOrigin(0).setScale(0.5);
}
update ()
{
this.time = this.frame / 60;
if (this.time * 60 | this.frame - 1 === 0)
{
this.time += 0.000001;
}
this.frame++;
this.u(this.time);
}
R (r, g, b, a)
{
a = a === undefined ? 1 : a;
return `rgba(${r|0},${g|0},${b|0},${a})`;
}
u (t)
{
let c = this.c;
let x = this.x;
let i;
let q;
c.width |= i = 300;
x.lineWidth = 0.1;
while (--i) { q = 19 + S(t / 6) / 28 * i,x.arc(S(q / 3) * i + q * 60,(C(q * S(t / 2)) + 4) * i / 2 + 200,(C(q) * 60 + 200) * S(i / 96),0,7); }
x.stroke();
}
}
const config = {
width: 960,
height: 540,
type: Phaser.CANVAS,
parent: 'phaser-example',
backgroundColor: '#ffffff',
scene: Example
};
const game = new Phaser.Game(config);
Создание динамического холста
Ключевой объект для работы — CanvasTexture. Это текстура, источником данных для которой служит HTML Canvas элемент. Его можно создать и модифицировать в реальном времени.
В методе create() создается текстура с именем 'dwitter' и разрешением 1920x1080. Затем мы получаем ссылки на сам элемент <canvas> и его 2D-контекст для рисования. Эта текстура добавляется на сцену как обычное изображение, масштабированное вдвое.
const canvasTexture = this.textures.createCanvas('dwitter', 1920, 1080);
this.c = canvasTexture.getSourceImage();
this.x = this.c.getContext('2d');
this.add.image(0, 0, 'dwitter').setOrigin(0).setScale(0.5);
Игровой цикл и время
Анимация управляется в методе update(), который вызывается каждый кадр. Для плавности анимации, привязанной ко времени, а не к частоте кадров, используется переменная this.time, вычисляемая из номера кадра.
Условный блок if — это технический трюк для предотвращения деления на ноль или других артефактов в самом первом кадре, гарантируя, что this.time никогда не будет абсолютным нулем при использовании в математических функциях.
update ()
{
this.time = this.frame / 60;
if (this.time * 60 | this.frame - 1 === 0)
{
this.time += 0.000001;
}
this.frame++;
this.u(this.time);
}
Вспомогательная функция для цвета
Метод R() — это удобная утилита для формирования CSS-строки цвета (rgba). Он принимает компоненты красного, зеленого, синего и опционально альфа-канал. Побитовый оператор | 0 используется для быстрого приведения чисел к целочисленному виду.
R (r, g, b, a)
{
a = a === undefined ? 1 : a;
return `rgba(${r|0},${g|0},${b|0},${a})`;
}
Сердце визуализации: функция u(t)
Вся магия происходит в функции u(t), где `t— текущее время. Каждый кадр холст очищается (строкаc.width |= i = 300;делает это, присваивая ширину), задается толщина линии, и в цикле рисуются дуги (arc`).
Параметры для каждой дуги (координаты x, y и радиус) вычисляются с помощью комбинации тригонометрических функций (`S— синус,C— косинус) от времениtи переменной циклаi. Это создает сложную интерференционную картину, где каждый элемент цикла вносит свой вклад в итоговый завихряющийся узор. После определения всех путей вызываетсяstroke()` для их отрисовки.
u (t)
{
let c = this.c;
let x = this.x;
let i;
let q;
c.width |= i = 300;
x.lineWidth = 0.1;
while (--i) { q = 19 + S(t / 6) / 28 * i,x.arc(S(q / 3) * i + q * 60,(C(q * S(t / 2)) + 4) * i / 2 + 200,(C(q) * 60 + 200) * S(i / 96),0,7); }
x.stroke();
}
Конфигурация игры
Конфиг игры стандартный. Важный момент — type: Phaser.CANVAS. Хотя CanvasTexture будет работать и в WebGL-режиме, использование рендерера Canvas может упростить отладку и обеспечить полную совместимость при прямой работе с 2D-контекстом.
const config = {
width: 960,
height: 540,
type: Phaser.CANVAS,
parent: 'phaser-example',
backgroundColor: '#ffffff',
scene: Example
};
const game = new Phaser.Game(config);
Что попробовать дальше
CanvasTexture в Phaser — это мост между игровым движком и полной свободой программируемой графики Canvas API. Вы можете создавать не только абстрактные эффекты, как в примере, но и динамические карты, уникальные частицы, шейдероподобные фильтры для 2D или кастомные элементы UI. Для экспериментов попробуйте: изменить математические формулы в u(t), используя this.R() для цветных линий; рисовать не arc, а rect или fillText; привязать параметры анимации к вводу игрока (например, скорости персонажа); или использовать несколько слоев CanvasTexture для композиции сложных сцен.
