О чем этот пример
Создание изометрических игр добавляет уникальный визуальный стиль, но усложняет логику взаимодействия. Классические методы получения тайлов по координатам экрана для ортогональных проекций не работают корректно. В этой статье мы разберем, как правильно получать изометрические тайлы при клике мышью, используя специальный API Phaser. Этот подход особенно полезен для стратегий, RPG или симуляторов с изометрической графикой, где важно точно определять, на какой элемент игрового мира кликнул пользователь.
Версия 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/isometric-sandbox-sheet.png');
this.load.tilemapTiledJSON('map', 'assets/tilemaps/iso/tilemaps/sandbox.json');
}
create ()
{
const map = this.add.tilemap('map');
const tileset = map.addTilesetImage('isometric-sandbox-sheet', 'tiles');
const layer = map.createLayer('Tile Layer 1', tileset);
// layer.setScale(4, 4);
const cursors = this.input.keyboard.createCursorKeys();
this.cameras.main.centerOn(0, 150);
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);
this.input.on('pointerdown', pointer => {
// const tile = layer.getTileAtWorldXY(pointer.worldX, pointer.worldY);
// const x = pointer.worldX;
// const y = pointer.worldY;
// const tileXY = layer.worldToTileXY(pointer.worldX, pointer.worldY, true);
// const tile = layer.getTileAt(tileXY.x, tileXY.y);
const tile = layer.getIsoTileAtWorldXY(pointer.worldX, pointer.worldY, false);
if (tile)
{
tile.tint = 0xff0000;
}
// const tileX = layer.worldToTileX(x, true);
// const tileY = layer.worldToTileX(y, true);
// console.log(tileXY, tileX, tileY);
// console.log(tileXY);
});
}
update (time, delta)
{
this.controls.update(delta);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
pixelArt: true,
scene: Example
};
const game = new Phaser.Game(config);
Настройка изометрической тайловой карты
Первым шагом является загрузка и создание изометрической карты. Обратите внимание на ключевые параметры конфигурации игры.
const config = {
type: Phaser.AUTO,
pixelArt: true,
scene: Example
};
Флаг pixelArt: true важен для сохранения четкости пиксельной графики при масштабировании. В методе preload загружаются два основных ресурса: изображение тайлсета и JSON-файл карты, созданный в редакторе Tiled.
preload ()
{
this.load.image('tiles', 'assets/tilemaps/iso/tilesets/isometric-sandbox-sheet.png');
this.load.tilemapTiledJSON('map', 'assets/tilemaps/iso/tilemaps/sandbox.json');
}
В create создается объект карты, к нему привязывается загруженное изображение и создается слой для отрисовки.
create ()
{
const map = this.add.tilemap('map');
const tileset = map.addTilesetImage('isometric-sandbox-sheet', 'tiles');
const layer = map.createLayer('Tile Layer 1', tileset);
}
Управление камерой для навигации
Изометрические карты часто обширны, поэтому необходима плавная камера. Phaser предоставляет удобный контроллер SmoothedKeyControl.
Сначала создаем объект управления курсорными клавишами и настраиваем дополнительные клавиши для зума.
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);
update (time, delta)
{
this.controls.update(delta);
}
Параметры acceleration, drag и maxSpeed позволяют тонко настроить ощущение от движения камеры, делая его плавным и отзывчивым.
Проблема получения тайла в изометрии
Основная сложность возникает при попытке определить, по какому тайлу кликнул игрок. В ортогональной проекции для этого достаточно перевести мировые координаты в координаты тайловой сетки с помощью worldToTileXY и получить тайл через getTileAt.
// Этот подход НЕ РАБОТАЕТ корректно для изометрических слоев:
const tileXY = layer.worldToTileXY(pointer.worldX, pointer.worldY, true);
const tile = layer.getTileAt(tileXY.x, tileXY.y);
Метод worldToTileXY предназначен для ортогональных проекций и не учитывает изометрическое преобразование координат. В результате вычисленный индекс тайла будет неверным.
Решение: метод getIsoTileAtWorldXY
Для изометрических тайловых слоев Phaser предоставляет специальный метод getIsoTileAtWorldXY. Он корректно обрабатывает математику изометрической проекции.
В обработчике события клика мыши используем этот метод:
this.input.on('pointerdown', pointer => {
const tile = layer.getIsoTileAtWorldXY(pointer.worldX, pointer.worldY, false);
if (tile)
{
tile.tint = 0xff0000;
}
});
Метод принимает три аргумента:
1. worldX — координата X клика в мировом пространстве.
2. worldY — координата Y клика в мировом пространстве.
3. nonNull (опциональный) — если true, метод всегда вернет объект тайла, даже если клик был между тайлами (в этом случае вернется ближайший). В нашем примере используется false, поэтому при клике в пустое место вернется null.
Если тайл найден, мы меняем его цвет с помощью свойства tint, что визуально подтверждает точность выбора.
Что попробовать дальше
Ключ к взаимодействию с изометрическим миром — использование правильного API. Метод getIsoTileAtWorldXY решает проблему точного определения тайла, абстрагируя разработчика от сложной математики изометрического преобразования.
**Идеи для экспериментов:**
1. Попробуйте параметр nonNull: true в getIsoTileAtWorldXY. Как это меняет поведение при клике между тайлами?
2. Добавьте логику для выделения не одного тайла, а области (например, 3x3).
3. Используйте полученный тайл для изменения его свойств (например, tile.index для смены текстуры) или для запуска игровой логики (постройка здания, выбор юнита).
