Создание собственного плагина

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

Плагины в Phaser делятся на два основных типа:

  • Глобальные плагины (Global Plugins) — создаются один раз на уровне игры и доступны во всех сценах.
  • Плагины сцены (Scene Plugins) — создаются отдельно для каждой сцены и уничтожаются вместе с ней.

Выбор типа зависит от назначения модуля. Если функциональность должна сохранять состояние между сценами — используется глобальный плагин. Если логика строго относится к конкретной сцене — применяется Scene Plugin.


Базовая структура плагина

Любой плагин в Phaser представляет собой класс, который наследуется от базового класса плагина. Для глобальных плагинов используется Phaser.Plugins.BasePlugin, для плагинов сцены — Phaser.Plugins.ScenePlugin.

Пример глобального плагина

class MyGlobalPlugin extends Phaser.Plugins.BasePlugin {

    constructor(pluginManager) {
        super(pluginManager);

        this.game = pluginManager.game;
    }

    init(data) {
        this.config = data;
    }

    start() {
        console.log('Global plugin started');
    }

    stop() {
        console.log('Global plugin stopped');
    }

    destroy() {
        super.destroy();
    }
}

Жизненный цикл глобального плагина

Глобальный плагин проходит несколько стадий:

  1. constructor — вызывается при создании экземпляра.
  2. init(data) — вызывается при инициализации.
  3. start() — вызывается после запуска.
  4. stop() — при остановке.
  5. destroy() — при уничтожении игры.

Методы init и start разделяют конфигурацию и запуск логики, что позволяет гибко управлять поведением.


Регистрация глобального плагина

Глобальный плагин подключается через конфигурацию игры:

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    plugins: {
        global: [
            {
                key: 'MyGlobalPlugin',
                plugin: MyGlobalPlugin,
                start: true,
                mapping: 'myPlugin'
            }
        ]
    }
};

const game = new Phaser.Game(config);

Параметры регистрации

  • key — уникальный идентификатор плагина.
  • plugin — ссылка на класс.
  • start — автоматически запускать или нет.
  • mapping — имя свойства, через которое плагин будет доступен в сценах.

После регистрации доступ к плагину внутри сцены осуществляется через:

this.myPlugin

Создание Scene Plugin

Плагин сцены наследуется от Phaser.Plugins.ScenePlugin и автоматически получает ссылку на сцену.

class MyScenePlugin extends Phaser.Plugins.ScenePlugin {

    constructor(scene, pluginManager) {
        super(scene, pluginManager);

        this.scene = scene;
        this.systems = scene.sys;
    }

    boot() {
        this.systems.events.on('destroy', this.destroy, this);
    }

    sayHello() {
        console.log('Hello from Scene Plugin');
    }
}

Особенности Scene Plugin

  • Создается для каждой сцены отдельно.
  • Имеет прямой доступ к scene.
  • Автоматически уничтожается вместе со сценой.
  • Поддерживает методы boot() и destroy().

Регистрация Scene Plugin

const config = {
    plugins: {
        scene: [
            {
                key: 'MyScenePlugin',
                plugin: MyScenePlugin,
                mapping: 'scenePlugin'
            }
        ]
    }
};

Использование внутри сцены:

this.scenePlugin.sayHello();

Доступ к системам Phaser внутри плагина

Через pluginManager.game или scene.sys можно получить доступ к:

  • Менеджеру сцен
  • Кэшу ресурсов
  • Системе событий
  • Камерам
  • Менеджеру ввода

Пример доступа к событиям игры:

this.game.events.on('hidden', () => {
    console.log('Game hidden');
});

Добавление пользовательских фабрик объектов

Плагин может расширять систему создания игровых объектов через GameObjectFactory.

Регистрация новой фабрики

Phaser.GameObjects.GameObjectFactory.register(
    'label',
    function (x, y, text) {
        const label = this.scene.add.text(x, y, text, {
            fontSize: '24px',
            color: '#ffffff'
        });

        this.displayList.add(label);
        this.updateList.add(label);

        return label;
    }
);

Теперь внутри сцены можно использовать:

this.add.label(100, 100, 'Hello');

Такой подход позволяет инкапсулировать создание сложных объектов в одном месте.


Расширение прототипов сцены

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

boot() {
    this.scene.customLog = function(message) {
        console.log('[Scene]', message);
    };
}

После этого метод становится частью сцены.

Следует учитывать, что подобное вмешательство увеличивает связанность кода. Более безопасным считается использование mapping.


Работа с событиями

Плагин может активно использовать систему событий Phaser.

Подписка на события сцены

this.scene.events.on('update', this.update, this);

Создание собственных событий

this.scene.events.emit('plugin-event', { score: 100 });

Такой подход превращает плагин в независимый модуль, взаимодействующий через события.


Инкапсуляция состояния

Глобальный плагин может хранить общее состояние:

class ScoreManager extends Phaser.Plugins.BasePlugin {

    constructor(pluginManager) {
        super(pluginManager);
        this.score = 0;
    }

    add(points) {
        this.score += points;
    }

    getScore() {
        return this.score;
    }
}

Такой плагин становится сервисом, доступным всем сценам.


Динамическая загрузка плагина

Phaser позволяет подключать плагины во время выполнения:

this.plugins.install('MyScenePlugin');

Или для глобального:

this.plugins.installGlobal('MyGlobalPlugin');

Это полезно для модульной архитектуры или ленивой инициализации.


Создание универсального API

Хорошо спроектированный плагин:

  • Не зависит от конкретной сцены.
  • Минимизирует прямой доступ к внутренним структурам.
  • Предоставляет четкий публичный API.
  • Использует события вместо жесткой связи.
  • Корректно очищает ресурсы в destroy().

Пример корректной очистки:

destroy() {
    this.scene.events.off('update', this.update, this);
    super.destroy();
}

Организация кода плагина

Рекомендуемая структура проекта:

plugins/
    MyGlobalPlugin.js
    MyScenePlugin.js

Экспорт через ES-модули:

export default MyScenePlugin;

Подключение:

import MyScenePlugin from './plugins/MyScenePlugin.js';

Расширение Loader через плагин

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

Phaser.Loader.FileTypesManager.register('customJSON', function (key, url) {
    this.addFile(new Phaser.Loader.File(this, {
        type: 'customJSON',
        key: key,
        url: url
    }));

    return this;
});

Использование:

this.load.customJSON('data', 'data.json');

Такой механизм позволяет интегрировать нестандартные форматы данных.


Плагин как модуль архитектуры

В крупном проекте плагины выполняют роль:

  • Менеджеров состояний
  • Сервисов аналитики
  • Систем управления UI
  • Обработчиков ввода
  • Аудиоменеджеров
  • Сетевых клиентов

Разделение логики на плагины упрощает тестирование и поддержку.


Взаимодействие между плагинами

Через pluginManager возможно получить доступ к другим плагинам:

const other = this.pluginManager.get('OtherPlugin');

Такой способ обеспечивает координацию между модулями без глобальных переменных.


Оптимизация и производительность

При создании плагина важно учитывать:

  • Минимизацию подписок на события.
  • Очистку таймеров и интервалов.
  • Отписку от глобальных событий.
  • Контроль утечек памяти.
  • Избежание тяжелых вычислений в update().

Плагин должен быть изолированным и безопасным для многократного создания и уничтожения.


Практический пример: плагин таймера

class TimerPlugin extends Phaser.Plugins.ScenePlugin {

    constructor(scene, pluginManager) {
        super(scene, pluginManager);
        this.timers = [];
    }

    createTimer(delay, callback) {
        const timer = this.scene.time.addEvent({
            delay: delay,
            callback: callback
        });

        this.timers.push(timer);
        return timer;
    }

    destroy() {
        this.timers.forEach(timer => timer.remove());
        this.timers = [];
        super.destroy();
    }
}

Этот плагин централизует управление таймерами сцены и гарантирует корректное удаление при уничтожении.


Проектирование расширяемых плагинов

Ключевые принципы:

  • Модульность — отсутствие жестких зависимостей.
  • Слабая связность — использование событий.
  • Чистый интерфейс — только публичные методы.
  • Контроль жизненного цикла — корректная инициализация и уничтожение.
  • Повторное использование — независимость от конкретного проекта.

Плагины в Phaser позволяют строить масштабируемую архитектуру, превращая движок в расширяемую платформу для разработки сложных игровых систем.