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

Иногда статичных спрайтов и готовых анимаций недостаточно. Вам нужна текстура, которая меняется в реальном времени в зависимости от игровой логики, математических функций или действий игрока. Phaser предоставляет мощный инструмент — Canvas Textures, позволяющий программно генерировать и изменять изображения прямо во время выполнения игры. Эта статья покажет, как создать динамический фоновый эффект, похожий на абстрактную волну цвета, используя всего несколько строк кода. Вы научитесь работать с `this.textures.createCanvas`, управлять пикселями через контекст рисования и обновлять текстуру каждый кадр, что открывает двери для создания процедурных анимаций, симуляций частиц или уникальных визуальных эффектов без подготовки графики заранее.

Версия 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 s;
        let i;

        s = 10; x.drawImage(c,s,0); for (i = 0; i <= c.height; i += s) { x.fillRect(0,i,s,s,x.fillStyle = `hsl(${i / c.height * C(t * i * 3) * 255},100%,50%)`); }
    }
}

const config = {
    width: 960,
    height: 540,
    type: Phaser.CANVAS,
    parent: 'phaser-example',
    backgroundColor: '#ffffff',
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка Canvas Texture

Всё начинается с создания текстуры на основе элемента HTML Canvas. В методе create() сцены мы используем встроенный API Phaser для генерации такой текстуры.

const canvasTexture = this.textures.createCanvas('dwitter', 1920, 1080);

Здесь createCanvas принимает ключ текстуры ('dwitter') и её размеры. Phaser создаёт скрытый canvas-элемент и регистрирует его как текстуру, которую можно использовать как любой другой спрайт.

this.c = canvasTexture.getSourceImage();
this.x = this.c.getContext('2d');

Метод getSourceImage() возвращает ссылку на сам HTMLCanvasElement, а getContext('2d') даёт нам инструмент для рисования на нём — классический 2D контекст.

this.add.image(0, 0, 'dwitter').setOrigin(0).setScale(0.5);

Наконец, мы добавляем изображение, которое использует нашу созданную текстуру. Установка .setOrigin(0) привязывает точку отображения к левому верхнему углу, а .setScale(0.5) уменьшает текстуру, чтобы она поместилась в окно игры размером 960x540.

Цикл обновления и управление временем

Для анимации необходимо обновлять текстуру каждый кадр. За это отвечает метод update(), который Phaser вызывает автоматически.

this.time = this.frame / 60;

Здесь мы вычисляем текущее время в секундах, предполагая 60 кадров в секунду. Переменная this.frame увеличивается каждый вызов.

if (this.time * 60 | this.frame - 1 === 0)
{
    this.time += 0.000001;
}

Эта проверка — технический трюк для предотвращения возможных ошибок при очень малых значениях времени. Оператор `|` (побитовое ИЛИ) приводит результат к целому числу, обеспечивая стабильность вычислений.

this.frame++;
this.u(this.time);

После инкремента счётчика кадров мы вызываем основной метод отрисовки `u`, передавая ему текущее время.

Функция генерации цвета

Перед тем как рисовать, нам нужен удобный способ задавать цвет. Метод `R` генерирует строку в формате RGBA для контекста canvas.

R (r, g, b, a)
{
    a = a === undefined ? 1 : a;
    return `rgba(${r|0},${g|0},${b|0},${a})`;
}

Параметр `a(альфа, прозрачность) по умолчанию равен 1 (непрозрачный). Выражениеr|0` использует побитовое ИЛИ с нулём для быстрого приведения числа к целому, что полезно при работе с результатами тригонометрических функций.

Сердце эффекта: процедурная отрисовка

Вся магия происходит в методе u(t). Он отвечает за рисование анимированного градиента или волны.

s = 10; x.drawImage(c,s,0); for (i = 0; i <= c.height; i += s) { x.fillRect(0,i,s,s,x.fillStyle = `hsl(${i / c.height * C(t * i * 3) * 255},100%,50%)`); }

Давайте разберём эту компактную строку по шагам: 1. s = 10 — устанавливает размер квадрата (10x10 пикселей). 2. x.drawImage(c,s,0) — сдвигает всё текущее изображение на canvas на 10 пикселей вправо. Это создаёт эффект плавного горизонтального скроллинга влево. 3. Цикл for проходит по высоте canvas с шагом в 10 пикселей. 4. x.fillRect(0,i,s,s,...) — рисует квадрат в левом столбце на высоте `i`. 5. x.fillStyle =hsl(...)`` — определяет цвет квадрата.

Цвет вычисляется в модели HSL: - **Hue (Оттенок)**: i / c.height * C(t * i * 3) * 255. Значение зависит от вертикальной позиции (`i), времени (t) и косинуса (C`). Косинус создаёт волнообразное изменение оттенка по вертикали, которое анимируется со временем. - **Saturation (Насыщенность)**: 100% — максимальная. - **Lightness (Яркость)**: 50% — средняя.

Именно комбинация C(t * i * 3) делает оттенок зависимым и от времени, и от позиции, создавая живую, «плывущую» цветовую волну.

Настройка игры и конфигурация

Пример завершается стандартной для Phaser конфигурацией игры.

const config = {
    width: 960,
    height: 540,
    type: Phaser.CANVAS,
    parent: 'phaser-example',
    backgroundColor: '#ffffff',
    scene: Example
};

Здесь важно указать type: Phaser.CANVAS, так как мы активно работаем с 2D контекстом. Фон установлен белым ('#ffffff'), чтобы контрастировать с нашей динамической текстурой.

const game = new Phaser.Game(config);

Последняя строка создаёт экземпляр игры, запуская весь описанный цикл.

Что попробовать дальше

Вы изучили, как превратить HTML Canvas в живую, обновляемую текстуру Phaser. Ключевые инструменты — this.textures.createCanvas для создания и getContext('2d') для рисования. **Идеи для экспериментов:** 1. Замените C(t * i * 3) на S(t * i * 0.5) (синус) или T(t + i) (тангенс) для других волновых паттернов. 2. Измените fillRect на fillCircle, используя arc, для создания вертикальных рядов кругов. 3. Вместо вертикального градиента попробуйте горизонтальный: измените цикл для прохода по ширине и рисуйте столбцы. 4. Добавьте влияние мыши: используйте this.input.activePointer.x для вычисления оттенка, создавая интерактивный визуализатор.