О чем этот пример
При разработке изометрических игр на Phaser вы могли столкнуться с неожиданным исчезновением тайлов при прокрутке камеры. Виной тому — оптимизация отрисовки, называемая кадрированием (culling), которая по умолчанию включена для изометрических слоёв. В этой статье разберём пример кода, демонстрирующий проблему, и покажем, как её решить одним простым вызовом. Этот приём поможет вам создавать стабильные изометрические сцены без "мерцающих" тайлов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
var config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: {
preload: preload,
create: create,
update: update
}
};
var controls;
var layer;
var game = new Phaser.Game(config);
function preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('tiles', 'assets/tilemaps/iso/iso-64x64-outside.png');
}
function fillLayer() {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
layer.putTileAt(0, i, j);
}
}
}
function create ()
{
var map = this.make.tilemap({ tileWidth: 64, tileHeight: 64 });
map.orientation = 1;
var tileset1 = map.addTilesetImage('iso-64x64-outside', 'tiles');
layer = map.createBlankLayer('ground', tileset1);
// layer.setSkipCull(true);
console.log(layer);
var cursors = this.input.keyboard.createCursorKeys();
// this.cameras.main.setScroll(-340, -100);
fillLayer();
var controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
acceleration: 0.04,
drag: 0.0005,
maxSpeed: 0.7
};
controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
}
function update (time, delta)
{
controls.update(delta);
}
Суть проблемы: кадрирование изометрических слоёв
Phaser активно использует кадрирование — технику оптимизации, при которой отрисовываются только объекты, попадающие в область видимости камеры. Для ортогональных карт это работает превосходно. Однако для изометрических (orientation: 1) вычисление видимой области усложняется из-за перспективы.
В некоторых версиях движка алгоритм кадрирования для изометрии может работать некорректно, "обрезая" тайлы, которые должны быть видны. Это приводит к их внезапному исчезновению при движении камеры. В предоставленном примере эта проблема моделируется, даже для маленькой карты 4x4 тайла.
Анализ ключевых строк кода
Давайте посмотрим, как создаётся изометрическая сцена в примере. Ключевой момент — конфигурация карты и слоя.
var map = this.make.tilemap({ tileWidth: 64, tileHeight: 64 });
map.orientation = 1;
Установка orientation в `1` переводит карту в изометрический режим.
layer = map.createBlankLayer('ground', tileset1);
// layer.setSkipCull(true);
Здесь создаётся слой. Закомментированная строка layer.setSkipCull(true) — это и есть решение проблемы. По умолчанию этот метод не вызван, и кадрирование активно.
Функция fillLayer просто заполняет слой тайлами с индексом 0 (первый тайл из tileset).
var controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
acceleration: 0.04,
drag: 0.0005,
maxSpeed: 0.7
};
controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
SmoothedKeyControl добавляет плавное управление камерой с помощью стрелок клавиатуры, что позволяет легко продемонстрировать проблему при движении.
Решение: метод setSkipCull
Phaser предоставляет прямой способ отключить кадрирование для конкретного тайлового слоя. Это метод setSkipCull.
layer.setSkipCull(true);
Вызов этого метода с аргументом true указывает движку не применять оптимизацию кадрирования к данному слою. Все тайлы на слое будут отрисовываться всегда, независимо от положения камеры. Это полностью решает проблему с исчезающими тайлами в изометрической проекции.
Важное замечание: используйте этот метод осознанно. Для очень больших карт отключение кадрирования может привести к падению производительности, так как движок будет пытаться отрисовать сотни или тысячи невидимых тайлов. Однако для карт умеренного размера или в качестве временного решения для бага — это идеальный вариант.
Где и когда вызывать setSkipCull
Метод следует вызывать сразу после создания слоя, до его заполнения данными. Это гарантирует, что корректное состояние будет установлено до любой отрисовки.
В контексте нашего примера, нужно раскомментировать одну строку в функции create:
function create ()
{
// ... создание map и tileset ...
layer = map.createBlankLayer('ground', tileset1);
layer.setSkipCull(true); // <-- Вот эта строка должна быть активна
// ... остальной код ...
}
После этого можно запустить пример и убедиться, что при плавной прокрутке камеры с помощью стрелок все 16 тайлов остаются на экране и не исчезают.
Что попробовать дальше
Метод layer.setSkipCull(true) — это простое и эффективное решение для бага с кадрированием в изометрических слоях Phaser. Оно позволяет сосредоточиться на разработке геймплея, не отвлекаясь на визуальные артефакты.
**Идеи для экспериментов:**
1. Создайте большую изометрическую карту и сравните производительность со включённым и выключенным кадрированием.
2. Попробуйте применить setSkipCull только к определённым слоям (например, к фону), оставив кадрирование для слоёв с объектами.
3. Исследуйте другие свойства слоя, такие как layer.cullPaddingX и layer.cullPaddingY, которые могут помочь настроить область кадрирования более точно.
