О чем этот пример
Работа с контейнерами (`Container`) и камерами (`Camera`) — ключевой навык при создании сложных интерфейсов, эффектов и управления видимостью в играх на Phaser 3. Этот пример демонстрирует, как трансформации объекта-спрайта внутри контейнера взаимодействуют с параметрами камеры, и как эти изменения можно отслеживать в реальном времени. Понимание этой механики позволяет точно позиционировать элементы игрового мира, создавать сложные анимации и реализовывать продвинутые эффекты камеры. В статье мы подробно разберем исходный код примера, объясним, как работает цепочка преобразований (спрайт → контейнер → камера) и как использовать инструменты отладки для визуализации и контроля этих процессов. Это знание пригодится при разработке любых проектов, где требуется точный контроль над положением и видимостью объектов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class OverLayConfig extends Phaser.Scene
{
constructor ()
{
super({ key: "overlay" })
}
createOverlay ()
{
this.graphics = this.add.graphics();
}
updateOverlay ()
{
this.graphics.clear();
this.graphics.lineStyle(2, 0x00ff00, 1);
// if (result)
// {
// var tl = sprite.getTopLeft(null, true);
// var tr = sprite.getTopRight(null, true);
// var bl = sprite.getBottomLeft(null, true);
// var br = sprite.getBottomRight(null, true);
// // cam1.getWorldPoint(tl.x, tl.y, tl);
// // cam1.getWorldPoint(tr.x, tr.y, tr);
// // cam1.getWorldPoint(bl.x, bl.y, bl);
// // cam1.getWorldPoint(br.x, br.y, br);
// graphics.lineStyle(2, 0x00ff00, 1);
// graphics.lineBetween(tl.x, tl.y, tr.x, tr.y);
// graphics.lineBetween(tl.x, tl.y, bl.x, bl.y);
// graphics.lineBetween(tr.x, tr.y, br.x, br.y);
// graphics.lineBetween(bl.x, bl.y, br.x, br.y);
// graphics.fillStyle(0x00ff00, 1);
// graphics.fillRect(tl.x, tl.y, 6, 6);
// graphics.fillStyle(0xff0000, 1);
// graphics.fillRect(tr.x, tr.y, 6, 6);
// graphics.fillStyle(0xff00ff, 1);
// graphics.fillRect(bl.x, bl.y, 6, 6);
// graphics.fillStyle(0x0000ff, 1);
// graphics.fillRect(br.x, br.y, 6, 6);
// }
}
}
class Example extends Phaser.Scene
{
constructor ()
{
super({ key: "main" })
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('eye', 'assets/pics/lance-overdose-loader-eye.png');
this.load.image('grid', 'assets/pics/debug-grid-1920x1920.png');
}
create ()
{
const cam1 = this.cameras.main.setName('Camera 1');
this.add.image(0, 0, 'grid').setOrigin(0).setAlpha(0.5);
const container = this.add.container(200, 100);
const sprite = this.add.sprite(100, 100, 'eye').setInteractive();
container.add(sprite);
sprite.setScale(2, 1);
container.setScale(2);
sprite.setAngle(20);
const text = this.add.text(10, 10, 'Click on sprite', { font: '16px Courier', fill: '#00ff00' });
sprite.on('pointerdown', function () {
this.input.enableDebug(sprite);
}, this);
const gui = new dat.GUI();
const p1 = gui.addFolder('Pointer');
p1.add(this.input, 'x').listen();
p1.add(this.input, 'y').listen();
p1.open();
const c1 = gui.addFolder('Camera');
c1.add(cam1, 'x').listen();
c1.add(cam1, 'y').listen();
c1.add(cam1, 'scrollX').listen();
c1.add(cam1, 'scrollY').listen();
c1.add(cam1, 'rotation').min(0).step(0.01).listen();
c1.add(cam1, 'zoom', 0.1, 2).step(0.1).listen();
c1.open();
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
scene: [Example, OverLayConfig]
};
const game = new Phaser.Game(config);
Структура сцены и настройка сцены-оверлея
В примере используются две сцены: основная (Example) и сцена для оверлея (OverLayConfig). Сцена-оверлей задумана для отрисовки вспомогательной графики, например, рамки вокруг объекта, но в данном примере её функционал не активирован.
В основной сцене в методе preload загружаются два изображения: текстура для спрайта и фоновая сетка для отладки.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('eye', 'assets/pics/lance-overdose-loader-eye.png');
this.load.image('grid', 'assets/pics/debug-grid-1920x1920.png');
}
Метод create — это сердце примера. Здесь происходит вся инициализация. Сначала мы получаем ссылку на основную камеру, даём ей имя 'Camera 1' и добавляем фоновую сетку с полупрозрачностью.
Создание контейнера и работа с трансформациями
Контейнер (Container) — это специальный игровой объект, который группирует другие объекты, позволяя применять к ним общие трансформации (позицию, масштаб, поворот) как к единому целому.
const container = this.add.container(200, 100);
const sprite = this.add.sprite(100, 100, 'eye').setInteractive();
container.add(sprite);
В этом коде создаётся контейнер с начальной позицией (200, 100). Затем создаётся интерактивный спрайт в позиции (100, 100) относительно глобальных координат сцены. После этого спрайт добавляется в контейнер. Важно понимать: после добавления в контейнер позиция, масштаб и угол поворота спрайта начинают вычисляться относительно контейнера.
Далее применяются трансформации к обоим объектам:
sprite.setScale(2, 1);
container.setScale(2);
Сначала спрайт растягивается по оси X в 2 раза. Затем весь контейнер (вместе со спрайтом внутри) также масштабируется в 2 раза. Эти преобразования складываются. Итоговый масштаб спрайта будет (2 * 2 = 4) по оси X и (1 * 2 = 2) по оси Y.
sprite.setAngle(20);
Спрайт внутри контейнера поворачивается на 20 градусов. Если бы мы повернули сам контейнер, повернулись бы все его дочерние элементы.
Отладка и визуализация с помощью dat.GUI
Одна из самых полезных частей примера — интеграция библиотеки dat.GUI для создания панели отладки. Она позволяет в реальном времени отслеживать и изменять параметры.
Панель разделена на две секции. В секции 'Pointer' выводятся текущие координаты курсора мыши (this.input.x, this.input.y). Эти значения обновляются автоматически благодаря методу .listen().
const p1 = gui.addFolder('Pointer');
p1.add(this.input, 'x').listen();
Секция 'Camera' гораздо информативнее. Здесь можно отслеживать и изменять ключевые параметры камеры cam1:
- `x,y` — позиция камеры в мире.
- scrollX, scrollY — смещение (скролл) камеры.
- rotation — угол поворота камеры.
- zoom — уровень масштабирования (зум) камеры.
c1.add(cam1, 'zoom', 0.1, 2).step(0.1).listen();
Попробуйте изменять эти параметры во время работы примера. Вы сразу увидите, как зум, поворот и скролл камеры влияют на отображение всего игрового мира, включая наш контейнер со спрайтом. Это наглядный способ понять, как мир преобразуется через камеру в итоговое изображение на экране.
Взаимодействие и цепочка преобразований
Итоговое положение и вид спрайта на экране определяются цепочкой преобразований: 1. **Локальные трансформации спрайта** (позиция, масштаб, угол) внутри своего родителя — контейнера. 2. **Глобальные трансформации контейнера**, которые применяются ко всем его детям. 3. **Преобразования камеры** (позиция, зум, поворот), которые проецируют мировые координаты в координаты отображения (viewport).
Когда вы кликаете на спрайт, срабатывает обработчик события pointerdown. Он активирует встроенный режим отладки Phaser для этого спрайта с помощью this.input.enableDebug(sprite). На экране появится зелёная рамка с информацией о спрайте, что помогает визуализировать его реальные границы после всех преобразований.
sprite.on('pointerdown', function () {
this.input.enableDebug(sprite);
}, this);
Задача закомментированного кода в сцене OverLayConfig — нарисовать поверх всего эту самую рамку и углы спрайта вручную, используя методы getTopLeft, getTopRight и другие. Эти методы возвращают мировые координаты углов спрайта, которые затем можно было бы преобразовать в координаты камеры с помощью cam1.getWorldPoint. Это низкоуровневый, но полный контроль за отрисовкой отладочной информации.
Что попробовать дальше
Этот пример наглядно показывает мощь системы трансформаций Phaser 3, построенной на принципах иерархии (контейнеры) и проекции (камеры). Понимая, как локальные преобразования объекта становятся мировыми и как камера их отображает, вы получаете полный контроль над визуальной частью игры.
**Идеи для экспериментов:**
1. В панели dat.GUI добавьте управление масштабом и углом поворота самого контейнера. Сравните, как это влияет на спрайт внутри.
2. Раскомментируйте и доработайте код в updateOverlay, чтобы рисовать рамку вокруг спрайта, которая будет корректно отображаться при любом зуме и повороте камеры.
3. Создайте вторую камеру (this.cameras.add) и настройте её на отображение только определённой области. Попробуйте привязать к ней свой dat.GUI для управления двумя камерами одновременно.
