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

В сложных интерфейсах с несколькими сценами часто нужно синхронизировать взаимодействие. Например, UI-сцена может отображать координаты в игровом мире, хотя её собственная система координат ограничена экраном. Эта статья показывает, как получить доступ к камере другой сцены и преобразовать координаты указателя в мировые, что полезно для создания интерактивных карт, отладчиков и сложных HUD. Вы научитесь использовать `this.scene.get()` для получения ссылки на другую сцену и метод камеры `getWorldPoint()` для точного преобразования координат, что позволит создавать более динамичные и информативные интерфейсы.

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

Живой запуск

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

Исходный код


class UIScene extends Phaser.Scene
{
    constructor ()
    {
        super({ key: 'ui' });
    }

    create ()
    {
        const text = this.add.text(10, 10).setText('Click to move');

        text.setShadow(1, 1, '#000000', 2);

        const worldCamera = this.scene.get('world').cameras.main;
        this.input.on('pointermove', function (pointer) {

            const pos = worldCamera.getWorldPoint(pointer.x, pointer.y);
            text.setText([
                'World: ' + pos.x + ' x ' + pos.y,
                'Camera: ' + worldCamera.midPoint.x + ' x ' + worldCamera.midPoint.y
            ]);

        });
    }
}

class WorldScene extends Phaser.Scene
{
    constructor ()
    {
        super({ key: 'world' });
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('map', 'assets/tests/camera/earthbound-scarab.png');
    }

    create ()
    {
        this.cameras.main.setBounds(0, 0, 1024, 2048);

        this.add.image(0, 0, 'map').setOrigin(0);

        this.cameras.main.setZoom(1);
        this.cameras.main.centerOn(0, 0);

        let pos = 0;

        this.input.on('pointerdown', function () {

            var cam = this.cameras.main;

            if (pos === 0)
            {
                cam.pan(767, 1096, 2000, 'Power2');
                cam.zoomTo(4, 3000);
                pos++;
            }
            else if (pos === 1)
            {
                cam.pan(703, 1621, 2000, 'Elastic');
                cam.zoomTo(2, 3000);
                pos++;
            }
            else if (pos === 2)
            {
                cam.pan(256, 623, 2000, 'Sine.easeInOut');
                cam.zoomTo(1, 3000);
                pos++;
            }
            else if (pos === 3)
            {
                cam.pan(166, 304, 2000);
                cam.zoomTo(4, 1500);
                pos++;
            }
            else if (pos === 4)
            {
                cam.pan(624, 158, 2000);
                cam.zoomTo(0.8, 3000);
                pos++;
            }
            else if (pos === 5)
            {
                cam.pan(600, 330, 2000);
                pos++;
            }
            else if (pos === 6)
            {
                cam.pan(748, 488, 2000);
                cam.zoomTo(1, 1000);
                pos++;
            }
            else if (pos === 7)
            {
                cam.pan(1003, 1719, 2000);
                pos++;
            }
            else if (pos === 8)
            {
                cam.pan(0, 0, 500);
                pos = 0;
            }
        }, this);

        this.scene.launch('ui');
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    pixelArt: true,
    physics: {
        default: 'arcade',
    },
    scene: [ WorldScene, UIScene ]
};

const game = new Phaser.Game(config);

Структура примера: две сцены

В примере используются две сцены: WorldScene (ключ 'world') и UIScene (ключ 'ui'). Мировая сцена загружает большую карту и управляет камерой, а UI-сцена отображает текстовую информацию. Связь между ними односторонняя: UI-сцена получает данные от мировой.

Код запускает UI-сцену из мировой в конце метода create(). Это гарантирует, что мировая сцена и её камера будут инициализированы к моменту обращения к ним из UI.

this.scene.launch('ui');

Получение камеры из другой сцены

Основной способ взаимодействия между сценами в Phaser — через менеджер сцен. В UI-сцене мы можем получить доступ к экземпляру мировой сцены, а через него — к её основной камере.

Метод this.scene.get('world') возвращает ссылку на экземпляр сцены с ключом 'world'. После этого мы получаем доступ к свойству cameras.main — это основная камера мировой сцены. Эта ссылка сохраняется в переменной для дальнейшего использования.

const worldCamera = this.scene.get('world').cameras.main;

Преобразование координат указателя

Координаты события pointermove в UI-сцене относятся к её собственному пространству (от 0x0 до ширины и высоты игры). Чтобы понять, куда указывает игрок в мире игры, нужно преобразовать эти координаты.

Метод камеры getWorldPoint(x, y) выполняет это преобразование. Он принимает координаты на экране (в данном случае, от UI-сцены) и возвращает объект Phaser.Math.Vector2 с соответствующими координатами в игровом мире, учитывая позицию, зум и границы камеры мировой сцены.

const pos = worldCamera.getWorldPoint(pointer.x, pointer.y);
text.setText([
    'World: ' + pos.x + ' x ' + pos.y,
    'Camera: ' + worldCamera.midPoint.x + ' x ' + worldCamera.midPoint.y
]);

Управление камерой в мировой сцене

Чтобы увидеть преобразование координат в действии, камера в WorldScene активно перемещается и масштабируется. Это демонстрирует, что преобразование работает корректно даже при движении камеры.

Методы cam.pan() и cam.zoomTo() анимируют перемещение и изменение зума камеры. Цикл из 9 позиций (pos) запускается по клику. Важно, что UI-сцена не управляет этой камерой, а лишь читает её состояние.

if (pos === 0)
{
    cam.pan(767, 1096, 2000, 'Power2');
    cam.zoomTo(4, 3000);
    pos++;
}

Практическое применение и нюансы

Этот подход полезен не только для отладки. Например, можно сделать интерактивную мини-карту в UI-сцене, где клик преобразуется в координаты мира и перемещает туда основную камеру. Или создать систему маркеров, которые позиционируются в UI, но указывают на объекты в мире.

Важное ограничение: метод getWorldPoint() работает корректно только если координаты, которые вы передаёте, соответствуют той же области просмотра, что и камера. В данном примере это так, потому что обе сцены (world и ui) используют одно и то же окно игры (родительский элемент parent: 'phaser-example'). Если бы UI-сцена была отрендерена в отдельном DOM-элементе с другими размерами, потребовалось бы дополнительное преобразование координат.

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

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

Доступ к камере другой сцены через this.scene.get() и преобразование координат с помощью getWorldPoint() открывают возможности для создания сложных связанных интерфейсов. Это фундамент для интерактивных карт, систем навигации или отладочных инструментов. Поэкспериментируйте: попробуйте из UI-сцены не только читать, но и управлять камерой мировой сцены (например, обрабатывать клик для вызова worldCamera.pan()). Или создайте в UI-сцене спрайт, который будет следовать за преобразованными координатами указателя, создавая эффект перекрестия на игровом мире.