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

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

Версия 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('eye', 'assets/pics/lance-overdose-loader-eye.png');
    }

    create ()
    {
        for (let i = 0; i < 14; i++)
        {
            const s = this.add.sprite(100 + i * 30, 100 + i * 30, 'eye').setInteractive().setName('eye ' + i);

            s.on('pointerdown', () =>
            {
                console.log(s.name, s.x, s.y);

            });
        }

        //  If you disable topOnly it will fire events for all objects the pointer is over
        //  regardless of their place on the display list
        this.input.setTopOnly(false);

        //  Events

        this.input.on('gameobjectdown', (pointer, gameObject) =>
        {
            // console.log(gameObject.name, gameObject.x, gameObject.y);

            gameObject.setTint(0x00ff00);

        });

        this.input.on('gameobjectout', (pointer, gameObject) =>
        {
            gameObject.clearTint();
        });

        this.input.on('gameobjectup', (pointer, gameObject) =>
        {
            gameObject.clearTint();
        });
    }
}

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

const game = new Phaser.Game(config);

Проблема topOnly по умолчанию

По умолчанию в Phaser включен режим topOnly. Это означает, что при клике на несколько перекрывающихся интерактивных объектов событие (например, pointerdown) сработает только для того, который находится выше всех в порядке отображения (display list). Остальные объекты под курсором игнорируются. Это поведение эффективно для большинства UI-элементов, где нужно кликать именно по верхней кнопке или окну.

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

Отключение topOnly: setTopOnly(false)

Ключевой метод для изменения поведения — this.input.setTopOnly(false). Вызов этой функции отключает фильтрацию по верхнему объекту. После этого глобальные события ввода, такие как gameobjectdown, будут срабатывать для каждого интерактивного объекта под указателем мыши или касанием, независимо от его позиции в стеке.

//  Отключаем режим 'только верхний объект'
//  Теперь события будут генерироваться для всех объектов под курсором
this.input.setTopOnly(false);

Важно вызвать этот метод в сцене после создания объектов или в create(). Изменение применяется ко всем последующим событиям.

Глобальные события Input Manager

Когда topOnly отключен, удобно использовать глобальные события менеджера ввода (this.input), а не вешать обработчики на каждый объект отдельно. Это централизует логику и позволяет обрабатывать события от множества объектов в одном месте.

В примере используются события gameobjectdown, gameobjectout и gameobjectup. Они автоматически получают ссылку на объект, с которым произошло взаимодействие.

//  Обработчик нажатия на любой интерактивный объект
this.input.on('gameobjectdown', (pointer, gameObject) => {
    //  gameObject — это спрайт, на который кликнули
    gameObject.setTint(0x00ff00); //  Зеленый оттенок
});

//  Обработчик выхода указателя за пределы объекта
this.input.on('gameobjectout', (pointer, gameObject) => {
    gameObject.clearTint(); //  Убираем оттенок
});

//  Обработчик отпускания кнопки мыши над объектом
this.input.on('gameobjectup', (pointer, gameObject) => {
    gameObject.clearTint();
});

Каждый обработчик получает два аргумента: объект pointer (с информацией о положении, кнопке и т.д.) и сам gameObject, вызвавший событие.

Создание и настройка интерактивных объектов

Чтобы объект мог генерировать события, его нужно сделать интерактивным с помощью setInteractive(). В примере создается несколько спрайтов в цикле, каждый из которых получает уникальное имя и обработчик локального события pointerdown.

for (let i = 0; i < 14; i++) {
    //  Создаем спрайт, делаем интерактивным, задаем имя
    const s = this.add.sprite(100 + i * 30, 100 + i * 30, 'eye').setInteractive().setName('eye ' + i);

    //  Локальный обработчик события (все равно сработает)
    s.on('pointerdown', () => {
        console.log(s.name, s.x, s.y);
    });
}

Обратите внимание: даже при отключенном topOnly локальные обработчики на объектах (через s.on) также будут срабатывать. Это позволяет комбинировать оба подхода.

Практическое применение и нюансы

Отключение topOnly открывает возможности для нестандартных механик: - **Сбор предметов в куче**: клик по верхнему предмету может одновременно подсветить и предметы под ним. - **Слоистая анимация**: несколько перекрывающихся спрайтов могут одновременно реагировать на наведение. - **Сложный UI**: элементы интерфейса, состоящие из нескольких частей, могут обрабатывать события совместно.

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

Также помните, что отключение topOnly может повлиять на производительность, если на сцене очень много интерактивных объектов — событие будет генерироваться для каждого из них под курсором.

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

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