О чем этот пример
Создание сложных игр требует чистого разделения кода. Когда интерфейс (UI) и игровая логика смешаны в одной сцене, поддержка и масштабирование проекта становятся сложными. В этой статье мы разберем пример использования двух сцен в Phaser: одной — для игрового мира, другой — для отображения очков. Такой подход делает архитектуру игры более модульной, понятной и готовой к росту. Вы научитесь обмениваться событиями между сценами, что является ключевым паттерном для управления состоянием игры.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class GameScene extends Phaser.Scene
{
constructor ()
{
super({ key: 'GameScene' });
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('bg', 'assets/skies/sky4.png');
this.load.image('crate', 'assets/sprites/crate.png');
}
create ()
{
this.add.image(400, 300, 'bg');
for (let i = 0; i < 64; i++)
{
const x = Phaser.Math.Between(0, 800);
const y = Phaser.Math.Between(0, 600);
const box = this.add.image(x, y, 'crate');
// Make them all input enabled
box.setInteractive();
}
this.input.on('gameobjectup', this.clickHandler, this);
}
clickHandler (pointer, box)
{
// Disable our box
box.input.enabled = false;
box.setVisible(false);
// Dispatch a Scene event
this.events.emit('addScore');
}
}
class UIScene extends Phaser.Scene
{
constructor ()
{
super({ key: 'UIScene', active: true });
this.score = 0;
}
create ()
{
// Our Text object to display the Score
const info = this.add.text(10, 10, 'Score: 0', { font: '48px Arial', fill: '#000000' });
// Grab a reference to the Game Scene
const ourGame = this.scene.get('GameScene');
// Listen for events from it
ourGame.events.on('addScore', function ()
{
this.score += 10;
info.setText(`Score: ${this.score}`);
}, this);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: [ GameScene, UIScene ]
};
const game = new Phaser.Game(config);
Архитектура двух сцен: GameScene и UIScene
Исходный код определяет две сцены, которые будут работать одновременно. GameScene отвечает за игровой мир: фон, объекты и их взаимодействие. UIScene — это слой поверх игрового мира, который занимается исключительно отображением пользовательского интерфейса, в данном случае — счета.
Обе сцены передаются в массив scene конфигурации игры. Ключевой параметр active: true в конструкторе UIScene гарантирует, что эта сцена будет активна сразу после создания, даже если GameScene также запущена. Это позволяет UI отображаться с самого начала.
scene: [ GameScene, UIScene ]
super({ key: 'UIScene', active: true });
Игровая сцена: создание объектов и обработка кликов
В методе create GameScene загружается фон и создаются 64 спрайта ящика в случайных позициях. Каждому ящику назначается интерактивность с помощью setInteractive(). Это необходимо, чтобы объект мог генерировать события ввода, такие как клик (или касание).
Затем сцена подписывается на глобальное событие gameobjectup системы ввода this.input. Это событие срабатывает, когда указатель (мышь или палец) отпускает кнопку над интерактивным игровым объектом. В обработчик передаются указатель (pointer) и сам объект (box).
for (let i = 0; i < 64; i++) {
const x = Phaser.Math.Between(0, 800);
const y = Phaser.Math.Between(0, 600);
const box = this.add.image(x, y, 'crate');
box.setInteractive();
}
this.input.on('gameobjectup', this.clickHandler, this);
Генерация пользовательского события
Когда игрок кликает на ящик, выполняется clickHandler. Его задача — спрятать ящик (чтобы создать иллюзию его исчезновения) и уведомить другие части игры о том, что очки должны быть добавлены.
Сначала ящик деактивируется (box.input.enabled = false) и скрывается (box.setVisible(false)). Это предотвращает повторную обработку клика и удаляет объект с экрана.
Затем происходит самое важное: сцена испускает (emit) свое собственное пользовательское событие с именем 'addScore'. Система событий Phaser (this.events) позволяет любой другой части кода подписаться на это событие и отреагировать.
clickHandler (pointer, box) {
box.input.enabled = false;
box.setVisible(false);
this.events.emit('addScore');
}
UI-сцена: подписка на события и обновление счета
UIScene создает текстовый объект для отображения счета. Чтобы получать уведомления от GameScene, ей сначала нужно получить на нее ссылку с помощью метода this.scene.get('GameScene').
Получив ссылку ourGame, UI-сцена подписывается на событие 'addScore', которое генерирует игровая сцена. Обработчик события увеличивает внутренний счетчик this.score на 10 и обновляет текст на экране.
Обратите внимание на использование function и контекста this. Третий аргумент в on() определяет контекст, в котором будет вызвана функция-обработчик. Здесь это this (экземпляр UIScene), поэтому внутри обработчика this.score относится к свойству UI-сцены.
const ourGame = this.scene.get('GameScene');
ourGame.events.on('addScore', function () {
this.score += 10;
info.setText(`Score: ${this.score}`);
}, this);
Преимущества и паттерны
Использование событий для связи между сценами создает слабую связность (loose coupling). GameScene не знает и не зависит от того, как UI обрабатывает начисление очков. Она просто сообщает о факте события. Это позволяет:
* Легко менять логику отображения счета (например, добавить анимацию).
* Подключать другие системы, которые также реагируют на начисление очков (например, систему достижений), просто подписавшись на то же событие.
* Тестировать сцены по отдельности.
Паттерн «Событие» (Events) в Phaser — это мощный инструмент для организации кода, который следует принципам реактивного программирования.
Что попробовать дальше
Разделение игровой логики и интерфейса через систему событий Phaser — это фундаментальный шаг к созданию поддерживаемых и масштабируемых проектов. Вы можете экспериментировать с этим примером: попробуйте добавить третью сцену для звуков, которая будет подписываться на 'addScore' и проигрывать приятный звук. Или измените GameScene так, чтобы разные типы ящиков генерировали события с разными именами ('addScoreBig', 'addBonus'), а UI-сцена обрабатывала их по-разному, умножая очки на коэффициент.
