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

В процессе разработки игры часто возникает необходимость сделать моментальный снимок текущего состояния сцены — для создания превью, отправки отчета об ошибке или реализации системы сохранения прогресса через скриншоты. Phaser 3 предоставляет простой и мощный метод `snapshot()` для захвата текущего кадра рендерера. В этой статье мы разберем, как не только сделать такой снимок и добавить его на страницу, но и преобразовать его в текстовый формат base64, который можно легко сохранить на сервере или в локальном хранилище браузера.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('myImage', 'assets/sprites/phaser1.png');
        this.load.glsl('fireball', 'assets/shaders/shader0.frag');
    }

    create ()
    {
        this.add.shader('fireball', 400, 300, 800, 600);

        for (let i = 0; i < 16; ++i)
        {
            this.add.image(Math.random() * 800, Math.random() * 600, 'myImage');
        }

        this.input.once('pointerdown', () => {

            this.renderer.snapshot(image => {

                //  For testing to see if the snapshot worked:
                document.body.appendChild(image);

                const snap = this.textures.createCanvas('snap', image.width, image.height);

                snap.draw(0, 0, image);

                const base64 = snap.canvas.toDataURL();

                console.log(base64);

            });

        });
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и метод `snapshot()`

Основной инструмент для захвата кадра находится в рендерере игры. Метод this.renderer.snapshot() принимает функцию обратного вызова, единственным аргументом которой будет объект HTMLImageElement, содержащий изображение текущего состояния canvas.

Важно помнить, что для использования шейдеров и метода snapshot требуется контекст WebGL. Поэтому в конфигурации игры должен быть указан type: Phaser.WEBGL.

Следующий код загружает текстуру и шейдер, а затем по клику мыши делает снимок и временно добавляет его в тело HTML-документа для визуальной проверки.

this.input.once('pointerdown', () => {
    this.renderer.snapshot(image => {
        //  Для проверки работоспособности снимка:
        document.body.appendChild(image);
    });
});

Создание текстуры из снимка

Просто получить изображение — лишь половина дела. Чтобы дальше работать с ним внутри Phaser (например, отрисовать как спрайт) или преобразовать в другие форматы, изображение нужно превратить в текстуру. Для этого используется метод this.textures.createCanvas(), который создает пустую canvas-текстуру с заданным именем и размерами.

Затем, используя метод .draw() этой текстуры, мы помещаем наше изображение в ее внутренний canvas. Теперь снимок доступен в менеджере текстур игры под ключом 'snap', и его можно использовать, например, так: this.add.image(400, 300, 'snap').

const snap = this.textures.createCanvas('snap', image.width, image.height);
snap.draw(0, 0, image);

Преобразование в формат base64

Ключевое преимущество canvas-текстуры в том, что мы получаем прямой доступ к DOM-элементу <canvas>. У каждого canvas есть стандартный метод .toDataURL(), который возвращает строку с изображением, закодированным в формате Data URL (base64). Это универсальный текстовый формат, который можно сохранить в localStorage, отправить на сервер через fetch или вставить как источник для тега <img>.

Полученная строка начинается с префикса (например, data:image/png;base64,), за которым следуют сами данные.

const base64 = snap.canvas.toDataURL();
console.log(base64); // Выводит длинную строку base64 в консоль

Полный код примера и конфигурация

Давайте соберем все шаги воедино. В этом примере после клика снимок будет добавлен на страницу, создана соответствующая текстура Phaser, а ее base64-представление выведено в консоль разработчика.

Обратите внимание на конфигурацию игры: для работы шейдера и метода snapshot необходим Phaser.WEBGL.

class Example extends Phaser.Scene {
    preload() {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('myImage', 'assets/sprites/phaser1.png');
        this.load.glsl('fireball', 'assets/shaders/shader0.frag');
    }

    create() {
        this.add.shader('fireball', 400, 300, 800, 600);
        for (let i = 0; i < 16; ++i) {
            this.add.image(Math.random() * 800, Math.random() * 600, 'myImage');
        }

        this.input.once('pointerdown', () => {
            this.renderer.snapshot(image => {
                document.body.appendChild(image);
                const snap = this.textures.createCanvas('snap', image.width, image.height);
                snap.draw(0, 0, image);
                const base64 = snap.canvas.toDataURL();
                console.log(base64);
            });
        });
    }
}

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

const game = new Phaser.Game(config);

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

Метод snapshot() в связке с createCanvas() и toDataURL() открывает множество практических возможностей: от простого создания скриншотов для отладки до реализации сложных функций, таких как система фото-режима в игре или облачное сохранение прогресса через изображение. Для экспериментов попробуйте изменить формат изображения в toDataURL('image/jpeg', 0.8) для сжатия, автоматически отправлять base64 на моковый сервер или создать спрайт из текстуры 'snap' и анимировать его на самой сцене.