О чем этот пример
Отладка столкновений в платформерах и головоломках — частая задача. Phaser предоставляет мощный, но не всегда очевидный инструмент — отрисовку граней тайлов (face edges) для визуализации коллайдеров. Эта статья на примере из официального репозитория объяснит, как устроена внутренняя логика расчёта граней, когда они появляются и исчезают, и как этим управлять. Понимание этих принципов сэкономит часы отладки при работе с плиточными картами.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
var config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#ffffff',
parent: 'phaser-example',
pixelArt: true,
scene: {
preload: preload,
create: create
}
};
var game = new Phaser.Game(config);
function preload() {
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.tilemapTiledJSON('mario', 'assets/tilemaps/maps/super-mario.json');
this.load.image('mario-tiles', 'assets/tilemaps/tiles/super-mario.png');
}
function create() {
var level = [
[ 1, 2, 3, 4, 7, 7, 7],
[ 5, 6, 7, 7, 4, 4, 7],
[ 9, 10, 11, 12, 4, 4, 7],
[13, 14, 15, 16, 7, 7, 7]
]
var map = this.make.tilemap({data: level, tileWidth: 16, tileHeight: 16, insertNull: false});
var tileset = map.addTilesetImage('mario-tiles');
var layer = map.createLayer(0, tileset);
layer.setCollision([ 2, 5, 6, 7, 10, 11, 15 ]);
var afterGraphics = this.add.graphics();
afterGraphics.x = layer.x;
afterGraphics.y = layer.y + 150;
afterGraphics.setScale(2, 2);
layer.renderDebug(afterGraphics);
// Run only 1 test at a time to compare before and after render debug
// 1 - putting & recalc
layer.putTileAt(2, 0, 0); // Good - adds left/top faces
// 2 - putting & preventing recalc
// layer.putTileAt(2, 0, 0, false); // Good - adds all faces
// layer.calculateFacesWithin(); // Good - removes right/bottom and leaves left/top
// 3 - putting a colliding index after collision set
// var tile = layer.getTileAt(0, 3);
// layer.putTileAt(2, 1, 3, false); // Good - no face change
// layer.putTileAt(2, 1, 3, true); // Good - face edges change
// 4 - removing
// layer.removeTileAt(1, 1); // Good - adds all inner faces edges
// 5 - removing and manual recalc
// layer.removeTileAt(1, 1, false, false); // Good - doesn't change edges
// layer.calculateFacesWithin(); // Good - removes right/bottom and leaves left/top
// 6 - copying and manual recalc
// layer.copy(0, 0, 2, 2, 4, 0, false); // Good - doesn't change edges
// layer.calculateFacesWithin(); // Good - removes right/bottom and leaves left/top
// 7 - Copying
// layer.copy(0, 0, 2, 2, 4, 1, true); // Good - copies and changes edges (including all neighbors)
// 8 - Putting a Tile
// layer.putTileAt(layer.getTileAt(1, 1), 2, 0, false); // Good - all faces collide
// layer.putTileAt(layer.getTileAt(0, 3), 1, 1, false); // Good - all faces don't collide
// 9 - Filling
// layer.fill(2, 1, 1, 5, 2, false); // Good - no face changes
// layer.fill(2, 1, 1, 5, 2, true); // Good - face changes
var afterGraphics = this.add.graphics();
afterGraphics.x = layer.x - this.sys.scale.width;
afterGraphics.y = layer.y + this.sys.scale.height / 2;
afterGraphics.setScale(2, 2);
layer.renderDebug(afterGraphics);
}
Суть примера: визуализация граней коллайдеров
Исходный код из репозитория Phaser демонстрирует работу с системой граней тайлов (Tile#faceTop, faceBottom и т.д.). Эти грани — логические флаги, которые определяют, с какой стороны тайла находится коллайдер. Они критически важны для корректной работы физики, например, чтобы персонаж мог стоять на платформе (faceTop) и не проваливался сквозь неё.
Пример создаёт небольшую карту из двумерного массива и назначает коллайдеры определённым индексам тайлов. Ключевой метод layer.renderDebug(afterGraphics) рисует поверх тайловой карты визуальное представление этих граней. Красные линии показывают, где система физики «видит» препятствие.
layer.setCollision([ 2, 5, 6, 7, 10, 11, 15 ]);
var afterGraphics = this.add.graphics();
layer.renderDebug(afterGraphics);
Автоматический и ручной пересчёт граней
Phaser старается автоматически обновлять грани соседних тайлов при изменении карты, чтобы коллайдеры оставались консистентными. Однако это поведение можно контролировать. В примере закомментированы несколько тестов, которые показывают разницу.
Флаг recalculateFaces в методах вроде putTileAt определяет, нужно ли сразу пересчитать грани для изменённой области и её соседей. Если передать false, изменения будут изолированы, и грани могут стать некорректными, пока вы вручную не вызовете calculateFacesWithin.
// Тест 2: Ручное управление
layer.putTileAt(2, 0, 0, false); // Не пересчитываем грани автоматически
layer.calculateFacesWithin(); // Пересчитываем вручную для всей карты
Это полезно для массовых операций (заполнение, копирование больших областей), чтобы избежать лишних вычислений после каждого мелкого изменения.
Ключевые операции и их влияние на грани
Рассмотрим основные методы работы с тайлами и то, как они взаимодействуют с флагами граней.
* **Добавление тайла (putTileAt):** Если новый тайл является коллайдером, а его сосед — нет, между ними появится грань. Если оба тайла — коллайдеры, внутренняя грань исчезнет. Флаг recalculateFaces управляет немедленным применением этих правил к соседям.
* **Удаление тайла (removeTileAt):** Удаление коллайдера «откроет» его стороны. Если соседние тайлы — коллайдеры, у них появятся новые грани, обращённые в пустоту.
* **Заполнение (fill) и копирование (copy):** Эти методы работают с областями. Без автоматического пересчёта они просто меняют индексы тайлов. С пересчётом — сразу обновляют все грани в изменённой области и по её границам с соседями.
// Тест 9: Заполнение области
layer.fill(2, 1, 1, 5, 2, false); // Без изменения граней
layer.fill(2, 1, 1, 5, 2, true); // С немедленным пересчётом граней
Практические сценарии использования
Понимание этой механики решает конкретные проблемы в играх.
1. **Деструкция окружения.** Когда игрок разрушает блок (removeTileAt), нужно убедиться, что у соседних блоков появились грани, обращённые в образовавшуюся пустоту. Иначе в дыру можно будет «упереться» сбоку, как в невидимую стену.
2. **Генерация или изменение карты.** При программной сборке уровня из кусков (copy) или случайном заполнении (fill) рекомендуется отключать автоматический пересчёт для производительности, а затем запускать один общий calculateFacesWithin для всей новой области.
3. **Динамические препятствия.** Если перемещать коллайдер (удалить старый тайл, добавить новый), нужно аккуратно управлять пересчётом, чтобы физика мира мгновенно обновилась и не возникло артефактов.
// Пример: безопасное разрушение блока с обновлением физики
layer.removeTileAt(x, y, false, false); // Удаляем без пересчёта
layer.calculateFacesWithin(x-1, y-1, 3, 3); // Пересчитываем только локальную зону 3x3
Что попробовать дальше
Система граней в Phaser — это умный механизм для поддержания целостности коллайдеров в тайловых мирах. Главный вывод: автоматический пересчёт удобен для точечных изменений, но для сложных операций эффективнее ручной контроль через calculateFacesWithin. Для экспериментов попробуйте реализовать разрушаемую стену, где каждый разрушенный кирпич корректно обновляет грани соседей, или генератор лабиринта, который после создания всей карты одним вызовом рассчитывает коллайдеры.
