О чем этот пример
Создание больших игровых миров — это лишь половина дела. Вторая, не менее важная часть — дать игроку возможность комфортно исследовать эти просторы. В этой статье мы разберем, как в Phaser работает камера, ее свойства `worldView` и как реализовать плавное управление движением камеры с помощью клавиатуры. Вы научитесь контролировать видимую область мира и поймете разницу между координатами камеры и границами ее видимой области.
Версия 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('grid', 'assets/pics/uv-grid-4096-ian-maclachlan.png');
}
create ()
{
this.add.image(0, 0, 'grid')
.setOrigin(0);
const cursors = this.input.keyboard.createCursorKeys();
const controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
acceleration: 0.02,
drag: 0.0005,
maxSpeed: 1.0
};
this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
const cam = this.cameras.main;
cam.setBounds(0, 0, 4096, 4096).setZoom(1);
const gui = new dat.GUI();
gui.addFolder('Camera');
gui.add(cam.midPoint, 'x').listen();
gui.add(cam.midPoint, 'y').listen();
gui.add(cam, 'scrollX').listen();
gui.add(cam, 'scrollY').listen();
gui.add(cam, 'width').listen();
gui.add(cam, 'height').listen();
gui.add(cam, 'displayWidth').listen();
gui.add(cam, 'displayHeight').listen();
gui.add(cam, 'zoom', 0.1, 4).step(0.1);
gui.add(cam.worldView, 'left').listen();
gui.add(cam.worldView, 'top').listen();
gui.add(cam.worldView, 'right').listen();
gui.add(cam.worldView, 'bottom').listen();
}
update (time, delta)
{
this.controls.update(delta);
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Создание мира и плавного управления камерой
В примере создается большой мир размером 4096x4096 пикселей, представленный текстурой-сеткой. Ключевой элемент — это плавное управление камерой с помощью клавиш-стрелок.
Сначала мы загружаем изображение и размещаем его в начале координат мира (0,0). Затем создаем объект управления SmoothedKeyControl. Эта система обеспечивает инерционное движение камеры с ускорением и замедлением, что чувствуется гораздо естественнее, чем мгновенное перемещение.
const controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
acceleration: 0.02,
drag: 0.0005,
maxSpeed: 1.0
};
this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
Конфигурация связывает камеру с клавишами, задает ускорение (acceleration), сопротивление (drag) для плавной остановки и максимальную скорость. Объект this.controls затем обновляется в каждом кадре в методе update.
Настройка камеры: границы и зум
Чтобы камера не вылетала за пределы нашего мира, необходимо задать ее границы. Это делается с помощью метода setBounds. Также мы сразу устанавливаем базовый уровень масштабирования (zoom) равным 1.
const cam = this.cameras.main;
cam.setBounds(0, 0, 4096, 4096).setZoom(1);
Метод setBounds принимает начальные координаты X, Y и размеры области, за которую камера не может выйти. Это критически важно для игр с фиксированной картой, чтобы игрок не уводил взгляд в пустоту за ее границами.
Свойства `scrollX/Y` и `midPoint`: где находится камера?
Phaser предоставляет несколько свойств для отслеживания позиции камеры. Наиболее интуитивно понятные — это scrollX и scrollY. Они представляют собой координаты верхнего левого угла видимой области камеры в мировом пространстве.
Другой полезной точкой отсчета является midPoint — объект с полями `xиy. Это координаты центра камеры в мире. Когда вы двигаете камеру, именноscrollX/YиmidPoint` изменяются первыми и напрямую.
gui.add(cam.midPoint, 'x').listen();
gui.add(cam.midPoint, 'y').listen();
gui.add(cam, 'scrollX').listen();
gui.add(cam, 'scrollY').listen();
Код выше добавляет эти свойства в панель отладки dat.GUI с флагом listen(), чтобы они обновлялись в реальном времени.
Секрет видимой области: объект `worldView`
Самое важное свойство для расчетов видимости — это cam.worldView. Это объект типа Phaser.Geom.Rectangle. Он описывает прямоугольную область мира, которая в данный момент видна на экране игрока.
В отличие от scrollX/Y (координаты угла), worldView хранит абсолютные мировые координаты всех четырех границ видимой области.
gui.add(cam.worldView, 'left').listen();
gui.add(cam.worldView, 'top').listen();
gui.add(cam.worldView, 'right').listen();
gui.add(cam.worldView, 'bottom').listen();
Используйте worldView, когда вам нужно определить, находится ли игровой объект (например, враг или предмет) в поле зрения камеры для его активации или отрисовки. Это гораздо эффективнее, чем расчеты на основе scrollX/Y и размера экрана.
Разрешение, зум и их влияние на `worldView`
Камера имеет два набора размеров: внутренние (width, height) и отображаемые (displayWidth, displayHeight). Внутренние размеры — это разрешение области, которую рендерит камера (часто равно размеру canvas). Отображаемые размеры — это внутренние размеры, умноженные на текущий zoom.
Именно отображаемые размеры (displayWidth, displayHeight) определяют, какую область мира (worldView) захватывает камера. При увеличении зума (zoom > 1) отображаемые размеры уменьшаются, а worldView сужается, показывая меньший участок мира, но в большем масштабе.
gui.add(cam, 'displayWidth').listen();
gui.add(cam, 'displayHeight').listen();
gui.add(cam, 'zoom', 0.1, 4).step(0.1);
Изменение значения zoom в панели управления наглядно демонстрирует эту связь: границы worldView меняются, а scrollX/Y остаются прежними, так как точка привязки камеры (ее верхний левый угол) не двигается.
Что попробовать дальше
Понимание разницы между scrollX/Y, midPoint и worldView — ключ к управлению видимой областью в Phaser. worldView — ваш главный инструмент для проверки видимости объектов и оптимизации рендеринга. Для экспериментов попробуйте
- Реализовать следящую камеру, которая мягко обновляет
scrollX/Y, и наблюдайте за изменениемworldView - Написать функцию, которая активирует врагов только когда они попадают в
worldViewкамеры - Связать зум камеры с колесом мыши для динамического приближения и отдаления
