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

При разработке игр на Phaser важно контролировать порядок инициализации систем. Частая ошибка — обращение к методам плагина в сцене до того, как сам плагин был запущен и сопоставлен (mapped). Это приводит к фатальной ошибке `TypeError: Cannot read properties of undefined` и остановке игры. В этой статье разберем, как правильно настраивать плагины в конфигурации игры, чтобы избежать этой проблемы и гарантировать, что ваши кастомные системы будут готовы к работе в момент создания сцены.

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

Живой запуск

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

Исходный код


class TestPlugin extends Phaser.Plugins.BasePlugin
{

    constructor(pluginManager)
    {
        super(pluginManager);
    }

    wibble ()
    {
        console.log('hello');
    }

}

class Example extends Phaser.Scene
{
    constructor()
    {
        super({
            key: "example"
        });
    }

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

    create ()
    {
        console.log('create');
        this.test.wibble();
    }
}

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#1d1d1d',
    parent: 'phaser-example',
    scale: {
        mode: Phaser.Scale.FIT,
        autoCenter: Phaser.Scale.CENTER_BOTH
    },
    scene: [ Example ],
    plugins: {
        global: [
            { key: 'TestPlugin', plugin: TestPlugin, start: false, mapping: 'test' }
        ]
    },
};

var game = new Phaser.Game(config);

Проблема: сцена создается до инициализации плагина

В предоставленном исходном коде плагин TestPlugin объявлен в глобальных плагинах конфига, но с параметром start: false. Это означает, что плагин будет зарегистрирован в Plugin Manager, но не будет автоматически запущен (т.е., его метод start не вызовется) перед созданием сцен.

Ключевой параметр mapping: 'test' указывает, что после запуска экземпляр плагина будет доступен в сцене через свойство this.test.

Ошибка возникает в методе create сцены Example, в строке:

this.test.wibble();

На момент выполнения этой строки свойство this.test равно undefined, потому что плагин с ключом 'TestPlugin' еще не был запущен (не был вызван его метод start), и, следовательно, сопоставление (mapping) не произошло. Сцена пытается использовать функциональность, которая еще не готова.

Решение: правильная конфигурация плагина

Чтобы плагин был доступен в сцене с самого начала ее жизненного цикла (в create, update и т.д.), его необходимо запустить. Делается это через параметр start в конфигурации плагина.

Исправленная конфигурация игры должна выглядеть так:

var config = {
    // ... другие настройки (type, width, height и т.д.)
    scene: [ Example ],
    plugins: {
        global: [
            { key: 'TestPlugin', plugin: TestPlugin, start: true, mapping: 'test' }
        ]
    },
};

Установка start: true гарантирует, что плагин будет запущен (и, как следствие, сопоставлен) до создания и инициализации любой из сцен, перечисленных в массиве scene. После этого в методе create сцены Example свойство this.test будет содержать экземпляр TestPlugin, и вызов this.test.wibble() успешно выполнится, выводя 'hello' в консоль.

Как работает параметр mapping

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

* При mapping: 'test' для **глобального** плагина (global:), экземпляр плагина становится доступен во всех системах, которые его поддерживают, например, в каждой сцене (Scene), через свойство с указанным именем (this.test). * Также можно использовать mapping для плагинов уровня сцены (scene:), но в этом случае плагин будет доступен только в рамках конкретной сцены, для которой он сконфигурирован.

После корректной настройки вы можете обращаться к методам плагина напрямую:

create ()
{
    // this.test теперь существует и является экземпляром TestPlugin
    this.test.wibble(); // Выведет 'hello' в консоль
}

Когда может понадобиться start: false?

Возникает логичный вопрос: зачем вообще нужен параметр start: false, если он вызывает проблемы? Его использование оправдано в сценариях отложенной или ручной инициализации.

1. **Динамическая загрузка плагинов:** Вы можете зарегистрировать плагин на старте игры, но запустить его позже, по требованию (например, после загрузки каких-то ассетов или в ответ на действие игрока). 2. **Условный запуск:** Запуск плагина только на определенных уровнях или в определенных игровых режимах.

Для ручного запуска плагина, сконфигурированного с start: false, можно использовать Plugin Manager:

// Где-то в коде вашей сцены, например, после условия
this.plugins.start('TestPlugin');
// Теперь mapping сработает, и this.test станет доступен
if (this.test) {
    this.test.wibble();
}

Важно помнить, что mapping срабатывает именно в момент **запуска** (start) плагина, а не его регистрации.

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

Порядок инициализации в Phaser имеет значение. Всегда проверяйте, что плагины, от которых зависит ваша сцена, запущены (start: true) до её создания. Используйте start: false осознанно, для сценариев отложенной инициализации, и не забывайте вручную запускать плагин через this.plugins.start() перед обращением к его методам. **Идеи для экспериментов:** 1. Создайте плагин, который зависит от загруженного ассета (например, шрифта). Настройте его с start: false и запускайте только после события filecomplete в preload сцены. 2. Реализуйте систему «тяжелых» плагинов (например, для аналитики), которые должны запускаться не сразу при старте игры, а только после первого клика пользователя (согласие на сбор данных). 3. Попробуйте использовать плагины уровня сцены (scene: в конфиге) с маппингом и сравните их область видимости с глобальными.