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

При разработке игр иногда возникает необходимость работать с графическими объектами, размеры которых превышают игровое окно или даже сам canvas. Например, для генерации больших изображений, текстур для масштабных карт или создания ассетов для экспорта. Стандартный подход с отображением в сцене здесь не подходит. В этой статье мы разберем, как использовать `DynamicTexture` для создания, заполнения и сохранения текстур любого размера, не ограничиваясь видимой областью рендерера.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('uv', 'assets/pics/uv-grid-4096-ian-maclachlan.png');
    }

    create ()
    {
        /*
        this.add.image(400, 300, 'uv');

        this.renderer.snapshot(image => {

            document.body.appendChild(image);

        });
        */

        const texture = this.textures.addDynamicTexture('test', 4096, 4096);

        texture.stamp('uv', null, 2048, 2048);

        texture.snapshot(image => {

            document.body.appendChild(image);

        });
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    backgroundColor: '#2d2d6d',
    scene: Example
};

const game = new Phaser.Game(config);

Проблема стандартного подхода

Обычно для работы с изображениями мы создаем игровой объект Image и добавляем его на сцену через this.add.image(). Phaser рендерит его в контекст Canvas, и мы можем сделать снимок этого Canvas с помощью метода this.renderer.snapshot(). Однако этот метод имеет ключевое ограничение: он захватывает только ту область Canvas, которая соответствует размеру игры, заданному в конфигурации (в нашем примере 800x600).

Любой контент, выходящий за пределы этих границ, обрезается или не рендерится вовсе. Таким образом, сохранить изображение, большее, чем игровое окно, стандартным способом невозможно. В закомментированном коде примера как раз показана эта попытка, которая не даст нужного результата для текстуры размером 4096x4096.

Динамические текстуры (DynamicTexture)

Решение — использовать объект DynamicTexture. Это специальный тип текстуры в Phaser, который существует независимо от основного холста игры. Его можно представить как виртуальный холст в памяти, на котором можно рисовать, не заботясь о размерах игрового окна.

В примере текстура создается в методе create() сцены. Давайте разберем процесс создания:

const texture = this.textures.addDynamicTexture('test', 4096, 4096);

* this.textures — это менеджер текстур сцены. * .addDynamicTexture(key, width, height) — метод для создания динамической текстуры. Он принимает: * key ('test') — уникальный строковый идентификатор для последующего доступа к текстуре. * width и height (4096, 4096) — размеры создаваемой текстуры в пикселях. Эти значения могут быть любыми, в том числе значительно превышающими размеры game.config.

Работа с содержимым текстуры: метод stamp

После создания текстура пуста. Чтобы заполнить ее изображением, используется метод .stamp(). В примере на текстуру «ставится штамп» с другим графическим ресурсом.

texture.stamp('uv', null, 2048, 2048);

* 'uv' — ключ изображения, загруженного в preload(). Это исходник для штампа. * null — в этом параметре можно передать индекс кадра или конфиг, но в нашем случае он не требуется. * 2048, 2048 — координаты `xиy` *внутри динамической текстуры*, куда будет помещен центр исходного изображения. Это позволяет точно позиционировать контент на виртуальном холсте большого размера.

Сохранение результата: snapshot текстуры

Самое важное — возможность сохранить результат работы с DynamicTexture в виде обычного изображения (HTMLImageElement). Для этого у динамической текстуры есть собственный метод .snapshot().

texture.snapshot(image => {
    document.body.appendChild(image);
});

* texture.snapshot(callback) — метод создает снимок *всей* динамической текстуры, независимо от ее размера (в нашем случае 4096x4096). * callback — функция, которая получает созданный объект HTMLImageElement в качестве аргумента. В примере это изображение просто добавляется в тело HTML-документа (document.body.appendChild(image)), и его можно увидеть в браузере. На практике вы можете сохранить его на диск, отправить на сервер или использовать для дальнейшей обработки.

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

Использование DynamicTexture — мощный метод для оффскринной графической работы в Phaser. Он снимает ограничения, накладываемые размерами игрового окна, и открывает возможности для генерации контента, создания редакторов карт или подготовки ассетов. **Идеи для экспериментов:** 1. Создайте текстуру и используйте методы texture.fill() или texture.draw() для рисования примитивов и текста. 2. Скомбинируйте несколько изображений, накладывая их друг на друга с разными координатами с помощью stamp(). 3. Сгенерируйте текстуру для тайловой карты огромного размера и сохраните ее как файл для использования вне игры.