О чем этот пример
При разработке игр с большими тайловыми картами критически важно, чтобы движок отсекал (culled) невидимые тайлы, повышая производительность. Однако, когда вы начинаете применять смещения слоёв, факторы прокрутки, масштабирование или работать с картами из тайлов разного размера, логика отсечения может дать сбой, и на экране появятся артефакты. Эта статья разбирает официальный тестовый пример Phaser, который наглядно проверяет корректность отсечения тайлов в различных сложных сценариях. Вы научитесь создавать аналогичные тесты для своих проектов, чтобы быть уверенными в стабильности графики и эффективности рендеринга.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
var config = {
type: Phaser.WEBGL,
width: 1000,
height: 800,
backgroundColor: '#2d2d88',
parent: 'phaser-example',
pixelArt: true,
scene: {
preload: preload,
create: create,
update: update
}
};
var totalTests = 0;
var testsPassed = 0;
var assert = (message, condition) => {
totalTests++;
if (condition) testsPassed++;
console.assert(condition, message)
};
var game = new Phaser.Game(config);
function preload() {
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.tilemapTiledJSON('desert', 'assets/tilemaps/maps/desert.json');
this.load.image('desert-tiles', 'assets/tilemaps/tiles/tmw_desert_spacing.png');
this.load.tilemapTiledJSON('mario', 'assets/tilemaps/maps/super-mario.json');
this.load.image('SuperMarioBros-World1-1', 'assets/tilemaps/tiles/super-mario.png');
this.load.tilemapTiledJSON('features-test', 'assets/tilemaps/maps/features-test.json');
this.load.image('ground_1x1', 'assets/tilemaps/tiles/ground_1x1.png');
this.load.image('dangerous-kiss', 'assets/tilemaps/tiles/dangerous-kiss.png');
this.load.image('walls_1x2', 'assets/tilemaps/tiles/walls_1x2.png');
this.load.image('tiles2', 'assets/tilemaps/tiles/tiles2.png');
}
function create() {
// Visual test to make sure tiles are culled properly when factoring in:
// - Layer position
// - Scroll factor
// - Layer scale
// - Maps that have multiple tilesizes
// Static map with offset, scroll factor & scale
var map = this.make.tilemap({ key: 'desert' });
var tiles = map.addTilesetImage('Desert', 'desert-tiles', 32, 32, 1, 1);
var layer = map.createLayer(0, tiles, -300, -400);
layer.setScrollFactor(0.25);
// Dynamic map with offset, scroll factor & scale
var map = this.make.tilemap({ key: 'mario' });
var tiles = map.addTilesetImage('SuperMarioBros-World1-1');
var layer = map.createLayer(0, tiles, 50, -25);
layer.setScrollFactor(1);
layer.setScale(2, 0.5);
// Map with multiple tileset sizes
var map = this.make.tilemap({ key: 'features-test' });
var ground_1x1 = map.addTilesetImage('ground_1x1');
var tiles2 = map.addTilesetImage('tiles2');
var dangerousTiles = map.addTilesetImage('dangerous-kiss');
var layer = map.createLayer('Tile Layer 1', ground_1x1, 0, 300);
var layer2 = map.createLayer('Offset Tile Layer', tiles2, 0, 300);
var layer3 = map.createLayer('Small Tile Layer', dangerousTiles, 300, 300).setScrollFactor(0.5);
var cursors = this.input.keyboard.createCursorKeys();
var controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
speed: 0.5
};
controls = new Phaser.Cameras.Controls.FixedKeyControl(controlConfig);
}
function update (time, delta)
{
controls.update(delta);
}
Суть примера: зачем нужен визуальный тест
Исходный код представляет собой не игру, а инструмент для проверки (visual test). Его цель — убедиться, что система рендеринга тайловых карт Phaser корректно определяет, какие тайлы находятся в области видимости камеры, а какие можно не отрисовывать (отсечь). Это становится нетривиальной задачей, когда к слоям применяются трансформации.
В коде создаётся несколько карт с разными параметрами, а затем с помощью клавиатуры можно перемещать камеру, наблюдая, не появляются ли графические артефакты (например, внезапное исчезновение или появление тайлов не там, где нужно). Для автоматизации проверки в коде есть простая функция assert, подсчитывающая пройденные тесты, хотя в данном примере она не вызывается — проверка проводится вручную, "на глаз".
Ключевые сложные случаи, которые проверяет этот тест:
* Позиция слоя (offset)
* Коэффициент прокрутки слоя (scrollFactor)
* Масштаб слоя (scale)
* Карты, составленные из тайловых наборов (tilesets) с разным размером тайлов.
Настройка сцены и загрузка ресурсов
Конфигурация игры стандартна, но важно использовать Phaser.WEBGL для доступа к полному функционалу рендерера. Также включен режим pixelArt: true, что влияет на текстурирование.
В функции preload загружаются три разные карты в формате Tiled JSON и соответствующие им изображения тайловых наборов. Обратите внимание на разные пути и имена. Phaser позволяет загружать несколько карт асинхронно перед началом сцены.
var config = {
type: Phaser.WEBGL,
width: 1000,
height: 800,
backgroundColor: '#2d2d88',
parent: 'phaser-example',
pixelArt: true,
scene: {
preload: preload,
create: create,
update: update
}
};
function preload() {
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.tilemapTiledJSON('desert', 'assets/tilemaps/maps/desert.json');
this.load.image('desert-tiles', 'assets/tilemaps/tiles/tmw_desert_spacing.png');
// ... загрузка других карт и изображений
}
Создание слоёв с трансформациями (функция create)
В функции create создаются три тайловые карты, демонстрирующие разные аспекты, которые могут сломать отсечение.
**1. Статичная карта со смещением и фактором прокрутки:**
Создаётся карта 'desert'. Слой смещается на (-300, -400), а scrollFactor устанавливается в 0.25. Это значит, что слой будет прокручиваться в 4 раза медленнее камеры, создавая эффект параллакса. Движок должен корректно вычислять видимую область с учётом этого коэффициента.
var map = this.make.tilemap({ key: 'desert' });
var tiles = map.addTilesetImage('Desert', 'desert-tiles', 32, 32, 1, 1);
var layer = map.createLayer(0, tiles, -300, -400);
layer.setScrollFactor(0.25);
**2. Динамическая карта с масштабированием:**
Создаётся карта 'mario'. Её слой имеет начальное смещение (50, -25), полный scrollFactor (1) и неоднородный масштаб: setScale(2, 0.5) (растяжение по X в 2 раза, сжатие по Y в 2 раза). Неравномерное масштабирование — особенно сложный случай для расчёта границ.
var map = this.make.tilemap({ key: 'mario' });
var tiles = map.addTilesetImage('SuperMarioBros-World1-1');
var layer = map.createLayer(0, tiles, 50, -25);
layer.setScrollFactor(1);
layer.setScale(2, 0.5);
**3. Карта с тайлами разного размера:**
Карта 'features-test' использует несколько тайлсетов. Обратите внимание, что для ground_1x1 не указаны размеры тайла в addTilesetImage, так как они берутся из данных JSON. Слои создаются на разных горизонтальных позициях (X: 0 и 300), а у одного из них снова задан scrollFactor (0.5). Движок должен корректно обрабатывать смешивание тайлов разного размера в одном слое.
var map = this.make.tilemap({ key: 'features-test' });
var ground_1x1 = map.addTilesetImage('ground_1x1');
var tiles2 = map.addTilesetImage('tiles2');
var dangerousTiles = map.addTilesetImage('dangerous-kiss');
var layer = map.createLayer('Tile Layer 1', ground_1x1, 0, 300);
var layer2 = map.createLayer('Offset Tile Layer', tiles2, 0, 300);
var layer3 = map.createLayer('Small Tile Layer', dangerousTiles, 300, 300).setScrollFactor(0.5);
Управление камерой для интерактивной проверки
Чтобы проверить отсечение в динамике, нужно иметь возможность перемещать камеру. В примере для этого используется система управления Phaser.Cameras.Controls.FixedKeyControl.
Сначала создаётся объект курсоров из клавиатуры. Затем конфигурационный объект controlConfig связывает эти клавиши с основной камерой (this.cameras.main) и задаёт скорость перемещения. Созданный экземпляр controls обновляется каждый кадр в функции update, что позволяет плавно двигать камеру стрелками.
var cursors = this.input.keyboard.createCursorKeys();
var controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
speed: 0.5
};
controls = new Phaser.Cameras.Controls.FixedKeyControl(controlConfig);
function update (time, delta) {
controls.update(delta);
}
Теперь, запустив пример и двигая камеру, вы можете визуально оценить, нет ли рывков, мигания тайлов или других аномалий на стыках слоёв с разными параметрами. Корректная работа — это плавное и предсказуемое появление тайлов из-за краёв экрана.
Что попробовать дальше
Данный тестовый пример — отличный шаблон для проверки рендеринга ваших собственных сложных тайловых сцен в Phaser. Он наглядно показывает, какие комбинации трансформаций требуют пристального внимания. Для экспериментов попробуйте: изменить scrollFactor на отрицательные значения, применить вращение к слою (через setRotation), или добавить в одну карту тайлсеты с сильно отличающимися размерами. Также вы можете расширить встроенную функцию assert, добавляя проверки на конкретные координаты тайлов, чтобы автоматизировать тестирование и интегрировать его в pipeline разработки.
