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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.atlas('cards', 'assets/atlas/cards.png', 'assets/atlas/cards.json');
    }

    create ()
    {
        //  Create a stack of random cards

        const frames = this.textures.get('cards').getFrameNames();

        let x = 100;
        let y = 100;

        for (let i = 0; i < 64; i++)
        {
            const image = this.add.image(x, y, 'cards', Phaser.Math.RND.pick(frames)).setInteractive({ draggable: true });

            x += 4;
            y += 4;
        }

        this.cameras.main.setSize(512, 300).setZoom(1).setBackgroundColor('#000000');
        this.cameras.add(512, 0, 512, 300).setZoom(0.25).setBackgroundColor('#0000aa');
        this.cameras.add(0, 300, 512, 300).setZoom(0.5).setBackgroundColor('#00aa00');
        this.cameras.add(512, 300, 512, 300).setZoom(0.5).setBackgroundColor('#aa0000').setRotation(0.4);

        this.input.on('drag', (pointer, gameObject, dragX, dragY) =>
        {

            gameObject.x = dragX;
            gameObject.y = dragY;

        });

    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 1024,
    height: 600,
    backgroundColor: '#fafafa',
    scene: Example
};

const game = new Phaser.Game(config);

Инициализация и загрузка ассетов

Вся логика примера сосредоточена в классе сцены Example. На этапе preload загружается один атлас — текстура, содержащая все кадры карт.

this.load.atlas('cards', 'assets/atlas/cards.png', 'assets/atlas/cards.json');

Метод this.load.atlas загружает изображение (PNG) и JSON-файл с координатами отдельных спрайтов (фреймов) внутри этого изображения. Это эффективный способ работы со множеством графических элементов.

Создание стопки карт и настройка интерактивности

В методе create сначала получаем список всех доступных фреймов из загруженного атласа. Затем в цикле создается 64 карты, каждая из которых смещена относительно предыдущей, формируя стопку.

const frames = this.textures.get('cards').getFrameNames();
let x = 100;
let y = 100;
for (let i = 0; i < 64; i++) {
    const image = this.add.image(x, y, 'cards', Phaser.Math.RND.pick(frames)).setInteractive({ draggable: true });
    x += 4;
    y += 4;
}

Ключевой момент — вызов setInteractive({ draggable: true }) для каждого изображения. Это регистрирует объект как цель для событий ввода и, что особенно важно, активирует специальное событие 'drag'. Без флага draggable: true событие drag не будет генерироваться для этого объекта.

Настройка системы камер

В примере создаются четыре независимые камеры, которые отображают одну и ту же сцену, но с разными параметрами.

this.cameras.main.setSize(512, 300).setZoom(1).setBackgroundColor('#000000');
this.cameras.add(512, 0, 512, 300).setZoom(0.25).setBackgroundColor('#0000aa');
this.cameras.add(0, 300, 512, 300).setZoom(0.5).setBackgroundColor('#00aa00');
this.cameras.add(512, 300, 512, 300).setZoom(0.5).setBackgroundColor('#aa0000').setRotation(0.4);
*   `this.cameras.main` — основная камера, создается автоматически. Здесь ей задается размер 512x300 пикселей, стандартный зум 1 и черный фон.
*   `this.cameras.add(...)` создает дополнительные камеры. Первые четыре аргумента — это `x`, `y`, `width`, `height` области отображения камеры на холсте игры.
*   Каждая камера настраивается независимо: изменяется масштаб (`setZoom`) и цвет фона (`setBackgroundColor`). Последняя камера также повернута на 0.4 радиана с помощью `setRotation`. Это демонстрирует, как можно создавать динамичные виды сцены.

Обработка события перетаскивания

Логика перетаскивания реализована предельно просто через слушатель события 'drag' на this.input.

this.input.on('drag', (pointer, gameObject, dragX, dragY) => {
    gameObject.x = dragX;
    gameObject.y = dragY;
});

Когда пользователь тянет за любой интерактивный объект с флагом draggable: true, генерируется событие 'drag'. Обработчик получает: * pointer — информация о курсоре/касании. * gameObject — ссылка на перетаскиваемый объект (в нашем случае — карту). * dragX, dragY — координаты, рассчитанные системой ввода, куда должен быть перемещен объект в мировых координатах.

Внутри обработчика мы просто присваиваем эти координаты свойствам `xиy` объекта, мгновенно обновляя его положение. Важно: это обновление будет отражено во всех камерах одновременно, так как они смотрят на одну и ту же сцену.

Конфигурация игры и запуск

Код завершается стандартной конфигурацией экземпляра игры Phaser.Game.

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 1024,
    height: 600,
    backgroundColor: '#fafafa',
    scene: Example
};
const game = new Phaser.Game(config);

Параметры width и height (1024x600) определяют общий размер холста игры. Фон #fafafa будет виден только в областях, не перекрытых камерами (так как у каждой камеры свой заданный фон). Класс Example передается как сцена по умолчанию.

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

Этот пример наглядно показывает, как Phaser разделяет логику сцены, отображения и ввода. Вы можете легко адаптировать этот подход: например, сделать одну камеру основной для игры, а другие использовать для миникарты, меню или отображения статистики. Для экспериментов попробуйте изменить параметры камер (позицию, зум, угол поворота) в реальном времени в ответ на события, ограничить область перетаскивания объектов или реализовать логику 'бросания' с инерцией, обрабатывая событие 'dragend'.