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

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

Версия 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.setPath('assets/input');
        this.load.image([ 'p', 'h', 'a', 's', 'e', 'r' ]);
    }

    create ()
    {
        this.input.addPointer(6);

        const p = this.add.image(0, 0, 'p').setInteractive();
        const h = this.add.image(0, 0, 'h').setInteractive();
        const a = this.add.image(0, 0, 'a').setInteractive();
        const s = this.add.image(0, 0, 's').setInteractive();
        const e = this.add.image(0, 0, 'e').setInteractive();
        const r = this.add.image(0, 0, 'r').setInteractive();

        Phaser.Actions.GridAlign([ p, h, a, s, e, r ], {
            width: 6,
            cellWidth: 132,
            cellHeight: 200,
            x: 68,
            y: 300
        });

        const text = this.add.text(10, 10, `${Phaser.VERSION} + v3`, { font: '16px Courier', fill: '#000000' });

        this.input.on('pointermove', function (pointer)
        {

            text.setText(this.input._over[pointer.id].length);

        }, this);

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

            gameObject.setTint(0x00ff00, 0x00ff00, 0xff0000, 0xff0000).setTintMode(Phaser.TintModes.FILL);

        });

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

            gameObject.clearTint();

        });
    }
}

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

const game = new Phaser.Game(config);

Добавление дополнительных указателей

По умолчанию Phaser создает два указателя: один для мыши, второй для первого касания. Для поддержки нескольких одновременных касаний нужно явно добавить новые указатели. Это делается в методе create сцены.

this.input.addPointer(6);

Этот вызов добавляет в систему ввода 6 дополнительных указателей. Теперь общее количество доступных указателей (включая два стандартных) станет 8. Это позволяет отслеживать до 8 одновременных касаний на сенсорном экране.

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

В примере создается 6 спрайтов-букв, каждый из которых помечается как интерактивный. Без вызова .setInteractive() объект не будет реагировать на события ввода.

const p = this.add.image(0, 0, 'p').setInteractive();
// ... и так для букв h, a, s, e, r

Чтобы красиво расположить объекты на экране, используется утилита Phaser.Actions.GridAlign. Она принимает массив объектов и конфигурацию сетки.

Phaser.Actions.GridAlign([ p, h, a, s, e, r ], {
    width: 6,
    cellWidth: 132,
    cellHeight: 200,
    x: 68,
    y: 300
});

Параметры width: 6 и количество объектов (6) задают сетку 1x6. Объекты будут выровнены в один ряд с заданными отступами между ячейками.

Отслеживание количества объектов под указателем

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

this.input.on('pointermove', function (pointer) {
    text.setText(this.input._over[pointer.id].length);
}, this);

При каждом движении указателя (или пальца) текст на экране обновляется. this.input._over — это внутренний массив Phaser, где для каждого pointer.id хранится список игровых объектов, над которыми в данный момент находится этот указатель. Мы берем длину этого списка и отображаем ее. Это полезно для отладки и понимания, как система ввода видит пересечения.

Визуальная обратная связь: события over и out

Чтобы игрок видел, на какой объект он навел палец, используются события gameobjectover и gameobjectout. Они срабатывают, когда указатель входит в зону объекта и выходит из нее.

При наведении объект окрашивается в два цвета с помощью setTint. Режим Phaser.TintModes.FILL означает, что тинт полностью заменяет исходные цвета текстуры.

this.input.on('gameobjectover', (pointer, gameObject) => {
    gameObject.setTint(0x00ff00, 0x00ff00, 0xff0000, 0xff0000).setTintMode(Phaser.TintModes.FILL);
});

Цвета задаются для каждого угла спрайта (верхний левый, верхний правый, нижний левый, нижний правый). В примере верхняя половина становится зеленой (0x00ff00), а нижняя — красной (0xff0000).

При уходе указателя тинт сбрасывается.

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

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

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