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

Добавление поддержки геймпада раскрывает новые возможности управления для вашей игры, будь то платформер, шутер или аркада. Эта статья на практическом примере покажет, как в Phaser 3 обрабатывать подключение геймпада и использовать его стики или D-Pad для плавного перемещения спрайтов по экрану. Вы научитесь настраивать систему ввода, корректно обрабатывать момент подключения устройства и читать состояние его кнопок в реальном времени.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    sprites = [];

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('sky', 'assets/skies/lightblue.png');
        this.load.image('elephant', 'assets/sprites/elephant.png');
    }

    create ()
    {
        this.add.image(0, 0, 'sky').setOrigin(0);

        let text;

        if (this.input.gamepad.total === 0)
        {
            text = this.add.text(10, 10, 'Press any button on a connected Gamepad', { font: '16px Courier', fill: '#00ff00' });

            this.input.gamepad.once('connected', function (pad)
            {

                console.log('connected', pad.id);

                for (let i = 0; i < this.input.gamepad.total; i++)
                {
                    this.sprites.push(this.add.sprite(Phaser.Math.Between(200, 600), Phaser.Math.Between(100, 500), 'elephant'));
                }

                text.destroy();

            }, this);
        }
        else
        {
            for (let i = 0; i < this.input.gamepad.total; i++)
            {
                this.sprites.push(this.add.sprite(Phaser.Math.Between(200, 600), Phaser.Math.Between(100, 500), 'elephant'));
            }
        }
    }

    update ()
    {
        const pads = this.input.gamepad.gamepads;

        for (let i = 0; i < pads.length; i++)
        {
            const gamepad = pads[i];

            if (!gamepad)
            {
                continue;
            }

            const sprite = this.sprites[i];

            if (gamepad.left)
            {
                sprite.x -= 4;
                sprite.flipX = false;
            }
            else if (gamepad.right)
            {
                sprite.x += 4;
                sprite.flipX = true;
            }

            if (gamepad.up)
            {
                sprite.y -= 4;
            }
            else if (gamepad.down)
            {
                sprite.y += 4;
            }
        }
    }
}

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

const game = new Phaser.Game(config);

Настройка проекта и базовой конфигурации

Всё начинается с конфигурации игры. Ключевой момент — явное включение системы геймпадов в настройках ввода. Без этого Phaser не будет слушать события от подключённых контроллеров.

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    input: {
        gamepad: true // Включаем поддержку геймпадов
    },
    scene: Example
};

const game = new Phaser.Game(config);

Класс сцены Example содержит три основных метода жизненного цикла: preload, create и update. Также объявляется массив sprites = [] для хранения ссылок на все создаваемые спрайты.

Загрузка ресурсов и первичное создание сцены

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

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('sky', 'assets/skies/lightblue.png');
    this.load.image('elephant', 'assets/sprites/elephant.png');
}

В create сначала добавляется фоновое изображение. Затем логика разделяется в зависимости от того, подключён ли уже геймпад к системе в момент запуска игры.

Обработка подключения геймпада

Phaser предоставляет доступ к количеству подключённых геймпадов через this.input.gamepad.total. Если на старте сцены геймпадов нет (total === 0), мы выводим текстовую подсказку и подписываемся на одноразовое событие connected.

if (this.input.gamepad.total === 0)
{
    text = this.add.text(10, 10, 'Press any button on a connected Gamepad', { font: '16px Courier', fill: '#00ff00' });

    this.input.gamepad.once('connected', function (pad)
    {
        console.log('connected', pad.id);
        // Создание спрайтов после подключения
        for (let i = 0; i < this.input.gamepad.total; i++)
        {
            this.sprites.push(this.add.sprite(Phaser.Math.Between(200, 600), Phaser.Math.Between(100, 500), 'elephant'));
        }
        text.destroy(); // Удаляем подсказку
    }, this);
}

Обратите внимание на третий аргумент this в once. Он задаёт контекст выполнения callback-функции, чтобы внутри неё this ссылался на экземпляр сцены, а не на объект события. Если геймпад уже подключён, спрайты создаются сразу в основном потоке метода create.

Чтение состояния геймпада и управление спрайтом

Основная логика движения находится в методе update, который вызывается на каждом кадре игры. Сначала получаем массив всех геймпадов this.input.gamepad.gamepads. Каждый элемент массива — это объект Gamepad или null, если слот пуст.

update ()
{
    const pads = this.input.gamepad.gamepads;

    for (let i = 0; i < pads.length; i++)
    {
        const gamepad = pads[i];
        if (!gamepad) { continue; } // Пропускаем пустые слоты
        const sprite = this.sprites[i];
        // ... логика движения
    }
}

Объект gamepad содержит булевы свойства left, right, up, down, которые становятся true, когда нажата соответствующая направляющая кнопка или отклонён стик. В примере эти свойства используются для изменения координат спрайта и его отражения по горизонтали.

if (gamepad.left)
{
    sprite.x -= 4;
    sprite.flipX = false;
}
else if (gamepad.right)
{
    sprite.x += 4;
    sprite.flipX = true;
}

if (gamepad.up)
{
    sprite.y -= 4;
}
else if (gamepad.down)
{
    sprite.y += 4;
}

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

Теперь вы умеете подключать геймпад к игре на Phaser 3 и управлять объектами с его помощью. Это основа для создания локального мультиплеера, где каждый игрок использует свой контроллер. Для экспериментов попробуйте

  1. Назначить разным кнопкам (buttons[0], buttons[1]) различные действия (прыжок, атака)
  2. Использовать значения осей аналоговых стиков (axes[0], axes[1]) для более плавного и точного управления скоростью движения
  3. Реализовать вибрацию (gamepad.vibration) в качестве тактильной отдачи