~2 мин чтения

Плагины

Создание и использование плагинов Phaser 4 — три формы плагинов (глобальный, сценический, игрового объекта) и разобранный пример системы сохранения.

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

ВидСуществует вЖизненный циклИспользовать для
Глобальныйgame.pluginsСоздаётся вместе с игрой; сохраняется.Межсценовые службы (сохранение, аналитика, сеть).
Сценическийscene.pluginsСоздаётся для каждой сцены; привязан к её жизненному циклу.Помощники уровня сцены (менеджер эффектов, движок диалогов).
Игрового объектаРасширяет this.add / this.makeПрикрепляет фабрику к сценам, которые её подключают.Пользовательские типы игровых объектов (HealthBar, Minimap).

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

Глобальный плагин

Класс, расширяющий Phaser.Plugins.BasePlugin. Он создаётся один раз при запуске игры и доступен отовсюду через this.plugins.get('Name').

class SavePlugin extends Phaser.Plugins.BasePlugin {
	private readonly KEY = 'save-v1';

	save(state: object) {
		localStorage.setItem(this.KEY, JSON.stringify(state));
	}

	load<T = unknown>(): T | null {
		const raw = localStorage.getItem(this.KEY);
		return raw ? (JSON.parse(raw) as T) : null;
	}

	clear() {
		localStorage.removeItem(this.KEY);
	}
}

Зарегистрируйте его в конфигурации игры:

new Phaser.Game({
	plugins: {
		global: [{ key: 'Save', plugin: SavePlugin, start: true, mapping: 'save' }],
	},
});

start: true создаёт экземпляр немедленно; mapping: 'save' делает его доступным как this.save в каждой сцене. Без mapping к нему обращаются через this.plugins.get('Save').

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

this.save.save({ level: 3, hp: 80 });
const restored = this.save.load<{ level: number; hp: number }>();

Сценический плагин

Класс, расширяющий Phaser.Plugins.ScenePlugin. Каждая сцена получает собственный экземпляр, и плагин может встраиваться в события жизненного цикла сцены:

class DialogPlugin extends Phaser.Plugins.ScenePlugin {
	private activeBox?: Phaser.GameObjects.Container;

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

	say(text: string) {
		this.activeBox?.destroy();
		const scene = this.scene!;
		this.activeBox = scene.add.container(scene.scale.width / 2, scene.scale.height - 80);
		this.activeBox.add(scene.add.rectangle(0, 0, 600, 100, 0x000000, 0.7));
		this.activeBox.add(scene.add.text(0, 0, text, { fontSize: '20px' }).setOrigin(0.5));
	}

	private shutdown() {
		this.activeBox?.destroy();
		this.activeBox = undefined;
	}
}

Регистрация для каждой сцены:

plugins: {
	scene: [{ key: 'Dialog', plugin: DialogPlugin, mapping: 'dialog' }],
}

Теперь this.dialog.say('Hello.') работает в любой сцене, которая получила эту регистрацию.

Плагин игрового объекта

Третья форма добавляет новые фабрики в this.add и this.make. Каноничный паттерн Phaser 4 — обычная функция, зарегистрированная через Phaser.GameObjects.GameObjectFactory.register:

class HealthBar extends Phaser.GameObjects.Container {
	private readonly fg: Phaser.GameObjects.Rectangle;

	constructor(scene: Phaser.Scene, x: number, y: number, max: number) {
		super(scene, x, y);
		const bg = scene.add.rectangle(0, 0, 120, 10, 0x222222);
		this.fg = scene.add.rectangle(-60, 0, 120, 10, 0x44dd44).setOrigin(0, 0.5);
		this.add([bg, this.fg]);
		this.setData('max', max);
		this.setData('value', max);
	}

	set(value: number) {
		this.setData('value', value);
		this.fg.width = 120 * (value / (this.getData('max') as number));
	}
}

Phaser.GameObjects.GameObjectFactory.register('healthBar', function (x, y, max) {
	const hb = new HealthBar(this.scene, x, y, max);
	this.displayList.add(hb);
	this.updateList.add(hb);
	return hb;
});

Теперь this.add.healthBar(20, 20, 100) работает в любой сцене.

Для пользователей TypeScript дополните тип фабрики:

declare global {
	namespace Phaser.GameObjects {
		interface GameObjectFactory {
			healthBar(x: number, y: number, max: number): HealthBar;
		}
	}
}

Подводные камни жизненного цикла

  • Не обращайтесь к this.scene до boot. Сценические плагины создаются рано; this.scene устанавливается в boot.
  • Очищайте подписки в shutdown. Сцены могут запускаться и останавливаться многократно; плагины, привязывающиеся к событиям сцены без отписки, будут давать утечки.
  • Синглтоны глобальных плагинов. Глобальный плагин — это синглтон: храните состояние, специфичное для сцены, внутри сцен, а не в плагине.

Упаковка

Для совместного использования между проектами поставляйте как ESM-модуль, где класс плагина является экспортом по умолчанию, плюс вызов регистрации (или помощник register(game), если хотите, чтобы потребители делали это явно). Не держите никаких зависимостей от Phaser в рантайм-импортах пакета — Phaser принадлежит потребителю; объявите его как peerDependency.

Связанные материалы