О чем этот пример
Создание динамических, изменяемых игровых миров — ключевой навык для разработчика. В этой статье разберем, как использовать метод `copy()` для тайловых слоев, отрисованных на GPU. Этот подход позволяет быстро копировать участки тайловой карты во время выполнения игры, что идеально подходит для процедурной генерации, разрушаемого окружения или реализации механик вроде "бульдозера". Мы рассмотрим пример, где прямоугольник тайлов копируется по клику мыши, и объясним важные нюансы работы с GPU-рендерингом.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
map;
destinationMarker;
sourceMarker;
controls;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('tiles', 'assets/tilemaps/tiles/tmw_desert_spacing.png');
this.load.tilemapTiledJSON('map', 'assets/tilemaps/maps/desert.json');
}
create ()
{
this.map = this.make.tilemap({ key: 'map' });
const tiles = this.map.addTilesetImage('Desert', 'tiles');
const layer = this.map.createLayer('Ground', tiles, 0, 0, true);
this.layer = layer;
// Graphic to show the "source" of the copy operation
this.sourceMarker = this.add.graphics({ lineStyle: { width: 5, color: 0xffffff, alpha: 1 } });
this.sourceMarker.strokeRect(0, 0, 6 * this.map.tileWidth, 6 * this.map.tileHeight);
// Graphic to show the "destination" of the copy operation
this.destinationMarker = this.add.graphics({ lineStyle: { width: 5, color: 0x000000, alpha: 1 } });
this.destinationMarker.strokeRect(0, 0, 6 * this.map.tileWidth, 6 * this.map.tileHeight);
this.destinationMarker.setPosition(this.map.tileWidth * 5, this.map.tileHeight * 10);
this.cameras.main.setBounds(0, 0, this.map.widthInPixels, this.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, 'Left-click to copy the tiles in the\nwhite rectangle to the black rectangle.', {
fontSize: '18px',
padding: { x: 10, y: 5 },
backgroundColor: '#000000',
fill: '#ffffff'
});
help.setScrollFactor(0);
}
update (time, delta)
{
this.controls.update(delta);
const worldPoint = this.input.activePointer.positionToCamera(this.cameras.main);
const sourceTileX = this.map.worldToTileX(worldPoint.x);
const sourceTileY = this.map.worldToTileY(worldPoint.y);
const destinationTileX = this.map.worldToTileX(this.destinationMarker.x);
const destinationTileY = this.map.worldToTileY(this.destinationMarker.y);
// Snap to tile coordinates, but in world space
this.sourceMarker.x = this.map.tileToWorldX(sourceTileX);
this.sourceMarker.y = this.map.tileToWorldY(sourceTileY);
if (this.input.manager.activePointer.isDown)
{
// Copy a 6 x 6 area at (sourceTileX, sourceTileY) to (destinationTileX, destinationTileY)
this.map.copy(sourceTileX, sourceTileY, 6, 6, destinationTileX, destinationTileY);
// Unlike TilemapLayer, we must manually update the TilemapGPULayer:
this.layer.generateLayerDataTexture();
}
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
pixelArt: true,
scene: Example
};
const game = new Phaser.Game(config);
Основа сцены: загрузка и создание тайловой карты
Пример начинается с загрузки тайлсета и карты в формате Tiled JSON. В методе create() создается тайловая карта и слой. Ключевой момент — использование createLayer с пятым параметром true, который включает GPU-ускорение для этого слоя.
this.map = this.make.tilemap({ key: 'map' });
const tiles = this.map.addTilesetImage('Desert', 'tiles');
const layer = this.map.createLayer('Ground', tiles, 0, 0, true);
this.layer = layer;
Визуальные маркеры и управление камерой
Для наглядности в сцене создаются два графических объекта (Graphics), которые служат маркерами. Белый прямоугольник — это источник для копирования, он следует за курсором мыши. Черный прямоугольник — фиксированное место назначения. Координаты маркеров привязаны к миру, а не к тайлам.
this.sourceMarker = this.add.graphics({ lineStyle: { width: 5, color: 0xffffff, alpha: 1 } });
this.sourceMarker.strokeRect(0, 0, 6 * this.map.tileWidth, 6 * this.map.tileHeight);
Для перемещения по большой карте используется система управления камерой Phaser.Cameras.Controls.FixedKeyControl, привязанная к стрелкам клавиатуры.
Сердце логики: метод update и копирование тайлов
Вся динамика происходит в методе update. Сначала позиция курсора преобразуется в координаты камеры с помощью positionToCamera. Затем эти мировые координаты переводятся в индексы тайлов на карте с помощью worldToTileX и worldToTileY. Так же вычисляются координаты тайла под маркером назначения.
const worldPoint = this.input.activePointer.positionToCamera(this.cameras.main);
const sourceTileX = this.map.worldToTileX(worldPoint.x);
const sourceTileY = this.map.worldToTileY(worldPoint.y);
Позиция белого маркера-источника обновляется каждый кадр, привязываясь к началу текущего тайла с помощью tileToWorldX и tileToWorldY.
Ключевой вызов copy() и важное отличие GPU-слоев
При клике мыши срабатывает основная операция. Метод copy() объекта Tilemap копирует область размером 6x6 тайла из точки-источника в точку-назначения.
this.map.copy(sourceTileX, sourceTileY, 6, 6, destinationTileX, destinationTileY);
Здесь проявляется ключевое отличие GPU-слоев от обычных. После изменения данных тайловой карты, слой, отрисовываемый на GPU, не обновляется автоматически. Необходимо вручную вызвать метод generateLayerDataTexture(), чтобы изменения отобразились на экране.
this.layer.generateLayerDataTexture();
Что попробовать дальше
Метод copy() — это мощный инструмент для манипуляции тайловыми мирами в реальном времени. Главный вывод: при работе с GPU-слоями (createLayer с флагом true) всегда помните о необходимости вызова generateLayerDataTexture() после изменений карты. Для экспериментов попробуйте изменить размер копируемой области, реализовать копирование по перетаскиванию мыши (drag-and-drop) или создать механику "штампов" ландшафта, сохраняя несколько областей-шаблонов для копирования.
