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

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

Версия 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.atlas('jellies', 'assets/atlas/jellies.png', 'assets/atlas/jellies.json');
    }

    create ()
    {
        const jelly1 = this.add.image(150, 400, 'jellies', 'WithShadow/Jelly1').setScale(0.5);
        const jelly2 = this.add.image(400, 400, 'jellies', 'WithShadow/Jelly2').setScale(0.5);
        const jelly3 = this.add.image(650, 400, 'jellies', 'WithShadow/Jelly3').setScale(0.5);

        const bubble1 = this.createSpeechBubble(20, 180, 220, 80, 'Global Handler!');
        const bubble2 = this.createSpeechBubble(290, 180, 220, 80, 'Global Key Code!');
        const bubble3 = this.createSpeechBubble(560, 180, 220, 80, 'Local Handler!');

        bubble1.setVisible(false);
        bubble2.setVisible(false);
        bubble3.setVisible(false);

        this.add.text(10, 10, 'Press the SPACE BAR', { font: '16px Courier', fill: '#ffffff' }).setShadow(1, 1);

        const spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);

        //  Phase 1: Key event.
        //  Emits only when the SPACE BAR is pressed down, and dispatches from the local Key object.
        //  You can call stopPropagation at this level, which will stop it reaching both listeners below.

        spaceKey.on('down', (key, event) =>
        {

            // event.stopPropagation();

            bubble3.setVisible(true);

        });

        //  Phase 2: Global keydown + keycode handler.
        //  Emits only on the SPACE BAR keycode event, but dispatches globally.
        //  You can call stopPropagation at this level, which will stop it reaching the listener below.

        this.input.keyboard.on('keydown-SPACE', event =>
        {

            event.stopPropagation();

            bubble2.setVisible(true);

        });

        //  Phase 3: Global keydown handler.
        //  Fires on ANY key press, so we need to check the keyCode internally.

        this.input.keyboard.on('keydown', event =>
        {

            if (event.keyCode === Phaser.Input.Keyboard.KeyCodes.SPACE)
            {
                bubble1.setVisible(true);
            }

        });
    }

    createSpeechBubble (x, y, width, height, quote)
    {
        const bubbleWidth = width;
        const bubbleHeight = height;
        const bubblePadding = 10;
        const arrowHeight = bubbleHeight / 3;

        const bubble = this.add.graphics({ x: x, y: y });

        //  Bubble shadow
        bubble.fillStyle(0x222222, 0.5);
        bubble.fillRoundedRect(6, 6, bubbleWidth, bubbleHeight, 16);

        //  Bubble color
        bubble.fillStyle(0xffffff, 1);

        //  Bubble outline line style
        bubble.lineStyle(4, 0x565656, 1);

        //  Bubble shape and outline
        bubble.strokeRoundedRect(0, 0, bubbleWidth, bubbleHeight, 16);
        bubble.fillRoundedRect(0, 0, bubbleWidth, bubbleHeight, 16);

        //  Calculate arrow coordinates
        const point1X = Math.floor(bubbleWidth / 4);
        const point1Y = bubbleHeight;
        const point2X = Math.floor((bubbleWidth / 4) * 1.4);
        const point2Y = bubbleHeight;
        const point3X = Math.floor(bubbleWidth / 4);
        const point3Y = Math.floor(bubbleHeight + arrowHeight);

        //  Bubble arrow shadow
        bubble.lineStyle(4, 0x222222, 0.5);
        bubble.lineBetween(point2X - 1, point2Y + 6, point3X + 2, point3Y);

        //  Bubble arrow fill
        bubble.fillTriangle(point1X, point1Y, point2X, point2Y, point3X, point3Y);
        bubble.lineStyle(2, 0x565656, 1);
        bubble.lineBetween(point2X, point2Y, point3X, point3Y);
        bubble.lineBetween(point1X, point1Y, point3X, point3Y);

        const content = this.add.text(0, 0, quote, { fontFamily: 'Arial', fontSize: 20, color: '#000000', align: 'center', wordWrap: { width: bubbleWidth - (bubblePadding * 2) } });

        const b = content.getBounds();

        content.setPosition(bubble.x + (bubbleWidth / 2) - (b.width / 2), bubble.y + (bubbleHeight / 2) - (b.height / 2));

        const container = this.add.container();

        container.add([ bubble, content ]);

        return container;
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и создание объектов

Прежде чем работать с клавиатурой, нужно подготовить сцену. В методе preload загружается атлас спрайтов. В create создаются три изображения медуз и три графических "пузыря" для текста с помощью вспомогательного метода createSpeechBubble. Изначально все пузыри скрыты (setVisible(false)). Также выводится текст-подсказка для игрока.

const jelly1 = this.add.image(150, 400, 'jellies', 'WithShadow/Jelly1').setScale(0.5);
const bubble1 = this.createSpeechBubble(20, 180, 220, 80, 'Global Handler!');
bubble1.setVisible(false);
this.add.text(10, 10, 'Press the SPACE BAR', { font: '16px Courier', fill: '#ffffff' }).setShadow(1, 1);

Фаза 1: Локальный обработчик на объекте клавиши

Самый специфичный и рекомендуемый способ — создать объект для конкретной клавиши с помощью this.input.keyboard.addKey(). Этот метод возвращает объект Key, который можно использовать для проверки состояния (зажата/отпущена) и подписки на его события.

const spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
spaceKey.on('down', (key, event) => {
    // event.stopPropagation();
    bubble3.setVisible(true);
});

Событие 'down' сработает только при нажатии на Пробел и будет диспетчеризовано локально, от самого объекта spaceKey. Важная особенность: вызвав event.stopPropagation() здесь, вы предотвратите всплытие события к более глобальным обработчикам, описанным ниже. Это полезно для изоляции логики.

Фаза 2: Глобальный обработчик по коду клавиши

Phaser позволяет глобально слушать события для конкретной клавиши, используя специальный формат названия события: 'keydown-' + KeyCode. Обработчик назначается непосредственно на this.input.keyboard.

this.input.keyboard.on('keydown-SPACE', event => {
    event.stopPropagation();
    bubble2.setVisible(true);
});

Этот обработчик сработает при нажатии Пробела, но событие является глобальным. Вызов event.stopPropagation() на этом уровне остановит всплытие только к следующему, самому общему обработчику ('keydown'), но не повлияет на локальный spaceKey.on('down'), который срабатывает в предыдущей фазе. Порядок важен!

Фаза 3: Самый общий глобальный обработчик

Метод this.input.keyboard.on('keydown', ...) регистрирует обработчик на любое нажатие любой клавиши. Поэтому внутри функции необходимо вручную проверить код клавиши, используя свойство event.keyCode.

this.input.keyboard.on('keydown', event => {
    if (event.keyCode === Phaser.Input.Keyboard.KeyCodes.SPACE) {
        bubble1.setVisible(true);
    }
});

Этот обработчик срабатывает последним в цепочке (если не остановлено всплытие). Он полезен, когда вам нужно обрабатывать множество клавиш в одном месте или реализовать систему "горячих клавиш", где комбинация определяется внутри функции.

Порядок срабатывания и всплытие событий

В примере наглядно показана строгая последовательность (или фазы) обработки: 1. **Локальный Key.on('down')** (для Пробела). 2. **Глобальный keydown-SPACE** (для Пробела). 3. **Глобальный keydown** (для любой клавиши).

Событие "всплывает" от самого конкретного обработчика к самому общему. Метод event.stopPropagation() позволяет прервать это всплытие на любом этапе. В примере он используется во второй фазе, поэтому третий, общий обработчик, никогда не получит событие о нажатии Пробела. Однако первый обработчик сработает в любом случае, так как находится вне этой цепочки глобального всплытия.

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

Выбор метода зависит от задачи. Для управления персонажем (WASD) идеально подходит создание объектов Key. Для глобальных команд (пауза, меню) используйте keydown-KEYCODE. Общий обработчик keydown пригодится для отладки или сложных комбинаций. Попробуйте изменить порядок вызовов on() в коде или раскомментировать stopPropagation() в разных фазах, чтобы увидеть, как меняется поведение. Экспериментируйте, создавая систему ввода, где одна клавиша активирует способность, но только если не зажата другая (например, Shift для усиления).