О чем этот пример
В мире разработки игр на Phaser часто возникает необходимость генерировать графику на лету — для спецэффектов, процедурного контента или уникальных визуализаций. Использование Canvas Texture позволяет создавать и изменять текстуры прямо во время выполнения игры, открывая двери для бесконечного творчества без загрузки внешних ресурсов. Эта статья разбирает реальный пример из официальных примеров Phaser, где с помощью математических функций и Canvas API создаётся гипнотическая анимированная текстура. Вы научитесь работать с `this.textures.createCanvas`, управлять контекстом рисования и связывать это с игровым циклом `update` для плавной анимации.
Версия 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 p;
let i;
let a;
let b;
c.width = 1920; p = Math.PI * 2; x.beginPath(); a = 540; for (i = 0; i < 480; i++) { b = p * (i / 480) * T(t / 4); x.lineTo(0,a + a * S(b)); x.lineTo(i * 4,a + a * -S(b)); }x.stroke();
}
}
const config = {
width: 960,
height: 540,
type: Phaser.CANVAS,
parent: 'phaser-example',
backgroundColor: '#ffffff',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка Canvas Texture
В Phaser текстура на основе Canvas создается методом this.textures.createCanvas. Этот метод возвращает объект текстуры, который можно использовать как обычное изображение. Ключевой момент — получение ссылки на сам элемент <canvas> и его контекст рисования для последующих операций.
const canvasTexture = this.textures.createCanvas('dwitter', 1920, 1080);
this.c = canvasTexture.getSourceImage();
this.x = this.c.getContext('2d');
После создания текстуры, мы добавляем её в сцену как изображение. Обратите внимание на setOrigin(0) и setScale(0.5) — это позиционирует изображение в левом верхнем углу и уменьшает его вдвое, чтобы оно поместилось в область игры размером 960x540.
this.add.image(0, 0, 'dwitter').setOrigin(0).setScale(0.5);
Игровой цикл и управление временем
Анимация текстуры происходит в методе update, который вызывается на каждом кадре игры. Для синхронизации анимации используется переменная this.time, вычисляемая из номера кадра.
this.time = this.frame / 60;
if (this.time * 60 | this.frame - 1 === 0)
{
this.time += 0.000001;
}
this.frame++;
this.u(this.time);
Условная конструкция if — это технический трюк для избежания деления на ноль или схожих артефактов в очень специфических математических вычислениях при time, равном 0. Основная работа по отрисовке делегируется методу `u, куда передается текущее времяt`.
Функция отрисовки и математическая магия
Ядро примера — метод u(t). Он полностью очищает холст (через присваивание c.width), а затем рисует сложный паттерн, используя тригонометрические функции Math.sin и Math.tan. Код написан в сжатом, 'dwitter'-стиле (отсылка к платформе dwitter.net для миниатюрных визуализаций).
c.width = 1920; p = Math.PI * 2; x.beginPath(); a = 540; for (i = 0; i < 480; i++) { b = p * (i / 480) * T(t / 4); x.lineTo(0,a + a * S(b)); x.lineTo(i * 4,a + a * -S(b)); }x.stroke();
Давайте развернем эту логику для ясности:
1. c.width = 1920 — быстрый способ очистки Canvas.
2. В цикле for вычисляется угол `b, который зависит от индексаiи времениt`.
3. x.lineTo() рисует линии, создающие зеркальный волнообразный узор. Синусоида S(b) определяет смещение по оси Y, создавая колебания.
4. x.stroke() выполняет обводку нарисованного пути.
Вспомогательная функция `Rпросто формирует строку цвета в формате CSSrgba()`.
return `rgba(${r|0},${g|0},${b|0},${a})`;
Оператор |0 (побитовое ИЛИ с нулем) — это быстрый способ преобразования числа в целое (integer).
Конфигурация игры и запуск
Ключевая настройка для этого примера — type: Phaser.CANVAS. Это указывает Phaser использовать рендерер на основе Canvas API вместо WebGL. Поскольку наша динамическая текстура также рисуется на Canvas, это обеспечивает максимальную совместимость.
const config = {
width: 960,
height: 540,
type: Phaser.CANVAS,
parent: 'phaser-example',
backgroundColor: '#ffffff',
scene: Example
};
const game = new Phaser.Game(config);
Размеры игры (960x540) в два раза меньше размеров холста текстуры (1920x1080), поэтому изображение масштабируется в setScale(0.5) для сохранения четкости.
Что попробовать дальше
Использование Canvas Texture в Phaser — мощный приём для динамической генерации графики. Вы можете создавать текстуры для частиц, фонов, уникальных интерфейсов или, как в этом примере, абстрактные анимации. Экспериментируйте: измените математические функции в методе `u(попробуйтеMath.cosилиMath.atan2), варьируйте количество итераций в цикле, добавьте цвет черезx.strokeStyle` или рисуйте не линии, а фигуры. Это прямой путь к созданию неповторимого визуального стиля вашей игры без единого графического файла.
