О чем этот пример
Создание больших игровых миров вручную — трудоемкая задача. Tilemap, или тайловая карта, решает эту проблему, позволяя собирать уровни из готовых плиток (тайлов) как мозаику. В этой статье мы разберем, как загрузить карту, созданную в популярном редакторе Tiled, в Phaser 3, настроить камеру для плавной прокрутки и добавить управление с клавиатуры. Этот подход идеально подходит для платформеров, RPG, стратегий и других жанров, где требуется детализированный и большой уровень.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
controls;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('tiles', 'assets/tilemaps/tiles/cybernoid.png');
this.load.tilemapTiledJSON('map', 'assets/tilemaps/maps/cybernoid.json');
}
create ()
{
const map = this.make.tilemap({ key: 'map' });
// The first parameter is the name of the tileset in Tiled and the second parameter is the key
// of the tileset image used when loading the file in preload.
const tiles = map.addTilesetImage('cybernoid', 'tiles');
// You can load a layer from the map using the layer name from Tiled, or by using the layer
// index (0 in this case).
const layer = map.createLayer(0, tiles, 0, 0);
this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
const cursors = this.input.keyboard.createCursorKeys();
const controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
speed: 0.5
};
this.controls = new Phaser.Cameras.Controls.FixedKeyControl(controlConfig);
const help = this.add.text(16, 16, 'Arrow keys to scroll', {
fontSize: '18px',
padding: { x: 10, y: 5 },
backgroundColor: '#000000',
fill: '#ffffff'
});
help.setScrollFactor(0);
}
update (time, delta)
{
this.controls.update(delta);
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
pixelArt: true,
scene: Example
};
const game = new Phaser.Game(config);
Подготовка ресурсов: загрузка карты и тайлсета
Работа с Tilemap начинается с загрузки двух ключевых ресурсов: изображения с набором тайлов (тайлсет) и файла карты, созданного в Tiled.
В методе preload мы используем this.load.image для загрузки спрайта, содержащего все наши тайлы. Метод this.load.tilemapTiledJSON загружает непосредственно файл карты в формате JSON, который экспортируется из Tiled. Важно, чтобы ключи, указанные при загрузке ('tiles' и 'map'), совпадали с теми, что используются позже в коде.
this.load.image('tiles', 'assets/tilemaps/tiles/cybernoid.png');
this.load.tilemapTiledJSON('map', 'assets/tilemaps/maps/cybernoid.json');
Создание и отрисовка карты в сцене
В методе create мы создаем объект тайловой карты на основе загруженных данных. Затем связываем загруженное изображение тайлов с конкретным тайлсетом, определенным внутри файла Tiled. После этого создаем слой для отображения.
Метод this.make.tilemap создает новый экземпляр карты. map.addTilesetImage связывает ключ загруженного изображения ('tiles') с именем тайлсета, заданным в редакторе Tiled ('cybernoid'). map.createLayer создает и отображает слой карты. В данном примере используется индекс слоя (0), но можно указать и его имя, заданное в Tiled.
const map = this.make.tilemap({ key: 'map' });
const tiles = map.addTilesetImage('cybernoid', 'tiles');
const layer = map.createLayer(0, tiles, 0, 0);
Настройка камеры и границ мира
Чтобы камера могла плавно перемещаться по уровню, который больше игрового окна, необходимо задать ей границы. Эти границы определяют пределы, за которые камера не может выйти.
Свойства map.widthInPixels и map.heightInPixels автоматически рассчитываются Phaser на основе размера карты и тайлов. Метод this.cameras.main.setBounds устанавливает границы для основной камеры, используя эти значения. Теперь камера знает размеры всего игрового мира.
this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
Добавление управления камерой с клавиатуры
Для перемещения камеры по миру мы используем готовый контроллер Phaser.Cameras.Controls.FixedKeyControl. Сначала создаем объект с конфигурацией, который связывает клавиши-стрелки с направлениями движения камеры и задает скорость перемещения.
this.input.keyboard.createCursorKeys() создает объект для отслеживания состояния стрелок. В конфиге controlConfig мы указываем, какую камеру управляем (this.cameras.main) и какие клавиши отвечают за движение. Скорость speed: 0.5 определяет, насколько быстро камера будет реагировать на нажатия. Контроллер создается один раз и сохраняется в свойстве сцены для использования в update.
const cursors = this.input.keyboard.createCursorKeys();
const controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
speed: 0.5
};
this.controls = new Phaser.Cameras.Controls.FixedKeyControl(controlConfig);
Обновление состояния и финальные штрихи
Чтобы контроллер камеры работал, его необходимо обновлять каждый кадр. Это делается в методе update, куда передается дельта-время (время, прошедное с последнего кадра).
Также в примере добавляется текстовый элемент с подсказкой. Важный момент: help.setScrollFactor(0) фиксирует этот текст относительно экрана, а не камеры. Это означает, что текст не будет двигаться при прокрутке уровня, оставаясь всегда в одном месте экрана.
update (time, delta)
{
this.controls.update(delta);
}
const help = this.add.text(16, 16, 'Arrow keys to scroll', {
fontSize: '18px',
padding: { x: 10, y: 5 },
backgroundColor: '#000000',
fill: '#ffffff'
});
help.setScrollFactor(0);
Что попробовать дальше
Теперь у вас есть основа для большого игрового уровня. Механика прокрутки камеры — фундамент для многих жанров. Для экспериментов попробуйте
- Добавить игрового персонажа и заставить камеру следовать за ним, используя
this.cameras.main.startFollow(sprite) - Создать в Tiled несколько слоев (например, задний фон, основной слой, слой поверхностей) и отобразить их в коде
- Настроить столкновения, добавив слой с коллизиями и используя
layer.setCollisionByProperty({ collides: true })
