О чем этот пример
При разработке игр часто возникает задача разделения интерфейса и игрового мира. Например, нужно чтобы элементы HUD (здоровье, очки) не вращались вместе с камерой или не увеличивались при зуме. Phaser предлагает мощный механизм для такого разделения - фильтры камер (`cameraFilter`). Эта статья покажет, как использовать свойство `cameraFilter` игровых объектов и метод `setCamera()` для тонкого управления тем, в каких камерах отображаются объекты. Вы научитесь создавать независимые UI-камеры и назначать объекты только конкретным камерам.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
var UI_CAM_1;
var UI_CAM_2;
var UIText1;
var UIText2;
var UIText3;
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');
UIText1 = this.add.text(0, 32, '0');
UIText2 = this.add.text(0, 64, '0');
UIText3 = this.add.text(500, 64, '0');
UI_CAM_1 = this.cameras.add();
UI_CAM_2 = this.cameras.add();
//list of all cameras
this.camlist = this.cameras.cameras;
//exclude gameobject to some camera
UIText1.cameraFilter = this.setCamera(UI_CAM_1);
UIText2.cameraFilter = this.setCamera(UI_CAM_1);
UIText3.cameraFilter = this.setCamera(UI_CAM_2);
image.cameraFilter = this.setCamera(this.cameras.main);
}
update ()
{
UIText1.setText("UI Camera 1");
UIText2.setText("Main camera rotation: " + this.cameras.main.rotation);
UIText3.setText("UI Camera 2");
UI_CAM_1.scrollY = Math.sin(this.time.now / 100) * 10;
UI_CAM_2.scrollX = 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;
}
setCamera(cam)
{
let l = (1 << this.camlist.length) - 1;
return l & ~cam.id;
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Что такое cameraFilter и зачем он нужен
Каждый игровой объект в Phaser (наследник GameObject) имеет свойство cameraFilter. Это 32-битное число, где каждый бит соответствует конкретной камере в игре. Если бит установлен в 0 - объект будет отображаться в этой камере, если в 1 - будет скрыт.
По умолчанию все объекты отображаются во всех камерах. Это удобно для большинства случаев, но становится проблемой при создании сложных сцен с несколькими камерами для разных целей (например, основная камера для мира + UI-камера для интерфейса).
// Пример: объект будет виден только в камере с id=0
UIText1.cameraFilter = 0b11111110; // Все биты кроме нулевого = 1
Создание и настройка дополнительных камер
В Phaser можно создавать любое количество камер через this.cameras.add(). Каждая новая камера получает уникальный идентификатор, который используется в фильтрах.
В нашем примере создаются две дополнительные камеры для UI элементов и используется основная камера (this.cameras.main) для фонового изображения.
// Создание дополнительных камер
UI_CAM_1 = this.cameras.add();
UI_CAM_2 = this.cameras.add();
// Получение списка всех камер сцены
this.camlist = this.cameras.cameras;
Все камеры по умолчанию имеют одинаковые размеры и положение, но их можно независимо настраивать - двигать, вращать, зумировать.
Метод setCamera() и работа с битовыми масками
Ключевой метод в примере - setCamera(cam), который вычисляет правильную битовую маску для фильтра камер. Он работает по принципу: "отображать объект во всех камерах, КРОМЕ указанной".
Давайте разберем логику метода:
setCamera(cam) {
// Создаем маску где все биты установлены в 1
// Например, для 3 камер: (1 << 3) - 1 = 0b111
let l = (1 << this.camlist.length) - 1;
// Инвертируем бит конкретной камеры
// Например, для камеры с id=1: 0b111 & ~1 = 0b111 & 0b110 = 0b110
return l & ~cam.id;
}
В битовой маске 0b110 (в двоичном виде):
- Бит 0 = 0 → объект виден в камере с id=0
- Бит 1 = 1 → объект скрыт в камере с id=1
- Бит 2 = 0 → объект виден в камере с id=2
Этот метод вызывается для каждого объекта, чтобы назначить его конкретным камерам:
UIText1.cameraFilter = this.setCamera(UI_CAM_1);
image.cameraFilter = this.setCamera(this.cameras.main);
Динамическое обновление камер в update()
В методе update() демонстрируется независимое управление разными камерами. Каждая камера может иметь свои собственные трансформации, которые не влияют на объекты в других камерах.
update() {
// UI_CAM_1 двигается по вертикали
UI_CAM_1.scrollY = Math.sin(this.time.now / 100) * 10;
// UI_CAM_2 двигается по горизонтали
UI_CAM_2.scrollX = 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;
}
Важно: хотя основная камера вращается, тексты UIText1 и UIText2 остаются неподвижными, потому что они назначены только UI_CAM_1. Текст UIText3 двигается горизонтально, так как он в UI_CAM_2. Изображение Эйнштейна вращается и зумируется вместе с основной камерой.
Практическое применение в играх
Техника фильтров камер особенно полезна в следующих сценариях:
1. **Разделение UI и игрового мира** - элементы интерфейса не должны вращаться/зумироваться с основной камерой 2. **Миникарты** - создание отдельной камеры для отображения карты в углу экрана 3. **Сплит-скрин** - несколько игроков на одном экране с разными камерами 4. **Эффекты пост-обработки** - применение шейдеров только к определенным камерам
Пример настройки HUD камеры:
// Создаем камеру для HUD
const hudCamera = this.cameras.add(0, 0, 800, 600);
hudCamera.setScroll(0, 0); // Фиксируем положение
// Назначаем элементы HUD только этой камере
healthBar.cameraFilter = this.setCamera(hudCamera);
scoreText.cameraFilter = this.setCamera(hudCamera);
Таким образом, при любых трансформациях основной камеры (дрожание, зум, вращение в босс-битвах) элементы интерфейса останутся стабильными.
Что попробовать дальше
Фильтры камер в Phaser 3 предоставляют мощный инструмент для управления видимостью объектов в многокамерных сценах. Используя битовые маски и свойство cameraFilter, вы можете создавать сложные композиции с независимыми слоями отображения.
Для экспериментов попробуйте:
1. Создать систему из 3-4 камер с разными эффектами
2. Реализовать динамическое переключение фильтров во время игровых событий
3. Комбинировать фильтры камер с системами глубины (depth) для сложных UI-композиций
4. Создать камеру только для эффектов частиц, которые должны игнорировать зум основной камеры
