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

Создание текстур на лету — мощный инструмент для оптимизации и динамического контента в играх. Вместо загрузки десятков изображений вы можете генерировать их программно, экономя память и время загрузки. В этой статье мы разберем, как создать Canvas-текстуру, нарисовать на ней градиент и анимировать её обновление, используя встроенные возможности Phaser.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    texture;

    create ()
    {
        this.texture = this.textures.createCanvas('gradient', 16, 256);

        //  We can access the underlying Canvas context like this:
        const grd = this.texture.context.createLinearGradient(0, 0, 0, 256);

        grd.addColorStop(0, '#8ED6FF');
        grd.addColorStop(1, '#004CB3');

        this.texture.context.fillStyle = grd;
        this.texture.context.fillRect(0, 0, 16, 256);

        //  Call this if running under WebGL, or you'll see nothing change
        this.texture.refresh();

        //  Add a bunch of images that all use the same texture
        for (let i = 0; i < 64; i++)
        {
            const image = this.add.image(8 + i * 16, 0, 'gradient');

            this.tweens.add({
                targets: image,
                y: 650,
                duration: 2000,
                ease: 'Quad.easeInOut',
                delay: i * 62.5,
                yoyo: true,
                repeat: -1
            });
        }

        this.time.addEvent({ delay: 4000, callback: this.updateTexture, callbackScope: this, loop: true });
    }

    updateTexture ()
    {
        const grd = this.texture.context.createLinearGradient(0, 0, 0, 256);

        grd.addColorStop(0, this.generateHexColor());
        grd.addColorStop(1, this.generateHexColor());

        this.texture.context.fillStyle = grd;
        this.texture.context.fillRect(0, 0, 16, 256);

        //  Call this if running under WebGL, or you'll see nothing change
        this.texture.refresh();
    }

    generateHexColor ()
    {
        return `#${((0.5 + 0.5 * Math.random()) * 0xFFFFFF << 0).toString(16)}`;
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Создание Canvas-текстуры: основа динамики

Phaser позволяет создавать текстуры прямо в коде, используя HTML5 Canvas. Это особенно полезно для генерации фонов, простых форм или текстур, которые меняются во время игры.

Ключевой метод — this.textures.createCanvas(). Он создает новый Canvas элемент, который становится текстурой, доступной для использования в спрайтах и изображениях.

this.texture = this.textures.createCanvas('gradient', 16, 256);

В этом примере мы создаем текстуру с ключом 'gradient', шириной 16 пикселей и высотой 256. После создания мы получаем доступ к её контексту рисования, чтобы работать с ней как с обычным Canvas.

Рисование на текстуре: работа с контекстом

После создания текстуры мы можем использовать стандартный Canvas API для рисования. Объект this.texture.context предоставляет доступ к контексту CanvasRenderingContext2D.

В примере рисуется вертикальный линейный градиент, который заполняет всю текстуру.

const grd = this.texture.context.createLinearGradient(0, 0, 0, 256);
grd.addColorStop(0, '#8ED6FF');
grd.addColorStop(1, '#004CB3');
this.texture.context.fillStyle = grd;
this.texture.context.fillRect(0, 0, 16, 256);

После рисования критически важно вызвать метод refresh(), если игра использует WebGL-рендерер. Этот метод сообщает графическому конвейеру, что текстура обновилась и её нужно перезагрузить в видеопамять.

this.texture.refresh();

Использование и анимация спрайтов с новой текстурой

Созданная текстура становится доступной в менеджере текстур Phaser под переданным ключом. Её можно использовать для любых сущностей, принимающих ключ текстуры, например, this.add.image().

В примере создается ряд изображений, которые используют нашу Canvas-текстуру 'gradient'. Каждому изображению добавляется твин (анимация) для движения по оси Y.

const image = this.add.image(8 + i * 16, 0, 'gradient');
this.tweens.add({
    targets: image,
    y: 650,
    duration: 2000,
    ease: 'Quad.easeInOut',
    delay: i * 62.5,
    yoyo: true,
    repeat: -1
});

Важно понимать, что все эти изображения ссылаются на одну и ту же текстуру в памяти. Изменение исходной Canvas-текстуры мгновенно отразится на всех спрайтах, которые её используют.

Динамическое обновление текстуры во время выполнения

Настоящая сила Canvas-текстур раскрывается, когда их содержимое можно менять после создания. В примере по таймеру каждые 4 секунды вызывается функция updateTexture.

Она заново генерирует градиент, используя два случайных цвета, и перерисовывает текстуру.

this.time.addEvent({ delay: 4000, callback: this.updateTexture, callbackScope: this, loop: true });

Функция обновления повторяет процесс рисования: создает градиент, устанавливает его как стиль заливки, заполняет прямоугольник и, что важно, снова вызывает refresh().

grd.addColorStop(0, this.generateHexColor());
grd.addColorStop(1, this.generateHexColor());
this.texture.context.fillStyle = grd;
this.texture.context.fillRect(0, 0, 16, 256);
this.texture.refresh();

После вызова refresh() все 64 изображения на сцене моментально обновляются, отображая новый градиент. Это эффективно и не требует обновления каждого спрайта по отдельности.

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

Canvas-текстуры в Phaser — это мост между гибкостью программируемой графики и производительностью аппаратного рендеринга. Они идеально подходят для динамических фонов, процедурно генерируемых элементов интерфейса, спецэффектов и текстур, реагирующих на игровые события. Для экспериментов попробуйте: генерировать текстуры шума для эффектов дыма или воды, создавать динамические текстуры для индикаторов здоровья, которые меняют цвет и форму, или рисовать простые фигуры (круги, полосы) для использования в частицах.