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

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

Версия 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('pic', 'assets/pics/brilliance-jim-sachs.png');
        this.load.image('logo', 'assets/sprites/phaser3-logo.png');
        this.load.image('red', 'assets/sprites/128x128.png');
        this.load.spritesheet('mummy', 'assets/sprites/metalslug_mummy37x45.png', { frameWidth: 37, frameHeight: 45, endFrame: 17 });

    }

    create ()
    {
        const g = this.add.graphics().setDepth(1);
        this.input.on('pointermove', (pointer) => {
            g.clear();
            g.lineStyle(2, 0xffffff);
            g.strokeRect(pointer.x - 2, pointer.y - 2, 132, 132);

        });

        this.input.on('pointerdown', (pointer) => {

            const textureManager = this.textures;

            this.game.renderer.snapshotArea(pointer.x, pointer.y, 128, 128, (image) =>
            {
                document.body.appendChild(image);

                if (textureManager.exists('area'))
                {
                    textureManager.remove('area');
                }

                textureManager.addImage('area', image);

                // particles.setTexture('area');
            });

        });

        //  Everything from here down is just stuff to display, so you can grab from it
        const bg = this.add.image(400, 455, 'pic').setScale(1.1);

        this.textures.get('mummy').setFilter(1);

        this.tweens.add({
            targets: bg,
            y: 100,
            duration: 5000,
            ease: 'Sine.easeInOut',
            repeat: -1,
            yoyo: true
        });

        this.anims.create({
            key: 'run',
            frames: 'mummy',
            frameRate: 20,
            repeat: -1
        });

        const container = this.add.container(-100, 0);

        container.setScale(2);

        const sprites = [];

        let x = 0;
        let y = 50;

        for (let i = 1; i <= 18; i++)
        {
            const sprite = this.add.sprite(x, y, 'mummy').play('run');

           y  += 100;

            sprites.push(sprite);

            if (i % 3 === 0)
            {
                x -= 100;
                y = 50;
            }
        }

        container.add(sprites);

        this.tweens.add({
            targets: container,
            x: 1800,
            duration: 20000,
            repeat: -1
        });

        const particles = this.add.particles(0, 0, 'red', {
            lifespan: 2000,
            speed: { min: 100, max: 200 },
            scale: { start: 1, end: 0 },
            quantity: 2,
            frequency: 500
        });

        const logo = this.physics.add.image(400, 100, 'logo');

        logo.setSize(128, 90);
        logo.setVelocity(100, 200);
        logo.setBounce(1, 1);
        logo.setCollideWorldBounds(true);

        particles.startFollow(logo);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 200 }
        }
    },
    scene: Example
};

const game = new Phaser.Game(config);

Как работает захват области экрана

Ключевой метод в примере — this.game.renderer.snapshotArea(). Он позволяет захватить прямоугольный участок рендера игры и получить его в виде HTMLImageElement. Этот элемент можно добавить на страницу для отладки или, что более полезно, зарегистрировать как текстуру в менеджере текстур Phaser.

this.game.renderer.snapshotArea(pointer.x, pointer.y, 128, 128, (image) => {
    document.body.appendChild(image);
    // ... работа с текстурой
});

Метод принимает координаты X, Y верхнего левого угла, ширину и высоту области, а также callback-функцию, которая получит готовое изображение. В примере область захвата фиксирована (128x128 пикселей) и привязана к положению курсора. Обратите внимание, что координаты отсчитываются от левого верхнего угла всего игрового канваса.

Создание и управление динамической текстурой

Полученное изображение можно добавить в менеджер текстур с помощью this.textures.addImage(). Однако перед этим стоит проверить, не существует ли уже текстура с таким ключом, чтобы избежать конфликтов. В примере это делается через this.textures.exists('area') и this.textures.remove('area').

if (textureManager.exists('area')) {
    textureManager.remove('area');
}
textureManager.addImage('area', image);

После регистрации текстура становится доступной по ключу 'area' и её можно использовать для любых спрайтов, частиц (ParticleEmitter) или других визуальных объектов. В закомментированной строке показано, как можно применить эту текстуру к системе частиц: particles.setTexture('area').

Визуальная отладка и интерактивность

Чтобы пользователь видел, какую область он захватывает, в примере используется графический объект (Graphics). При движении мыши (pointermove) рисуется белая рамка размером 132x132, указывающая на будущую область скриншота. Она следует за курсором.

const g = this.add.graphics().setDepth(1);
this.input.on('pointermove', (pointer) => {
    g.clear();
    g.lineStyle(2, 0xffffff);
    g.strokeRect(pointer.x - 2, pointer.y - 2, 132, 132);
});

Сам захват происходит по событию pointerdown. Вся остальная сцена (анимированный фон, бегущие мумии, двигающийся логотип с системой частиц) служит лишь красочным "донором" для создания интересных динамических текстур. Это демонстрирует, что snapshotArea() захватывает именно текущий кадр рендера, включая все анимации и эффекты.

Практическое применение и ограничения

Динамические текстуры открывают множество возможностей: - **Спецэффекты:** Можно захватить взрыв или магическое свечение и использовать его для других частиц. - **Персонализация:** Игрок может выделить часть экрана как эмблему или флаг. - **Миникарты:** Захват области игрового мира для отображения в углу экрана.

Важно помнить, что snapshotArea() — операция синхронная и может быть ресурсоемкой, особенно при больших размерах области или высокой частоте вызовов. Не стоит вызывать её каждый кадр. Также текстура, добавленная через addImage(), существует только в оперативной памяти (RAM) и не сохраняется автоматически.

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

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