О чем этот пример
В процессе разработки игры часто возникает необходимость сделать снимок экрана: для создания мини-карт, системы реплеев или просто отладки визуальных эффектов. Phaser предоставляет удобный метод `snapshotArea`, но его использование с учётом зума и скролла камеры может быть неочевидным. Эта статья покажет, как правильно рассчитать область для снимка, учитывая текущее состояние камеры, и получить именно ту часть игрового мира, которую вы видите на экране.
Версия 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('uv', 'assets/pics/uv-grid-4096-ian-maclachlan.png');
}
create ()
{
this.add.image(2048, 2048, 'uv');
this.cameras.main.setZoom(0.75);
this.cameras.main.setScroll(2000, 1024);
this.renderer.snapshotArea(0, 0, 256, 256, image => {
document.body.appendChild(image);
});
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#2d2d6d',
scene: Example
};
const game = new Phaser.Game(config);
Суть проблемы и исходный пример
Рассмотрим пример из официального репозитория. В нём загружается большая текстурная карта, камера отдаляется с помощью зума и смещается. Затем делается попытка снять небольшой участок экрана.
this.renderer.snapshotArea(0, 0, 256, 256, image => {
document.body.appendChild(image);
});
Проблема этого кода в том, что координаты для snapshotArea (0, 0, 256, 256) задаются в пикселях относительно *canvas*, а не игрового мира. При активном зуме и скролле камеры эти координаты не соответствуют тому, что мы видим в центре экрана. Фактически, этот код всегда снимает левый верхний угол canvas, независимо от положения камеры.
Как работает камера: зум и скролл
Чтобы сделать снимок видимой области, нужно понимать трансформации, которые применяет камера. Два ключевых метода:
this.cameras.main.setZoom(0.75);
this.cameras.main.setScroll(2000, 1024);
* setZoom(0.75) уменьшает масштаб отображения игрового мира до 75%. Это значит, что в один пиксель canvas'а «вмещается» больше игровых единиц.
* setScroll(2000, 1024) смещает точку отсчёта камеры в игровом мире. Теперь в центре (или начале координат) viewport'а камеры находятся мировые координаты (2000, 1024).
Метод snapshotArea работает на уровне рендерера, *после* применения всех трансформаций камеры. Поэтому для расчёта нужной области мы должны использовать не мировые, а экранные (canvas) координаты.
Расчёт области снимка с учётом камеры
Правильный подход — получить экранные координаты нужной точки игрового мира. Для этого у камеры есть метод getWorldPoint. Допустим, мы хотим снять квадрат 256x256 пикселей вокруг текущего центра камеры.
Сначала определим центр камеры в мировых координатах:
const centerWorldX = this.cameras.main.scrollX + (this.cameras.main.width / 2) / this.cameras.main.zoom;
const centerWorldY = this.cameras.main.scrollY + (this.cameras.main.height / 2) / this.cameras.main.zoom;
Затем переведём желаемые границы из мировых координат в экранные. Например, чтобы получить координаты левого верхнего угла квадрата со стороной 256 мировых единиц:
const snapshotWorldWidth = 256;
const snapshotWorldHeight = 256;
const topLeftWorldX = centerWorldX - snapshotWorldWidth / 2;
const topLeftWorldY = centerWorldY - snapshotWorldHeight / 2;
const screenPoint = this.cameras.main.getWorldPoint(topLeftWorldX, topLeftWorldY);
Теперь screenPoint.x и screenPoint.y — это искомые координаты для snapshotArea.
Финальный рабочий код
Объединим все шаги. Следующий код сделает снимок квадрата 256x256 мировых единиц, расположенного по центру текущего viewport'а камеры, с учётом её зума и скролла.
create () {
this.add.image(2048, 2048, 'uv');
this.cameras.main.setZoom(0.75);
this.cameras.main.setScroll(2000, 1024);
// 1. Размер снимка в мировых единицах
const snapWorldSize = 256;
// 2. Находим центр камеры в мире
const camCenterX = this.cameras.main.scrollX + (this.cameras.main.width / 2) / this.cameras.main.zoom;
const camCenterY = this.cameras.main.scrollY + (this.cameras.main.height / 2) / this.cameras.main.zoom;
// 3. Вычисляем мировые координаты левого верхнего угла снимка
const snapWorldX = camCenterX - snapWorldSize / 2;
const snapWorldY = camCenterY - snapWorldSize / 2;
// 4. Конвертируем мировые координаты в экранные (canvas)
const screenPoint = this.cameras.main.getWorldPoint(snapWorldX, snapWorldY);
// 5. Рассчитываем размер области для снимка в пикселях с учётом зума
const screenSize = snapWorldSize * this.cameras.main.zoom;
// 6. Делаем снимок рассчитанной области canvas
this.renderer.snapshotArea(
screenPoint.x,
screenPoint.y,
screenSize,
screenSize,
(image) => {
document.body.appendChild(image);
}
);
}
Ключевой момент: размер области для snapshotArea также должен быть пересчитан через умножение на this.cameras.main.zoom, так как зум влияет на то, сколько пикселей canvas'а занимает одна мировая единица.
Что попробовать дальше
Использование snapshotArea для снимка части игрового мира требует пересчёта координат из мировой системы в систему координат canvas. Для этого используйте метод камеры getWorldPoint. Этот подход универсален и будет работать при любых трансформациях камеры. Для экспериментов попробуйте сделать снимок не статичной области, а, например, области вокруг игрового персонажа в реальном времени или реализовать создание динамической мини-карты уровня на основе серии таких снимков.
