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

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

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

Живой запуск

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

Исходный код


class SceneA extends Phaser.Scene {

    constructor ()
    {
        super('sceneA');

        this.hotdog;
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('pic', 'assets/pics/case.jpg');
        this.load.image('hotdog', 'assets/sprites/hotdog.png');
    }

    create ()
    {
        this.add.image(400, 300, 'pic');

        var hotdog = this.add.image(400, 300, 'hotdog');

        this.add.text(10, 10, 'Scene A. Press arrows to move. Click to change Scene.', { font: '16px Courier', fill: '#00ff00' });

        this.input.keyboard.addCapture('UP, DOWN, LEFT, RIGHT')

        this.input.keyboard.on('keydown_UP', function (event) {

            hotdog.y -= 4;

        }, this);

        this.input.keyboard.on('keydown_DOWN', function (event) {

            hotdog.y += 4;

        }, this);

        this.input.keyboard.on('keydown_LEFT', function (event) {

            console.log('A left');
            hotdog.x -= 4;

        }, this);

        this.input.keyboard.on('keydown_RIGHT', function (event) {

            console.log('A right');
            hotdog.x += 4;

        }, this);

        this.input.on('pointerdown', function () {

            console.log('down');
            this.scene.pause();
            this.scene.run('sceneB');

        }, this);

        this.hotdog = hotdog;
    }

    update ()
    {
        this.hotdog.rotation += 0.001;
    }

}

class SceneB extends Phaser.Scene {

    constructor ()
    {
        super('sceneB');

        this.hotdog;
    }

    create ()
    {
        var graphics = this.add.graphics();

        graphics.fillStyle(0x000000, 0.5);
        graphics.fillRect(0, 0, 800, 600);

        this.add.text(10, 30, 'Scene B. Press arrows to move. Space to change Scene.', { font: '16px Courier', fill: '#00ff00' });

        var hotdog = this.add.image(400, 300, 'hotdog').setTint(0xff0000);

        this.input.keyboard.addCapture('UP, DOWN, LEFT, RIGHT');

        this.input.keyboard.on('keydown_UP', function (event) {

            hotdog.y -= 4;

        }, this);

        this.input.keyboard.on('keydown_DOWN', function (event) {

            hotdog.y += 4;

        }, this);

        this.input.keyboard.on('keydown_LEFT', function (event) {

            console.log('B left');
            hotdog.x -= 4;

        }, this);

        this.input.keyboard.on('keydown_RIGHT', function (event) {

            console.log('B right');
            hotdog.x += 4;

        }, this);

        this.input.keyboard.once('keydown_SPACE', function (event) {

            this.scene.stop();
            this.scene.resume('sceneA');

        }, this);

        this.hotdog = hotdog;
    }

}

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

var game = new Phaser.Game(config);

Инициализация сцен и загрузка ресурсов

В примере определены две сцены: SceneA и SceneB. Каждая наследуется от Phaser.Scene. В конструкторе задаётся уникальный ключ сцены, который используется для её идентификации при переключении.

В SceneA метод preload загружает два изображения: фоновую картинку и спрайт хот-дога. Обратите внимание, что SceneB не перезагружает ресурсы — она использует уже загруженную в памяти текстуру hotdog, что является стандартным поведением Phaser.

class SceneA extends Phaser.Scene {
    constructor () {
        super('sceneA'); // Уникальный ключ сцены
        this.hotdog;
    }
    preload () {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('pic', 'assets/pics/case.jpg');
        this.load.image('hotdog', 'assets/sprites/hotdog.png');
    }

Настройка ввода и захват клавиш в SceneA

В методе create сцены A создаётся фон, спрайт хот-дога и информационный текст. Ключевой момент — настройка обработки клавиатуры.

Метод this.input.keyboard.addCapture('UP, DOWN, LEFT, RIGHT') предотвращает прокрутку страницы браузера при нажатии стрелок, «захватывая» эти события для игры.

Затем для каждой клавиши стрелки назначается обработчик события keydown_*. Внутри этих обработчиков изменяются координаты спрайта hotdog. Контекст (this) передаётся третьим аргументом в on, чтобы внутри функции-колбэка this указывал на экземпляр сцены.

Также на клик мыши (pointerdown) назначено переключение сцен: текущая сцена ставится на паузу pause(), а сцена B запускается run().

create () {
    this.add.image(400, 300, 'pic');
    var hotdog = this.add.image(400, 300, 'hotdog');
    this.add.text(10, 10, 'Scene A. Press arrows to move. Click to change Scene.', { font: '16px Courier', fill: '#00ff00' });

    this.input.keyboard.addCapture('UP, DOWN, LEFT, RIGHT');

    this.input.keyboard.on('keydown_UP', function (event) {
        hotdog.y -= 4;
    }, this);
    // ... аналогично для DOWN, LEFT, RIGHT

    this.input.on('pointerdown', function () {
        console.log('down');
        this.scene.pause();
        this.scene.run('sceneB');
    }, this);
    this.hotdog = hotdog;
}

Создание наложенной сцены и альтернативное управление

SceneB создаётся как наложенная сцена. В её методе create рисуется полупрозрачный чёрный прямоугольник с помощью Graphics, создавая эффект затемнения фона из SceneA. Спрайт хот-дога добавляется заново, но с красным оттенком (setTint), чтобы визуально отличаться.

Обработка стрелок настраивается аналогично, но логика привязана к новому спрайту hotdog в контексте SceneB. Это показывает, что обработчики событий клавиатуры из разных сцен работают независимо, даже если они назначены на одни и те же клавиши.

Для возврата в SceneA используется событие keydown_SPACE. Важно отметить применение метода once вместо on — обработчик сработает только один раз. При нажатии пробела SceneB останавливается (stop()), а SceneA возобновляется (resume()).

create () {
    var graphics = this.add.graphics();
    graphics.fillStyle(0x000000, 0.5);
    graphics.fillRect(0, 0, 800, 600);

    var hotdog = this.add.image(400, 300, 'hotdog').setTint(0xff0000);

    this.input.keyboard.addCapture('UP, DOWN, LEFT, RIGHT');
    // ... обработчики для стрелок

    this.input.keyboard.once('keydown_SPACE', function (event) {
        this.scene.stop();
        this.scene.resume('sceneA');
    }, this);
    this.hotdog = hotdog;
}

Непрерывная анимация в update и конфигурация игры

В методе update сцены A происходит постоянное вращение спрайта хот-дога (this.hotdog.rotation += 0.001). Поскольку сцена A поставлена на паузу при активации сцены B, её цикл update приостанавливается. При возобновлении (resume) анимация продолжается с того же места.

Конфигурация игры включает обе сцены в массиве scene. Phaser автоматически инициализирует их в порядке указания, но активной будет только первая сцена в списке (SceneA). Остальные сцены можно запускать и останавливать динамически.

update () {
    this.hotdog.rotation += 0.001;
}
// ...
var config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: [ SceneA, SceneB ] // Обе сцены переданы в массив
};
var game = new Phaser.Game(config);

Разница между pause/run и stop/resume

В этом примере показаны два подхода к управлению сценами:
1.  **Пауза и запуск другой сцены:** `this.scene.pause(); this.scene.run('sceneB');`
    *   `pause()`: Приостанавливает выполнение текущей сцены (останавливаются `update`, физика, таймеры сцены), но сцена остаётся в памяти, и её объекты отображаются.
    *   `run('sceneB')`: Запускает сцену B, если она ещё не активна, или возобновляет её, если она была на паузе. Сцены A и B теперь выполняются параллельно (но A на паузе).
2.  **Остановка и возобновление:** `this.scene.stop(); this.scene.resume('sceneA');`
    *   `stop()`: Полностью останавливает и уничтожает текущую сцену (SceneB). Все её игровые объекты, обработчики событий и данные удаляются.
    *   `resume('sceneA')`: Снимает сцену A с паузы, её цикл `update` возобновляется.

Выбор между pause/run и stop/resume зависит от необходимости сохранять состояние временно скрытой сцены.

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

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

  1. Добавить в SceneB свой метод update с другой анимацией и понаблюдать за её работой
  2. Использовать switch вместо run и stop для полной замены одной сцены на другую
  3. Передавать данные (например, координаты хот-дога) из SceneA в SceneB через реестр данных игры (this.registry) или напрямую при запуске сцены