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

В разработке игр часто возникает необходимость в создании самодостаточных, переиспользуемых модулей. Классический пример — мини-игры на внутриигровых экранах или динамические виджеты. Пример Boing.js из демо Phaser показывает, как создать автономную физическую сцену, которую можно размещать и перемещать внутри основного игрового пространства, как независимое окно. Этот подход полезен для организации сложных проектов, изоляции логики и создания модульных интерфейсов.

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

Живой запуск

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

Исходный код


class Boing extends Phaser.Scene {

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

        this.parent = parent;

        this.ball;
        this.shadow;
    }

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

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

        this.physics.world.setBounds(10, 24, 330, 222);

        this.ball = this.physics.add.sprite(100, 32, 'boing', 'boing1').play('boing');
        this.shadow = this.add.image(this.ball.x + 62, this.ball.y - 2, 'boing', 'shadow');

        this.ball.setVelocity(Phaser.Math.Between(40,80), 110);
        this.ball.setBounce(1, 1);
        this.ball.setCollideWorldBounds(true);

        this.events.on('postupdate', this.postUpdate, this);
    }

    postUpdate ()
    {
        this.shadow.setPosition(this.ball.x + 44, this.ball.y - 2);
    }

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

        this.scene.bringToTop();
    }

}

Boing.WIDTH = 344;
Boing.HEIGHT = 266;

Концепция: Сцена как виджет

Класс Boing наследуется от Phaser.Scene, но используется не как полноэкранный уровень, а как инкапсулированный компонент. Его ключевая особенность — привязка к внешним координатам (parent), которые задаются из родительской сцены.

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

Настройка области видимости и физики

В методе create() происходит первоначальная настройка сцены. Важнейший шаг — определение вьюпорта камеры. Это задает «окно», через которое мы видим содержимое сцены Boing на основном холсте игры.

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

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

this.physics.world.setBounds(10, 24, 330, 222);

Создание и настройка физического объекта

Мяч создается как физический спрайт (physics.add.sprite) с начальной анимацией. Тень — это обычное изображение (add.image), привязанное к начальной позиции мяча.

Физическое поведение настраивается тремя ключевыми командами:

// Задается случайная начальная скорость по X и фиксированная по Y
this.ball.setVelocity(Phaser.Math.Between(40,80), 110);

// Упругость (отскок) устанавливается в 1 по обеим осям (идеально упругий отскок)
this.ball.setBounce(1, 1);

// Включается столкновение с границами мира, которые мы задали ранее
this.ball.setCollideWorldBounds(true);

Анимация 'boing', указанная в методе play(), должна быть предварительно загружена и определена в ассетах игры.

Синхронизация объектов: событие postupdate

Чтобы тень следовала за мячом, необходимо обновлять ее позицию каждый кадр. Однако делать это в основном цикле обновления (update) не рекомендуется для визуальных элементов, не влияющих на физику.

Вместо этого используется событие postupdate, которое срабатывает после всех расчетов физики и логики, но до рендеринга. Это идеальное место для синхронизации визуальных эффектов.

// Подписка на событие в create()
this.events.on('postupdate', this.postUpdate, this);

// Метод, который обновляет позицию тени относительно мяча
postUpdate ()
{
    this.shadow.setPosition(this.ball.x + 44, this.ball.y - 2);
}

Смещение +44 и -2 подобраны эмпирически для корректного визуального совмещения с пререндеренной графикой.

Управление положением виджета извне

Публичный метод refresh() — это API для взаимодействия с этой сценой из родительской сцены. Его основная задача — обновить позицию вьюпорта камеры, если изменились переданные координаты parent.x и parent.y.

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

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

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

Пример Boing демонстрирует мощный паттерн модульности в Phaser 3, где сцена выступает в роли независимого, переиспользуемого виджета с собственной физикой и логикой. Для экспериментов попробуйте: создать несколько экземпляров Boing с разными координатами; изменить физические параметры (гравитацию, трение) внутри сцены; реализовать взаимодействие между мячом и элементами интерфейса родительской сцены; или использовать этот подход для создания мини-игр на внутриигровом компьютере или панели управления.