О чем этот пример
Разделение игровой логики на независимые модули — ключ к поддержке сложных проектов. В этой статье мы разберем паттерн SubScene (или SubGame), который позволяет встроить целую мини-игру в основную сцену Phaser. Этот подход полезен для создания изолированных механик, таких как битвы в RPG, головоломки или мини-игры в открытом мире, без монолитного кода. Вы научитесь управлять несколькими игровыми контекстами, переключать их и поддерживать чистую архитектуру.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: "arcade",
arcade: {
debug: false,
gravity: { y: 0 }
}
},
scene: [ Preloader, WorldMap, SubGame ]
};
const game = new Phaser.Game(config);
Что такое SubGame?
SubGame — это не официальный термин Phaser, а распространенный паттерн проектирования. По сути, это отдельная сцена Phaser (например, SubGame в примере), которая загружается и управляется из другой, родительской сцены (в данном случае, WorldMap).
Основная идея — инкапсуляция. Вся логика, ресурсы, объекты и обработчики событий для мини-игры содержатся внутри этой под-сцены. Это позволяет: - Четко разделять ответственность между разными частями игры. - Легко включать и выключать целые игровые режимы. - Избегать конфликтов систем, например, физики или ввода.
В предоставленном примере конфигурации игры видно, что движок Phaser будет использовать три сцены, перечисленные в массиве scene.
Анализ конфигурации игры
Конфигурационный объект — это сердце любого приложения на Phaser. В нем задаются базовые параметры отрисовки, физический движок и, что самое важное для нашей темы, последовательность сцен.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: "arcade",
arcade: {
debug: false,
gravity: { y: 0 }
}
},
scene: [ Preloader, WorldMap, SubGame ]
};
Давайте разберем ключевые моменты:
- scene: [ Preloader, WorldMap, SubGame ]: Это массив классов сцен. Phaser создаст их экземпляры в этом порядке. Первой запустится сцена Preloader, затем управление перейдет к WorldMap. Сцена SubGame будет создана, но не запущена, до явного вызова.
- physics.default: "arcade": Включает простой и быстрый физический движок Arcade. Обратите внимание, что gravity: { y: 0 } отключает гравитацию по умолчанию, что типично для топ-даун игр или игр с управляемым движением.
- Важно: эта физическая конфигурация будет глобальной для всех сцен, если они не переопределят ее локально.
Как запустить SubGame из основной сцены
Запуск мини-игры из основной сцены (например, WorldMap) осуществляется с помощью менеджера сцен Phaser. Предположим, в WorldMap игрок взаимодействует с объектом (сундуком, NPC), которое должно запустить мини-игру.
В методе обработки взаимодействия в классе WorldMap вам нужно будет вызвать метод this.scene.launch() или this.scene.start().
// Пример кода внутри класса WorldMap
openMiniGame() {
// Метод launch() запускает сцену SubGame параллельно с текущей.
// Текущая сцена (WorldMap) остается активной, но может быть приостановлена.
this.scene.launch('SubGame');
// Часто основную сцену приостанавливают, чтобы ее логика не работала фоном.
this.scene.pause();
}
Ключевое отличие между launch() и start():
- launch(): Запускает сцену параллельно. Вы можете управлять несколькими сценами одновременно (например, для эффекта наложения).
- start(): Останавливает текущую сцену и запускает новую. Это полное переключение контекста.
Для паттерна SubGame чаще используют launch(), чтобы позже легко вернуться в основную сцену.
Как вернуться из SubGame обратно
Завершение мини-игры и возврат в основной мир также происходит через менеджер сцен. Логика закрытия должна быть внутри класса SubGame.
Например, при достижении цели в мини-игре или по нажатию кнопки "Выход" нужно остановить текущую SubGame и возобновить основную сцену.
// Пример кода внутри класса SubGame
completeMiniGame() {
// 1. Останавливаем (и уничтожаем) текущую сцену SubGame.
this.scene.stop();
// 2. Возобновляем основную сцену (WorldMap), которую мы ранее приостановили.
// Важно: обращаемся к сцене по ее системному ключу (указанному в game.config).
this.scene.resume('WorldMap');
}
Важные нюансы:
- `this.scene.stop()`: Останавливает именно эту сцену (`SubGame`). Все ее игровые объекты, таймеры и слушатели событий будут корректно уничтожены, что предотвращает утечки памяти.
- `this.scene.resume('WorldMap')`: Снова активирует логику обновления (`update`) и рендеринг сцены `WorldMap`. Игрок увидит мир в том же состоянии, в котором его оставил.
- Можно передавать данные между сценами через аргументы методов `launch()` и `start()`, что полезно для настройки мини-игры.
Практические советы по реализации
Чтобы паттерн SubGame работал гладко, следуйте этим рекомендациям:
1. **Изоляция данных:** SubGame должна быть максимально независима. Выносите все ее конфигурации, текстуры и звуки в локальные свойства класса или отдельный файл данных.
2. **Управление вводом:** Убедитесь, что события клавиатуры или мыши обрабатываются только активной сценой. Phaser делает это автоматически для сцены на переднем плане, но если сцены запущены параллельно, может потребоваться ручное управление фокусом ввода.
3. **Очистка ресурсов:** Все созданные в create() объекты будут автоматически уничтожены при вызове scene.stop(). Однако, если вы создавали слушатели событий за пределами Phaser (например, document.addEventListener), их нужно удалять в методе shutdown() или destroy() класса сцены.
// В классе SubGame
shutdown() {
// Явная очистка кастомных слушателей, если они были добавлены.
document.removeEventListener('customEvent', this.handler);
}
4. **Системный ключ сцены:** Убедитесь, что вы обращаетесь к сценам по правильным ключам. По умолчанию Phaser использует имя класса как ключ ('WorldMap', 'SubGame'). Вы можете задать его явно, передав строку конфигурации в super() внутри конструктора сцены.
Что попробовать дальше
Паттерн SubScene — это мощный инструмент для структурирования игр на Phaser. Он превращает монолитную сцену в модульную систему, где каждая мини-игра живет в отдельном, тестируемом контейнере. Для экспериментов попробуйте
- Передавать параметры из WorldMap в SubGame (например, сложность)
- Создать менеджер под-игр, который бы управлял их очередностью
- Реализовать плавные переходы между сценами с помощью затемнений или анимаций камеры
