О чем этот пример

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