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

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

Версия 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('logo', 'assets/sprites/phaser1.png');
        this.load.image('bunny', 'assets/sprites/bunny.png');
        this.load.image('bg', 'assets/pics/platformer-backdrop.png');
        this.load.image('checker', 'assets/pics/checker.png');
    }

    create ()
    {
        const bg = this.add.sprite(400, 300, 'bg').setOrigin(0.5).setScale(2.5);

        const logo = this.make.sprite({key:'logo', add: false}).setScale(0.2);
        const rt = this.make.renderTexture({x: 0, y: 0, width: 800, height: 600, add: false}).setOrigin(0.0);

        const bunny1 = this.add.sprite(400, 300, 'bunny').setTint(0x000000);
        const checker = this.add.sprite(400, 300, 'checker');
        const bunny0 = this.add.sprite(400, 300, 'bunny')

        const bunnyMask = bunny0.enableFilters().filters.external.addMask(rt);
        bunnyMask.invertAlpha = true;
        checker.enableFilters().filters.external.addMask(bunny1);

        this.input.on('pointermove', function (event) {
            if (event.isDown)
            {
                rt.draw(logo, event.x, event.y).render();
            }
        }, this);
    }
}


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

const game = new Phaser.Game(config);

Подготовка сцены и загрузка ресурсов

В методе preload загружаются все необходимые изображения. Обратите внимание на использование this.load.setBaseURL для установки базового URL, что удобно для загрузки ассетов из удаленного репозитория.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('logo', 'assets/sprites/phaser1.png');
    this.load.image('bunny', 'assets/sprites/bunny.png');
    this.load.image('bg', 'assets/pics/platformer-backdrop.png');
    this.load.image('checker', 'assets/pics/checker.png');
}

Затем, в create, создаются основные игровые объекты. Фоновый спрайт bg масштабируется, чтобы заполнить экран. Ключевой момент — создание спрайта logo и объекта Render Texture (rt) с помощью фабрики this.make. Параметр add: false указывает, что эти объекты не должны автоматически добавляться на дисплейный список сцены — они будут использоваться как ресурсы для отрисовки и маскирования.

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

На сцену добавляются три объекта: два спрайта с зайцем (bunny1 и bunny0) и один с текстурой шахматной доски (checker). Они позиционируются в центре экрана. bunny1 сразу окрашивается в черный цвет с помощью .setTint(0x000000).

const bunny1 = this.add.sprite(400, 300, 'bunny').setTint(0x000000);
const checker = this.add.sprite(400, 300, 'checker');
const bunny0 = this.add.sprite(400, 300, 'bunny')

Далее настраивается цепочка масок. Сначала для спрайта bunny0 включается поддержка фильтров через .enableFilters(). Затем к его внешним фильтрам (filters.external) добавляется маска, в качестве которой выступает наш Render Texture (rt). Свойство invertAlpha = true инвертирует альфа-канал маски: там, где rt прозрачен, bunny0 будет виден, и наоборот.

const bunnyMask = bunny0.enableFilters().filters.external.addMask(rt);
bunnyMask.invertAlpha = true;

Аналогично, для спрайта checker добавляется маска в виде черного зайца (bunny1). Теперь checker будет виден только в тех пикселях, где виден bunny1.

Интерактивное рисование в Render Texture

Сердце примера — обработка перемещения указателя. Слушатель события 'pointermove' проверяет, зажата ли кнопка мыши (event.isDown). Если да, то в Render Texture по координатам курсора рисуется маленький логотип Phaser (logo). Метод .render() после .draw() немедленно применяет изменения к текстуре.

this.input.on('pointermove', function (event) {
    if (event.isDown)
    {
        rt.draw(logo, event.x, event.y).render();
    }
}, this);

Поскольку rt является маской для верхнего зайца (bunny0), каждый нарисованный логотип создает в этой маске непрозрачную область. Из-за invertAlpha = true эта область становится "дыркой" в маске, через которую становится виден сам bunny0. Таким образом, пользователь "процарапывает" верхний слой (который на самом деле является маской), открывая изображение под ним. Нижний слой (checker), замаскированный черным зайцем, остается статичным фоном для эффекта.

Конфигурация игры и инициализация

Пример требует использования WebGL-рендерера, так как фильтры и внешние маски поддерживаются только в этом контексте. Это указывается в объекте конфигурации в поле type. Класс нашей сцены (Example) передается в свойство scene. После создания экземпляра Phaser.Game с этой конфигурацией сцена автоматически инициализируется.

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

const game = new Phaser.Game(config);

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

Этот пример демонстрирует гибкость связки Render Texture и системы масок в Phaser 3. Вы можете менять рисунок для "царапины" (например, на кисть с частицами), использовать несколько Render Texture для многослойных эффектов или привязать логику очистки к прогрессу игрока. Попробуйте изменить invertAlpha на false, чтобы инвертировать эффект, или использовать маску для создания динамических областей столкновений, невидимых для игрока.