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

При разработке игр часто возникает необходимость создавать невидимые интерактивные зоны — триггеры, кнопки-ловушки или области с особым поведением. Однако в Phaser по умолчанию интерактивность отключается для объектов с нулевой прозрачностью (`alpha: 0`). В этой статье мы разберем, как использовать свойство `alwaysEnabled` для обхода этого ограничения и создания гибких интерактивных систем, независимых от визуального отображения объектов.

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

Живой запуск

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

Исходный код


var config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: {
        create: create
    }
};

var game = new Phaser.Game(config);

function create()
{
    //  Test 1

    /*
    var invisibleRect = this.add.rectangle(400, 300, 300, 300, 0x00ff00);
    var rect = this.add.rectangle(400, 300, 256, 256, 0x0000ff);
    var text = this.add.text(128, 128, "0").setFontSize(28);
    var count = 0;

    invisibleRect.name = 'invis';
    invisibleRect.setInteractive();
    invisibleRect.input.alwaysEnabled = true;

    invisibleRect.on('pointerdown', function () {
        count++;
        text.text = count.toString();
    });

    rect.name = 'rect';
    rect.setInteractive();

    invisibleRect.alpha = 0;

    // invisibleRect.setDepth(0);
    rect.setDepth(-1);
    */

    //  Test 2
    var invisibleRect = this.add.rectangle(400, 300, 300, 300, 0x00ff00);
    var rect = this.add.rectangle(400, 300, 256, 256, 0x0000ff).setAlpha(0.5);
    var rect2 = this.add.rectangle(350, 300, 256, 256, 0xff0000).setAlpha(0.5);
    var text = this.add.text(128, 128, "0").setFontSize(28);
    var count = 0;

    invisibleRect.name = 'invis';
    invisibleRect.setInteractive();
    invisibleRect.input.alwaysEnabled = true;

    invisibleRect.on('pointerdown', function () {
        count++;
        text.text = count.toString();
    });

    rect.name = 'rect1';
    rect.setInteractive();

    rect2.name = "rect2"
    rect2.setInteractive();

    invisibleRect.alpha = 0;
    // invisibleRect.alpha = 0.3;

    invisibleRect.setDepth(3);
    rect.setDepth(2);
    rect2.setDepth(1);

    console.log(this.input);
}

Проблема: невидимый объект не реагирует на клики

По умолчанию в Phaser объекты с alpha: 0 (полностью прозрачные) теряют интерактивность, даже если на них установлено setInteractive(). Это логичное поведение для визуальных элементов, но оно становится проблемой, когда нужно создать невидимую «горячую зону». Например, для скрытой кнопки-активатора или области, которая должна срабатывать при наведении курсора.

Рассмотрим базовый пример без alwaysEnabled:

var invisibleRect = this.add.rectangle(400, 300, 300, 300, 0x00ff00);
invisibleRect.setInteractive();
invisibleRect.alpha = 0; // Интерактивность теряется!

Зеленый прямоугольник после установки alpha: 0 перестанет откликаться на события ввода, хотя setInteractive() был вызван.

Решение: включаем alwaysEnabled

Свойство alwaysEnabled — это флаг, который принудительно сохраняет интерактивность объекта, независимо от его визуального состояния. Оно находится в объекте input интерактивного игрового объекта.

Ключевой код решения:

invisibleRect.setInteractive();
invisibleRect.input.alwaysEnabled = true; // Принудительно включаем интерактивность
invisibleRect.alpha = 0; // Теперь объект невидим, но кликабелен

После установки alwaysEnabled: true объект будет обрабатывать события указателя (pointerdown, pointerover и др.), даже если он полностью прозрачен. Это свойство следует устанавливать после вызова setInteractive(), так как объект input создается именно в этот момент.

Практический пример: кликабельный невидимый слой

В исходном коде примера реализована сцена с тремя прямоугольниками разной глубины (depth) и прозрачности. Невидимый зеленый прямоугольник (invisibleRect) расположен поверх всех и обрабатывает клики благодаря alwaysEnabled.

Полный код создания сцены:

function create() {
    var invisibleRect = this.add.rectangle(400, 300, 300, 300, 0x00ff00);
    var rect = this.add.rectangle(400, 300, 256, 256, 0x0000ff).setAlpha(0.5);
    var rect2 = this.add.rectangle(350, 300, 256, 256, 0xff0000).setAlpha(0.5);
    var text = this.add.text(128, 128, "0").setFontSize(28);
    var count = 0;

    invisibleRect.name = 'invis';
    invisibleRect.setInteractive();
    invisibleRect.input.alwaysEnabled = true; // Ключевая строка

    invisibleRect.on('pointerdown', function () {
        count++;
        text.text = count.toString(); // Счетчик увеличивается при клике
    });

    rect.name = 'rect1';
    rect.setInteractive(); // Кликабелен, но перекрыт невидимым слоем

    rect2.name = "rect2";
    rect2.setInteractive();

    invisibleRect.alpha = 0; // Полная прозрачность
    invisibleRect.setDepth(3); // Самый верхний слой
    rect.setDepth(2);
    rect2.setDepth(1);
}

При клике в области пересечения прямоугольников сработает событие pointerdown только у invisibleRect, так как он имеет наибольшую глубину (depth: 3) и интерактивность у него принудительно включена. Синий (rect) и красный (rect2) прямоугольники, несмотря на свою интерактивность, будут перекрыты невидимым слоем.

Взаимодействие с глубиной (Depth) и порядком отрисовки

Свойство alwaysEnabled не отменяет стандартную систему управления порядком отрисовки и обработки ввода в Phaser. Объекты с большим значением depth отображаются поверх других и имеют приоритет в обработке событий.

В примере установлены следующие глубины:

invisibleRect.setDepth(3);
rect.setDepth(2);
rect2.setDepth(1);

Это означает: 1. invisibleRect — верхний слой, его события обрабатываются в первую очередь. 2. Даже если rect и rect2 видимы и интерактивны, клик по ним «перехватывается» невидимым прямоугольником выше. 3. Если бы alwaysEnabled было false, invisibleRect игнорировал бы ввод, и клик обрабатывали бы нижележащие прямоугольники.

Сочетание depth и alwaysEnabled позволяет создавать сложные многослойные интерфейсы, где визуально задние элементы могут быть логически активными, а передние — служить невидимыми масками или триггерами.

Использование и ограничения

Основное применение alwaysEnabled — создание невидимых интерактивных областей: * **Секретные зоны** на карте, активируемые кликом. * **Невидимые кнопки** или триггеры для пазлов. * **Области с особым поведением** (например, увеличение урона в определенной зоне). * **Слои-маски**, которые должны блокировать ввод для объектов под ними, оставаясь невидимыми.

Важное ограничение: alwaysEnabled работает только с объектами, у которых есть компонент Input, т.е. после вызова setInteractive(). Также помните, что это свойство влияет только на интерактивность самого объекта, но не на его детей в контейнере.

Проверить, включена ли принудительная интерактивность, можно через консоль, как это сделано в примере:

console.log(this.input); // Изучаем менеджер ввода
// Или для конкретного объекта:
console.log(invisibleRect.input.alwaysEnabled);

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

Свойство alwaysEnabled — это мощный инструмент для тонкого управления интерактивностью в Phaser, позволяющий отделить логику обработки ввода от визуального представления объекта. Вы можете экспериментировать: создавайте сложные системы триггеров, меняйте depth у объектов динамически, чтобы управлять приоритетом кликов, или комбинируйте невидимые области с системами физики (enableBody) для создания нестандартных игровых механик. Попробуйте применить этот подход для создания скрытых тестовых панелей или интерактивного фона, не мешающего основному UI.