О чем этот пример
Создание интерактивных интерфейсов и сложных игровых систем часто требует одновременного отображения сцены с разных ракурсов. В этой статье мы разберем пример, который демонстрирует мощь системы камер 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'.
