О чем этот пример
Миникарта — классический элемент игрового интерфейса, который помогает игрокам ориентироваться в больших мирах. В этом примере мы реализуем не просто статичную иконку карты, а динамическое окно, которое следует за игроком, показывая только его ближайшее окружение. Это достигается с помощью кадрирования (crop) кадра текстуры и привязки позиции кадрирования к координатам игрока. Подход полезен не только для миникарт, но и для создания эффектов видоискателя, динамических масок или отображения только части большой текстуры в UI-элементах. Мы разберем, как использовать методы `setCutSize` и `setCutPosition` объекта `Frame` в связке с физикой Matter.js для плавного передвижения.
Версия 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('map', 'assets/tests/camera/earthbound-scarab.png');
this.load.image('map2', 'assets/tests/camera/earthbound-scarab.png');
this.load.image('ship', 'assets/sprites/shmup-ship2.png');
}
create ()
{
// Set world bounds
this.matter.world.setBounds(0, 0, 800, 600);
this.background = this.add.image(0, 0, "map2").setOrigin(0,0);
// Add a minimap
this.minimap = this.add.image(20, 20, "map").setOrigin(0,0);
this.minimap.frame.setCutSize(64, 64);
// Add a player ship and camera follow
this.player = this.matter.add.sprite(512, 512, 'ship')
.setFixedRotation()
.setFrictionAir(0.05)
.setMass(30);
this.cursors = this.input.keyboard.createCursorKeys();
}
update ()
{
if (this.cursors.left.isDown)
{
this.player.thrustBack(0.1);
this.player.flipX = true;
}
else if (this.cursors.right.isDown)
{
this.player.thrust(0.1);
this.player.flipX = false;
}
if (this.cursors.up.isDown)
{
this.player.thrustLeft(0.1);
}
else if (this.cursors.down.isDown)
{
this.player.thrustRight(0.1);
}
this.minimap.frame.setCutPosition(this.player.x, this.player.y);
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
parent: 'phaser-example',
pixelArt: true,
physics: {
default: 'matter',
matter: {
gravity: {
x: 0,
y: 0
},
enableSleeping: true
}
},
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и загрузка ресурсов
В методе preload загружаются три изображения. Обратите внимание, что map и map2 загружают одну и ту же текстуру (earthbound-scarab.png). Это не ошибка, а осознанный прием: мы будем использовать один и тот же спрайт для фона и для миникарты, но с разными настройками отображения.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('map', 'assets/tests/camera/earthbound-scarab.png');
this.load.image('map2', 'assets/tests/camera/earthbound-scarab.png');
this.load.image('ship', 'assets/sprites/shmup-ship2.png');
}
Создание мира, фона и миникарты
В create сначала настраиваются границы физического мира Matter.js. Затем создается фон (this.background) из текстуры map2. Он занимает всю сцену, начиная с точки (0,0).
Ключевой объект — миникарта (this.minimap). Она создается как изображение из текстуры map и позиционируется в левом верхнем углу. Важный шаг — работа с ее кадром (frame). Метод setCutSize(64, 64) устанавливает размер области кадрирования. Это означает, что из исходной большой текстуры будет виден только прямоугольник 64x64 пикселя.
create ()
{
// Установка границ мира
this.matter.world.setBounds(0, 0, 800, 600);
this.background = this.add.image(0, 0, "map2").setOrigin(0,0);
// Создание миникарты
this.minimap = this.add.image(20, 20, "map").setOrigin(0,0);
this.minimap.frame.setCutSize(64, 64);
}
Создание игрока и управление
Игрок создается как физический спрайт Matter.js с помощью this.matter.add.sprite. Ему задаются свойства: фиксированный поворот (setFixedRotation), небольшое сопротивление воздуха для плавности (setFrictionAir) и масса. Управление осуществляется через курсорные клавиши (this.cursors).
// Создание корабля игрока
this.player = this.matter.add.sprite(512, 512, 'ship')
.setFixedRotation()
.setFrictionAir(0.05)
.setMass(30);
this.cursors = this.input.keyboard.createCursorKeys();
Обновление: управление и движение миникарты
В методе update обрабатывается ввод с клавиатуры. В зависимости от нажатой клавиши вызываются методы thrust, thrustBack, thrustLeft, thrustRight, которые применяют силу к телу Matter.js, заставляя корабль двигаться. Свойство flipX меняется для отражения спрайта по горизонтали в зависимости от направления.
Самая важная строка для миникарты — this.minimap.frame.setCutPosition(this.player.x, this.player.y). Она обновляет позицию области кадрирования (cut) кадра миникарты, привязывая ее центр к текущим координатам игрока. Таким образом, в миникарте всегда отображается та часть большой карты, где сейчас находится игрок.
update ()
{
if (this.cursors.left.isDown)
{
this.player.thrustBack(0.1);
this.player.flipX = true;
}
else if (this.cursors.right.isDown)
{
this.player.thrust(0.1);
this.player.flipX = false;
}
if (this.cursors.up.isDown)
{
this.player.thrustLeft(0.1);
}
else if (this.cursors.down.isDown)
{
this.player.thrustRight(0.1);
}
// Ключевая строка: обновление области видимости миникарты
this.minimap.frame.setCutPosition(this.player.x, this.player.y);
}
Конфигурация игры и физики
Конфиг настраивает игру с физическим движком Matter.js. Обратите внимание на параметры: гравитация отключена (x:0, y:0), что типично для космических или воздушных симуляторов, и включен enableSleeping для оптимизации (неактивные тела перестают рассчитываться). pixelArt: true обеспечивает четкое отображение пиксельной графики.
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
parent: 'phaser-example',
pixelArt: true,
physics: {
default: 'matter',
matter: {
gravity: {
x: 0,
y: 0
},
enableSleeping: true
}
},
scene: Example
};
const game = new Phaser.Game(config);
Что попробовать дальше
Мы создали динамическую миникарту, которая в реальном времени следует за игроком, используя методы кадрирования Frame.setCutSize и Frame.setCutPosition. Этот прием отделяет логику отображения области от логики перемещения, делая код чистым и производительным.
**Идеи для экспериментов:**
1. Измените setCutSize для увеличения или уменьшения области обзора на миникарте.
2. Добавьте к миникарте рамку или фон, создав ее как дочерний элемент контейнера.
3. Реализуйте плавное следование кадрирования (LERP) для более мягкого движения окна миникарты.
4. Используйте этот же принцип для создания «подсветки» или «лупы» над определенными участками большой карты в интерфейсе.
