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

При разработке сложных игровых интерфейсов или систем мини-игр внутри основной сцены часто возникает необходимость изолировать логику и визуальное отображение отдельных компонентов. Класс `Juggler` из примера демонстрирует элегантный паттерн создания дочерней сцены с собственным viewport, который можно динамически позиционировать на экране. Этот подход полезен для создания независимых UI-окон, мини-карт, диалоговых систем или любых других элементов, которые должны "плавать" над основной игровой сценой, сохраняя свою внутреннюю логику обновления и отрисовки.

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

Живой запуск

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

Исходный код


class Juggler extends Phaser.Scene {

    constructor (handle, parent)
    {
        super(handle);

        this.parent = parent;
    }

    create ()
    {
        const bg = this.add.image(0, 0, 'jugglerWindow').setOrigin(0);

        this.cameras.main.setViewport(this.parent.x, this.parent.y, Juggler.WIDTH, Juggler.HEIGHT);

        this.add.sprite(100, 22, 'juggler').setOrigin(0).play('juggler');
    }

    refresh ()
    {
        this.cameras.main.setPosition(this.parent.x, this.parent.y);

        this.scene.bringToTop();
    }

}

Juggler.WIDTH = 328;
Juggler.HEIGHT = 226;

Концепция дочерней сцены с viewport

Вместо того чтобы добавлять множество спрайтов и логики в одну главную сцену, Phaser позволяет создавать несколько сцен, работающих одновременно. Класс Juggler наследуется от Phaser.Scene и предназначен для работы как независимый визуальный компонент.

Ключевая идея — эта сцена рисуется не на весь экран, а в ограниченной прямоугольной области (viewport), координаты которой задаются относительно другой, "родительской" сущности. Это позволяет разместить анимацию жонглера в окне, которое можно перемещать по экрану, просто обновляя координаты viewport.

class Juggler extends Phaser.Scene {
    constructor (handle, parent) {
        super(handle);
        this.parent = parent;
    }
}

Инициализация viewport в методе create

Метод create() выполняется один раз при запуске сцены. Здесь происходит первоначальная настройка камеры и добавление графики.

Сначала добавляется фоновое изображение jugglerWindow, которое будет служить рамкой окна. Важно установить начало координат в (0,0) методом .setOrigin(0), чтобы дальнейшее позиционирование элементов внутри viewport было предсказуемым.

Затем происходит главная манипуляция: камере сцены задается viewport с помощью this.cameras.main.setViewport(). Его положение (this.parent.x, this.parent.y) и размеры (Juggler.WIDTH, Juggler.HEIGHT) определяют, где и какого размера будет "окно" на основном экране игры, внутри которого будет видно содержимое этой сцены.

create ()
{
    const bg = this.add.image(0, 0, 'jugglerWindow').setOrigin(0);
    this.cameras.main.setViewport(this.parent.x, this.parent.y, Juggler.WIDTH, Juggler.HEIGHT);
    this.add.sprite(100, 22, 'juggler').setOrigin(0).play('juggler');
}

Динамическое обновление позиции окна

Поскольку сцена Juggler получает ссылку на parent в конструкторе, она может реагировать на изменения его состояния. Метод refresh() предназначен для обновления позиции viewport этой сцены без пересоздания всей графики.

Вызов this.cameras.main.setPosition() изменяет левый верхний угол области отображения сцены. Это эффективнее, чем пересоздавать viewport полностью, если изменились только координаты.

Строка this.scene.bringToTop() гарантирует, что данная сцена будет отрисована поверх других сцен, что критично для поведения "окна", которое всегда должно быть видимым.

refresh ()
{
    this.cameras.main.setPosition(this.parent.x, this.parent.y);
    this.scene.bringToTop();
}

Статические константы и взаимодействие с родителем

Использование статических полей класса для хранения констант (ширины и высоты) — это хорошая практика, которая делает код читаемее и позволяет легко изменять размер компонента в одном месте.

Juggler.WIDTH и Juggler.HEIGHT используются при создании viewport и, вероятно, также известны родительской сцене или объекту, который управляет позицией (parent). Это обеспечивает согласованность: размер окна и размер фоновой текстуры jugglerWindow должны совпадать.

Juggler.WIDTH = 328;
Juggler.HEIGHT = 226;

Роль parent — это любой объект, имеющий свойства `xиy. В реальном проекте это может быть объект данных UI, другая сцена или даже указатель на DOM-элемент. Дочерняя сценаJuggler` абстрагирована от деталей, ей нужны только координаты.

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

Паттерн, показанный в классе Juggler, предоставляет мощный и чистый способ организации кода для сложных интерфейсов и составных сцен в Phaser. Он разделяет ответственность, изолирует состояние и позволяет легко управлять позицией и порядком отрисовки. Для экспериментов попробуйте: 1. Сделать parent динамическим объектом, который перемещается по клику мыши. 2. Добавить в метод refresh() проверку на выход за границы экрана. 3. Создать несколько экземпляров Juggler с разными parent объектами и анимациями. 4. Реализовать закрытие окна через уничтожение сцены (this.scene.remove()) по сигналу от parent.