О чем этот пример
Гексагональные (шестиугольные) тайлы — это мощный инструмент для создания стратегических игр, пошаговых тактик или карт с нестандартной геометрией. В отличие от квадратной сетки, гексагоны обеспечивают более естественные и равные расстояния для перемещения юнитов. В этой статье мы разберем готовый пример из официального репозитория Phaser, который демонстрирует загрузку гексагональной карты, создание камеры с плавным управлением и добавление интерактивности — окрашивание тайлов по клику. Вы научитесь работать с тайлмапами из Tiled и управлять камерой в изометрическом (или псевдоизометрическом) пространстве.
Версия 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('tiles', 'assets/tilemaps/iso/tilesets/hex-tiles.png');
this.load.tilemapTiledJSON('map', 'assets/tilemaps/hex/10x10-test.json');
}
create ()
{
const map = this.add.tilemap('map');
const tileset = map.addTilesetImage('hex-tiles', 'tiles');
const layer = map.createLayer('Tile Layer 1', tileset);
// layer.setScale(2.8, 1.4);
const cursors = this.input.keyboard.createCursorKeys();
const controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
acceleration: 0.02,
drag: 0.0005,
maxSpeed: 0.7
};
this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
const cols = [
0xff0000,
0x00ff00,
0x0000ff,
0xff00ff,
0xffff00,
0x00ffff
];
let c = 0;
this.input.on('pointerdown', pointer => {
const tile = layer.getTileAtWorldXY(pointer.worldX, pointer.worldY);
if (tile)
{
tile.tint = cols[c];
c++;
if (c === cols.length)
{
c = 0;
}
}
});
}
update (time, delta)
{
this.controls.update(delta);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
pixelArt: true,
scene: Example
};
const game = new Phaser.Game(config);
Загрузка ресурсов и создание карты
Весь процесс начинается в методе preload. Здесь загружаются два ключевых ресурса: изображение с набором тайлов (tileset) и JSON-файл карты, созданный в редакторе Tiled.
this.load.image('tiles', 'assets/tilemaps/iso/tilesets/hex-tiles.png');
this.load.tilemapTiledJSON('map', 'assets/tilemaps/hex/10x10-test.json');
В методе create мы создаем объект тайлмапы, связываем загруженное изображение с тайлсетом внутри карты и, наконец, создаем слой для отрисовки.
const map = this.add.tilemap('map');
const tileset = map.addTilesetImage('hex-tiles', 'tiles');
const layer = map.createLayer('Tile Layer 1', tileset);
Важный момент: имя тайлсета ('hex-tiles') — это то имя, которое было задано внутри редактора Tiled при создании карты. Второй аргумент ('tiles') — это ключ, под которым изображение было загружено в кеш Phaser. Метод addTilesetImage выполняет связывание этих двух сущностей.
Комментированная строка layer.setScale(2.8, 1.4); показывает, как можно масштабировать слой, если исходный размер тайлов не соответствует нужному масштабу игры.
Плавное управление камерой с клавиатуры
Для исследования большой карты необходима подвижная камера. В примере используется Phaser.Cameras.Controls.SmoothedKeyControl — специальный контроллер, который обеспечивает плавное, инерционное движение с ускорением и ограничением максимальной скорости.
Сначала создается объект конфигурации controlConfig. В нем указывается камера (this.cameras.main), привязка клавиш стрелок для направления, а также клавиши `QиE` для зума.
const controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
acceleration: 0.02,
drag: 0.0005,
maxSpeed: 0.7
};
Параметры acceleration (ускорение), drag (сопротивление, замедляющее движение) и maxSpeed (максимальная скорость) тонко настраивают "ощущения" от управления. Контроллер создается на основе этой конфигурации и обновляется каждый кадр в методе update.
this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
// ...
update(time, delta) {
this.controls.update(delta);
}
Использование delta (время, прошедшее с предыдущего кадра) обеспечивает независимость скорости движения камеры от частоты кадров.
Интерактивность: определение и окрашивание тайлов
Сердце интерактивности в примере — обработчик события pointerdown. Его задача: определить, по какому именно гексагональному тайлу кликнул игрок, и изменить его цвет.
Ключевую роль играет метод layer.getTileAtWorldXY(pointer.worldX, pointer.worldY). Он преобразует координаты указателя в мировой системе координат (уже с учетом позиции камеры) в конкретный тайл на слое. Это особенно важно для гексагональных и изометрических карт, где преобразование координат не является тривиальным — Phaser делает это за нас.
this.input.on('pointerdown', pointer => {
const tile = layer.getTileAtWorldXY(pointer.worldX, pointer.worldY);
if (tile) {
tile.tint = cols[c];
c++;
if (c === cols.length) { c = 0; }
}
});
Если тайл найден (if (tile)), мы меняем его свойство tint, устанавливая один из предопределенных цветов из массива cols. Счетчик `cциклически перебирает все цвета. Свойствоtint` — это эффективный способ окрашивания спрайта (и тайла) без создания дополнительных текстур.
Конфигурация игры и важные настройки
Конфигурационный объект config содержит несколько важных для работы с пиксельной графикой и тайлмапами настроек.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
pixelArt: true,
scene: Example
};
Установка pixelArt: true — критически важный момент. Эта опция отключает линейную интерполяцию текстур при масштабировании, сохраняя четкие, "пиксельные" края. Без этого настройки гексагональные тайлы при масштабировании камеры (зуме) могут выглядеть размытыми.
Также обратите внимание, что сцена передается напрямую через свойство scene. Для простых примеров этого достаточно, но в более крупных проектах вы можете использовать массив сцен или фабричный метод.
Что попробовать дальше
Этот пример дает прочную основу для работы с гексагональными сетками в Phaser. Вы научились загружать карты из Tiled, управлять камерой с плавной инерцией и взаимодействовать со сложной тайловой структурой. Для экспериментов попробуйте:
1. Изменить параметры acceleration, drag и maxSpeed в контроллере камеры, чтобы добиться совершенно другого "фелинга".
2. Вместо смены цвета (tint) реализовать логику игры: размещать юнитов, менять тип местности или вычислять путь.
3. Активировать масштабирование слоя (layer.setScale) и подобрать коэффициенты для интеграции гексагональной карты в игру с другими элементами интерфейса.
4. Добавить отладочную отрисовку хитбоксов тайлов, чтобы визуально убедиться в точности попадания getTileAtWorldXY.
