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

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

Версия 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('gradient24', "assets/skies/gradient24.png");
        this.load.image('phaser2', 'assets/sprites/phaser2.png');
    }

    create ()
    {
        this.add.image(400, 300, "gradient24");

        this.sprite = this.add.image(400, 400, "phaser2");
        this.sprite.enableFilters();
        this.glow = this.sprite.filters.internal.addGlow();
        this.glow.setPaddingOverride(null); // Add auto-padding.

        this.add.text(400, 100, "Read me in the cameras", {
            fontSize: 64,
            fontFamily: "Sans-serif",
            color: "#7f00ff",
        }).setOrigin(0.5, 0.5);

        // Capture the current render stage.
        this.cameras.main.setForceComposite(true);
        this.add.captureFrame("capture");
        console.log(this);

        // Display the captured scene.
        // Note, the texture will be the same size as the game,
        // so the "screens" are scaled down here.
        // To capture a different size, you can use the renderTexture method.
        this.camera1 = this.add.image(120, 120, "capture").setScale(0.25).setRotation(-0.1);
        this.camera2 = this.add.image(400, 100, "capture").setScale(0.25);
        this.camera3 = this.add.image(680, 120, "capture").setScale(0.25).setRotation(0.1);

        this.camera1.enableFilters().filters.external.addBlocky(16);
        this.camera2.enableFilters().filters.external.addGlow();
        this.camera3.enableFilters().filters.external.addShadow();
    }

    update (time)
    {
        this.sprite.rotation = Math.sin(time / 1000) * 0.1;
        this.glow.scale = 2 + 2 * Math.sin(time / 123);
        this.camera1.rotation = -0.1 + Math.sin(time / 917) * 0.05;
        this.camera3.scale = 0.25 + Math.sin(time / 1111) * 0.1;
    }
}

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

const game = new Phaser.Game(config);

Суть примера: `add.captureFrame`

Ключевой метод в этом примере — this.add.captureFrame("capture"). Он делает снимок того, что в данный момент отрендерила основная камера (this.cameras.main), и сохраняет его как текстуру в кэше текстур игры под указанным ключом ("capture"). После этого к текстуре можно обращаться как к обычному изображению.

Важный нюанс: перед захватом вызывается this.cameras.main.setForceComposite(true). Это заставляет рендерер гарантированно отрисовать все элементы сцены (включая фильтры) в один проход перед захватом. Без этого фильтры, примененные к спрайтам, могли бы не попасть в итоговый снимок.

this.cameras.main.setForceComposite(true);
this.add.captureFrame("capture");

От снимка к объектам: создание 'камер'

После того как текстура "capture" создана, мы можем отобразить ее на сцене несколько раз, создавая иллюзию нескольких камер, показывающих одну и ту же сцену. В примере создается три таких объекта-изображения.

Каждый объект масштабируется (setScale(0.25)), чтобы имитировать уменьшенное изображение, и позиционируется в разных частях экрана. Им также задается небольшой поворот для визуального разнообразия.

this.camera1 = this.add.image(120, 120, "capture").setScale(0.25).setRotation(-0.1);
this.camera2 = this.add.image(400, 100, "capture").setScale(0.25);
this.camera3 = this.add.image(680, 120, "capture").setScale(0.25).setRotation(0.1);

Важно понимать: это статичный снимок, сделанный в момент вызова captureFrame. Объекты на исходной сцене могут продолжать двигаться, но их 'отражение' в этих 'камерах' останется неизменным, пока мы не сделаем новый захват.

Наложение фильтров на 'камеры'

Phaser позволяет применять фильтры постобработки как к исходным игровым объектам (внутренние фильтры), так и к их конечному отображению на экране (внешние фильтры). В этом примере используется и то, и другое.

1. **Внутренний фильтр (Internal Filter) для основного спрайта:** Фильтр свечения (addGlow()) применяется к самому спрайту phaser2 и влияет на то, как он будет выглядеть на исходном рендере, а значит, и на захваченной текстуре.

this.sprite.enableFilters();
    this.glow = this.sprite.filters.internal.addGlow();

2. **Внешние фильтры (External Filter) для 'камер':** К каждому изображению-снимку применяется свой внешний фильтр. Эти фильтры работают с уже готовым изображением текстуры "capture". Таким образом, мы можем по-разному стилизовать каждый экземпляр одного и того же снимка.

this.camera1.enableFilters().filters.external.addBlocky(16);
    this.camera2.enableFilters().filters.external.addGlow();
    this.camera3.enableFilters().filters.external.addShadow();

Динамика: анимация параметров

Чтобы сцена не выглядела статичной, в методе update анимируются различные свойства. Это демонстрирует, что 'камеры' — это полноценные игровые объекты, которыми можно управлять.

- Вращается исходный спрайт phaser2 и меняется масштаб его свечения. - Изменяется поворот первой 'камеры' и масштаб третьей. Обратите внимание, что анимация масштаба применяется уже после начального setScale(0.25), создавая пульсирующий эффект.

update (time) {
    this.sprite.rotation = Math.sin(time / 1000) * 0.1;
    this.glow.scale = 2 + 2 * Math.sin(time / 123);
    this.camera1.rotation = -0.1 + Math.sin(time / 917) * 0.05;
    this.camera3.scale = 0.25 + Math.sin(time / 1111) * 0.1;
}

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

Метод captureFrame — это мощный инструмент для создания сложных композиций и эффектов в Phaser. Он позволяет 'заморозить' момент игры и творчески его переиспользовать. Для экспериментов попробуйте: вызывать захват кадра не один раз в create, а периодически в update, создавая эффект live-мини-карты; использовать this.textures.renderTexture для захвата области с другим размером или в другую текстуру; комбинировать несколько захватов с разных камер для построения нестандартных интерфейсов.