О чем этот пример
При разработке интерфейсов или сложных визуальных эффектов в играх часто требуется разделить логику отображения игрового мира и UI-элементов. В Phaser 3 камеры предоставляют мощный метод `ignore()`, позволяющий гибко управлять тем, какие объекты отрисовывает каждая из них. Эта техника особенно полезна для создания статических или независимо анимируемых панелей поверх игрового мира, без необходимости манипулировать глубиной (depth) каждого элемента вручную. Вы сможете изолировать UI от трансформаций игровой камеры (зума, поворота) и наоборот.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('einstein', 'assets/pics/ra-einstein.png');
}
create ()
{
const image = this.add.image(400, 300, 'einstein');
this.UIText1 = this.add.text(0, 32, '0');
this.UIText2 = this.add.text(0, 64, '0');
this.UIText3 = this.add.text(540, 64, '3');
this.cont1 = this.add.container();
this.cont2 = this.add.container();
this.cont1.add(this.UIText1);
this.cont1.add(this.UIText2);
this.cont2.add(this.UIText3);
this.group = this.add.group();
this.group.add(this.cont1);
this.group.add(this.cont2);
// Add in a new camera, the same size and position as the main camera
const UICam = this.cameras.add(0, 0, 800, 600);
// The main camera will not render the children
this.cameras.main.ignore(this.group.getChildren());
// The new UI Camera will not render the background image
UICam.ignore(image);
}
update ()
{
this.UIText1.setText("Main camera rotation: " + this.cameras.main.rotation);
this.UIText2.setText("Main camera zoom: " + this.cameras.main.zoom);
this.UIText3.setText("lol: " + this.cont2.y);
//wobble the container
this.cont2.y = Math.sin(this.time.now / 100) * 10;
this.cameras.main.setZoom(Math.abs(Math.sin(this.cameras.main.rotation)) * 0.5 + 1);
this.cameras.main.rotation += 0.01;
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Зачем нужен метод ignore()?
Основная камера в Phaser (this.cameras.main) по умолчанию отрисовывает все игровые объекты в сцене. Однако в сложных сценах, например, когда игровой мир вращается или масштабируется, элементы пользовательского интерфейса (счётчики, кнопки, панели) должны оставаться стабильными на экране.
Метод ignore() позволяет указать камере список объектов (или целые группы и контейнеры), которые она должна исключить из процесса отрисовки. Это создаёт чистую архитектуру: одна камера отвечает за мир, другая — за интерфейс.
// Основная камера перестанет отрисовывать детей группы
this.cameras.main.ignore(this.group.getChildren());
// Новая UI-камера не будет отрисовывать фоновое изображение
UICam.ignore(image);
Структура примера: контейнеры, группы и камеры
В примере создаётся два типа объектов: фоновое изображение (image) и элементы UI, сгруппированные для удобства.
Текстовые объекты (UIText1, UIText2, UIText3) добавляются в контейнеры (cont1, cont2). Контейнеры позволяют трансформировать (например, перемещать) все дочерние объекты как единое целое.
Затем эти контейнеры добавляются в группу (group). Группа в Phaser — это способ управления коллекцией объектов.
Создаётся вторая камера (UICam), идентичная по размеру и позиции основной. Теперь у нас две независимые системы отрисовки.
// Создание контейнеров и группы
this.cont1 = this.add.container();
this.cont2 = this.add.container();
this.group = this.add.group();
// Добавление объектов в контейнеры и группу
this.cont1.add(this.UIText1);
this.cont1.add(this.UIText2);
this.group.add(this.cont1);
this.group.add(this.cont2);
// Добавление новой камеры
const UICam = this.cameras.add(0, 0, 800, 600);
Разделение отрисовки мира и UI
Ключевой момент — применение метода ignore() к камерам. Мы говорим основной камере игнорировать все объекты внутри нашей группы (которая содержит контейнеры с текстом). Это означает, что вращение и зум основной камеры не будут влиять на отображение текста.
В то же время новой UI-камере (UICam) мы приказываем игнорировать фоновое изображение. Таким образом, UI-камера будет отрисовывать только те объекты, которые не были ей явно проигнорированы — в данном случае, нашу группу с текстом.
В результате текст остаётся стабильным на экране, в то время как фон, управляемый основной камерой, вращается и зумируется.
// Основная камера игнорирует UI-объекты
this.cameras.main.ignore(this.group.getChildren());
// UI-камера игнорирует фоновое изображение
UICam.ignore(image);
Анимация и обновление
В функции update() происходят две независимые анимации, демонстрирующие разделение логики.
1. Анимация UI: контейнер cont2 (с текстом "lol") слегка колеблется вверх-вниз с помощью синуса. Это анимация, управляемая напрямую через свойство `y`, и она видна только через UI-камеру.
2. Анимация мира: основная камера постоянно поворачивается (rotation) и её зум (zoom) пульсирует. Эти трансформации влияют только на фоновое изображение Эйнштейна, так как UI-объекты для этой камеры проигнорированы.
Текстовые поля обновляются для отображения текущих значений вращения и зума основной камеры.
update ()
{
// Обновление текста значениями из основной камеры
this.UIText1.setText("Main camera rotation: " + this.cameras.main.rotation);
this.UIText2.setText("Main camera zoom: " + this.cameras.main.zoom);
this.UIText3.setText("lol: " + this.cont2.y);
// Анимация UI: покачивание контейнера
this.cont2.y = Math.sin(this.time.now / 100) * 10;
// Анимация мира: зум и вращение основной камеры
this.cameras.main.setZoom(Math.abs(Math.sin(this.cameras.main.rotation)) * 0.5 + 1);
this.cameras.main.rotation += 0.01;
}
Что попробовать дальше
Использование метода ignore() камер — это эффективный и производительный способ разделить слои отрисовки в вашей игре. Вместо того чтобы управлять глубиной множества объектов, вы назначаете им разные камеры.
**Идеи для экспериментов:**
1. Создайте несколько UI-камер для разных частей интерфейса (например, одна для HUD, другая для диалоговых окон) и управляйте их видимостью с помощью setVisible().
2. Попробуйте применить ignore() к физическим телам (physics.add.sprite), чтобы создать эффект "мира в мире", где физика работает только в определённой области экрана.
3. Добавьте эффекты постобработки (через setPostPipeline) только к основной камере, оставив UI-элементы чёткими.
