О чем этот пример
Камера в Phaser — мощный инструмент для управления областью видимости игрового мира. Однако одновременное использование зума (`zoom`) и области отрисовки (`setViewport`) может привести к неочевидным результатам. Эта статья поможет понять, как правильно совмещать эти параметры, чтобы избежать нежелательного кадрирования или смещения объектов. Мы разберем реальный пример из официальной документации Phaser, где показаны типичные ошибки и их решения. Вы научитесь контролировать видимую область сцены, что особенно полезно для создания мини-карт, режимов наблюдения или динамического изменения масштаба.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
var scroller;
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('pic', 'assets/pics/skull-and-bones.jpg');
this.load.image('vulkaiser', 'assets/pics/vulkaiser-red.png');
this.load.image('mushroom', 'assets/sprites/mushroom2.png');
}
create ()
{
// In the middle
this.add.image(400, 300, 'pic');
// Why isn't this cropped?
scroller = this.add.image(400, 300, 'vulkaiser').setScale(4).setAlpha(0.3);
// In the corners
this.add.image(0, 0, 'mushroom').setOrigin(0, 0);
this.add.image(800, 0, 'mushroom').setOrigin(1, 0);
this.add.image(0, 600, 'mushroom').setOrigin(0, 1);
this.add.image(800, 600, 'mushroom').setOrigin(1, 1);
const cam = this.cameras.main;
// Test 1 - Change the x/y and keep w/h the same and the scene is cropped properly. The x/y appear to offset from the top left.
// cam.setViewport(0, 0, 800, 600);
// Test 2 - Combine Test 1 with 'zoom' and the scene is no longer cropped and the viewport size is scaled wrong
// cam.zoom = 0.5;
// Test 3 - Try half zoom and a half viewport size together - appears broken, image still pops out of the top, placement seems wrong
cam.setViewport(300, 100, 400, 300);
cam.zoom = 0.5;
cam.scrollX = 200;
cam.scrollY = 150;
// Test 4 - Change position as if zoom is applied to the camera center, but keep original w/h
// Seems to place the camera in the correct position, but scaled items appear outside of the viewport (because scissor width = 800x600)
// cam.setViewport(-200, -150, 800, 600);
// cam.zoom = 0.5;
// Test 5 - Change position and viewport with zoom. Gets scissored properly but scale is all wrong (half size it should be) and weirdly offset.
// cam.setViewport(-200, -150, 400, 300);
// cam.zoom = 0.5;
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
scene: Example
};
const game = new Phaser.Game(config);
Инициализация сцены и загрузка ассетов
В примере создается стандартная сцена Phaser с тремя изображениями. Код в preload загружает ресурсы с удаленного сервера, используя базовый URL.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('pic', 'assets/pics/skull-and-bones.jpg');
this.load.image('vulkaiser', 'assets/pics/vulkaiser-red.png');
this.load.image('mushroom', 'assets/sprites/mushroom2.png');
Расстановка объектов и получение главной камеры
В методе create размещаются четыре изображения: фоновое, полупрозрачный спрайт vulkaiser (который будет масштабирован) и четыре гриба по углам холста. Обратите внимание, что для грибов устанавливается точка происхождения (setOrigin), чтобы прижать их к соответствующим углам.
this.add.image(400, 300, 'pic');
scroller = this.add.image(400, 300, 'vulkaiser').setScale(4).setAlpha(0.3);
this.add.image(0, 0, 'mushroom').setOrigin(0, 0);
this.add.image(800, 0, 'mushroom').setOrigin(1, 0);
this.add.image(0, 600, 'mushroom').setOrigin(0, 1);
this.add.image(800, 600, 'mushroom').setOrigin(1, 1);
const cam = this.cameras.main;
Переменная cam теперь ссылается на главную камеру сцены (this.cameras.main). С ней мы и будем экспериментировать.
Эксперименты с вьюпортом и зумом
В исходном коде закомментированы несколько тестов. Давайте разберем активный (Тест 3). Он устанавливает вьюпорт камеры — прямоугольную область на canvas, где будет отрисовываться мир.
cam.setViewport(300, 100, 400, 300);
cam.zoom = 0.5;
cam.scrollX = 200;
cam.scrollY = 150;
Метод setViewport(300, 100, 400, 300) задает область отрисовки: она начинается в точке (300, 100) на canvas и имеет размеры 400x300 пикселей. Параметр zoom = 0.5 уменьшает масштаб отображаемого мира в два раза. Свойства scrollX и scrollY смещают точку, которую камера смотрит в игровом мире.
**Важно:** setViewport определяет «окошко» на canvas, а zoom и scroll влияют на то, какая часть игрового мира в это окошко попадает. Их совместная работа не всегда интуитивна.
Почему объекты могут выходить за границы?
Проблема в Тесте 3 (и других закомментированных) — в конфликте между системой отсечения (вьюпорт) и трансформацией мира (зум). Когда вы применяете зум, мир масштабируется относительно центра камеры. Но область отсечения (setViewport) остается жестко заданной на canvas. Если масштабированный объект оказывается частично за пределами этой области, он будет обрезан (scissored).
В исходном примере спрайт vulkaiser масштабирован в 4 раза (setScale(4)), а затем камера применяет зум 0.5. Итоговый масштаб спрайта — 2. При текущих настройках вьюпорта и скролла часть этого крупного спрайта может вылезать за установленные границы, создавая визуальный артефакт.
Правильный подход к управлению областью видимости
Для точного контроля используйте камеру последовательно:
1. **Определите цель.** Нужно ли вам просто уменьшить масштаб всей сцены (используйте zoom)? Или показать только её часть в определенном месте на экране (используйте setViewport)?
2. **Настройте скролл.** Свойства scrollX и scrollY перемещают "прицел" камеры по игровому миру. Их значения указываются в координатах игрового мира, а не canvas.
3. **Проверяйте вьюпорт.** Если вы используете setViewport, помните, что это абсолютные координаты на canvas. Они не масштабируются с zoom.
// Пример: показать увеличенную часть мира в правом нижнем углу canvas
cam.setViewport(400, 300, 400, 300); // Окно 400x300 в правом нижнем квадранте
cam.zoom = 2.0; // Увеличение в 2 раза
cam.centerOn(200, 150); // Смотрим на точку (200,150) в мире
Что попробовать дальше
Ключ к управлению камерой в Phaser — понимание разделения систем: setViewport управляет областью отрисовки на экране, а zoom и scroll — тем, как мир проецируется в эту область. Для экспериментов попробуйте: создать UI-элемент (например, мини-карту) с помощью отдельной камеры и своего вьюпорта; реализовать плавное приближение к объекту, анимируя zoom и scrollX/Y; проверить, как setBounds камеры взаимодействует с вьюпортом при скроллинге.
