Phaser 瓦片地图 #

瓦片地图概述 #

瓦片地图(Tilemap)是一种高效的地图表示方式,通过重复使用小图像块(瓦片)来构建大型游戏世界。

text
┌─────────────────────────────────────────────────────────────┐
│                    瓦片地图结构                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Tilemap                                                    │
│  ├── Tileset(瓦片集)                                     │
│  │   ├── 瓦片图像                                          │
│  │   └── 瓦片属性                                          │
│  ├── Layer(图层)                                         │
│  │   ├── Tile Layer(瓦片图层)                            │
│  │   └── Object Layer(对象图层)                          │
│  └── Properties(属性)                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Tiled 地图编辑器 #

安装 Tiled #

Tiled 是一个免费、开源的地图编辑器,支持多种格式导出。

创建地图 #

  1. 新建地图:File → New → New Map
  2. 设置地图属性:
    • Orientation:Orthogonal(正交)/ Isometric(等角)
    • Tile size:瓦片尺寸(如 32x32)
    • Map size:地图大小(行列数)

创建瓦片集 #

  1. 新建瓦片集:File → New → New Tileset
  2. 导入瓦片图像
  3. 设置瓦片属性(碰撞、动画等)

图层类型 #

text
┌─────────────────────────────────────────────────────────────┐
│                    图层类型                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Tile Layer(瓦片图层)                                     │
│  ├── 绘制瓦片                                              │
│  ├── 地面、墙壁、装饰                                      │
│  └── 支持多图层叠加                                        │
│                                                             │
│  Object Layer(对象图层)                                   │
│  ├── 放置对象                                              │
│  ├── 敌人生成点、触发区域                                  │
│  └── 自定义属性                                            │
│                                                             │
│  Image Layer(图像图层)                                    │
│  ├── 背景图像                                              │
│  └── 视差滚动                                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

加载地图 #

导出格式 #

Tiled 支持多种导出格式,Phaser 推荐使用 JSON 格式:

  1. File → Export As
  2. 选择 JSON 格式
  3. 保存为 .json 文件

加载资源 #

javascript
preload() {
  this.load.tilemapTiledJSON('map', 'assets/map.json');
  
  this.load.image('tiles', 'assets/tiles.png');
  
  this.load.image('background', 'assets/background.png');
  
  this.load.spritesheet('player', 'assets/player.png', {
    frameWidth: 32,
    frameHeight: 32
  });
}

创建地图 #

javascript
create() {
  const map = this.make.tilemap({ key: 'map' });
  
  const tileset = map.addTilesetImage('tiles', 'tiles');
  
  const groundLayer = map.createLayer('Ground', tileset);
  
  const wallsLayer = map.createLayer('Walls', tileset);
  
  const decorationLayer = map.createLayer('Decoration', tileset);
}

多瓦片集 #

javascript
create() {
  const map = this.make.tilemap({ key: 'map' });
  
  const terrainTiles = map.addTilesetImage('terrain', 'terrain');
  const buildingTiles = map.addTilesetImage('buildings', 'buildings');
  const propsTiles = map.addTilesetImage('props', 'props');
  
  const groundLayer = map.createLayer('Ground', [terrainTiles, buildingTiles]);
  const propsLayer = map.createLayer('Props', propsTiles);
}

图层操作 #

获取图层 #

javascript
const map = this.make.tilemap({ key: 'map' });

const layer = map.getLayer('Ground');

const layer = map.getLayerAt(0);

const layers = map.layers;

const layerNames = map.getLayerNames();

创建图层 #

javascript
const map = this.make.tilemap({ key: 'map' });
const tileset = map.addTilesetImage('tiles', 'tiles');

const layer = map.createLayer('Ground', tileset);

const layer = map.createLayer('Ground', tileset, x, y);

const layer = map.createLayer(0, tileset);

const layer = map.createBlankLayer('NewLayer', tileset);

图层属性 #

javascript
layer.alpha = 0.5;
layer.setVisible(false);
layer.setDepth(10);
layer.setScale(2, 2);
layer.setPosition(100, 100);

layer.setScrollFactor(0.5, 0.5);

layer.setCullPadding(2, 2);

layer.skipCull = true;

图层碰撞 #

javascript
const map = this.make.tilemap({ key: 'map' });
const tileset = map.addTilesetImage('tiles', 'tiles');
const wallsLayer = map.createLayer('Walls', tileset);

wallsLayer.setCollisionByProperty({ collides: true });

wallsLayer.setCollisionBetween(1, 100);

wallsLayer.setCollision([1, 2, 3, 4, 5]);

wallsLayer.setCollisionByExclusion([-1, 0]);

this.physics.add.collider(this.player, wallsLayer);

瓦片操作 #

javascript
const tile = layer.getTileAt(5, 5);

const tile = layer.getTileAtWorldXY(200, 200);

const tiles = layer.getTilesWithin(0, 0, 10, 10);

layer.putTileAt(10, 5, 5);

layer.putTileAtWorldXY(10, 200, 200);

layer.removeTileAt(5, 5);

layer.fill(10, 0, 0, 10, 10);

layer.randomize(0, 0, 10, 10, [1, 2, 3, 4, 5]);

layer.weightedRandomize(0, 0, 10, 10, [
  { index: 1, weight: 4 },
  { index: 2, weight: 1 }
]);

layer.replaceByIndex(1, 10);

瓦片属性 #

javascript
const tile = layer.getTileAt(5, 5);

console.log(tile.index);
console.log(tile.x, tile.y);
console.log(tile.pixelX, tile.pixelY);
console.log(tile.properties);
console.log(tile.rotation);
console.log(tile.flipX, tile.flipY);
console.log(tile.alpha);
console.log(tile.tint);

if (tile.properties.collides) {
  console.log('This tile collides');
}

对象层 #

解析对象层 #

javascript
const map = this.make.tilemap({ key: 'map' });

const spawnPoint = map.findObject('Objects', obj => obj.name === 'Spawn Point');
this.player = this.physics.add.sprite(spawnPoint.x, spawnPoint.y, 'player');

const objects = map.filterObjects('Objects', obj => obj.type === 'enemy');
objects.forEach(obj => {
  this.enemies.create(obj.x, obj.y, 'enemy');
});

const objectLayer = map.getObjectLayer('Objects');
objectLayer.objects.forEach(obj => {
  console.log(obj.name, obj.type, obj.x, obj.y);
});

对象属性 #

javascript
const map = this.make.tilemap({ key: 'map' });
const objects = map.getObjectLayer('Objects').objects;

objects.forEach(obj => {
  console.log('Name:', obj.name);
  console.log('Type:', obj.type);
  console.log('Position:', obj.x, obj.y);
  console.log('Size:', obj.width, obj.height);
  console.log('Rotation:', obj.rotation);
  console.log('Visible:', obj.visible);
  console.log('Properties:', obj.properties);
  
  if (obj.properties) {
    const health = obj.properties.find(p => p.name === 'health');
    if (health) {
      console.log('Health:', health.value);
    }
  }
});

创建对象 #

javascript
const map = this.make.tilemap({ key: 'map' });

const enemies = map.filterObjects('Objects', obj => obj.type === 'enemy');
enemies.forEach(obj => {
  const enemy = this.physics.add.sprite(obj.x, obj.y, 'enemy');
  
  if (obj.properties) {
    const speed = obj.properties.find(p => p.name === 'speed');
    if (speed) {
      enemy.speed = speed.value;
    }
  }
  
  this.enemyGroup.add(enemy);
});

const triggers = map.filterObjects('Objects', obj => obj.type === 'trigger');
triggers.forEach(obj => {
  const zone = this.add.zone(obj.x + obj.width / 2, obj.y + obj.height / 2, obj.width, obj.height);
  this.physics.world.enable(zone);
  zone.body.setAllowGravity(false);
  zone.body.moves = false;
  
  this.physics.add.overlap(this.player, zone, () => {
    console.log('Triggered:', obj.name);
  });
});

相机跟随 #

世界边界 #

javascript
const map = this.make.tilemap({ key: 'map' });

this.physics.world.setBounds(0, 0, map.widthInPixels, map.heightInPixels);

this.player.setCollideWorldBounds(true);

this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);

this.cameras.main.startFollow(this.player, true, 0.08, 0.08);

this.cameras.main.startFollow(this.player, true, 0.1, 0.1, 0, 0);

视差滚动 #

javascript
const map = this.make.tilemap({ key: 'map' });

const backgroundLayer = map.createLayer('Background', tileset);
backgroundLayer.setScrollFactor(0.1);

const midgroundLayer = map.createLayer('Midground', tileset);
midgroundLayer.setScrollFactor(0.5);

const foregroundLayer = map.createLayer('Foreground', tileset);
foregroundLayer.setScrollFactor(1.2);

动态地图 #

动态瓦片 #

javascript
update() {
  const tile = this.groundLayer.getTileAtWorldXY(this.player.x, this.player.y + 32);
  
  if (tile && tile.index === 10) {
    this.groundLayer.putTileAtWorldXY(11, this.player.x, this.player.y + 32);
  }
}

breakTile(x, y) {
  const tile = this.groundLayer.getTileAtWorldXY(x, y);
  if (tile && tile.properties.breakable) {
    this.groundLayer.removeTileAtWorldXY(x, y);
    
    this.createDebris(x, y);
    this.sound.play('break');
  }
}

动态碰撞 #

javascript
updateMap() {
  this.collisionLayer.forEachTile(tile => {
    if (tile.index === 10) {
      tile.setCollision(true);
    }
  });
}

地图转换 #

世界坐标与瓦片坐标 #

javascript
const map = this.make.tilemap({ key: 'map' });

const tileX = map.worldToTileX(worldX);
const tileY = map.worldToTileY(worldY);

const worldX = map.tileToWorldX(tileX);
const worldY = map.tileToWorldY(tileY);

const tile = map.getTileAt(tileX, tileY, true, 'Ground');

路径查找 #

javascript
findPath(startX, startY, endX, endY) {
  const map = this.make.tilemap({ key: 'map' });
  
  const startTileX = map.worldToTileX(startX);
  const startTileY = map.worldToTileY(startY);
  const endTileX = map.worldToTileX(endX);
  const endTileY = map.worldToTileY(endY);
  
  const path = [];
  
  return path;
}

完整示例 #

平台游戏地图 #

javascript
class GameScene extends Phaser.Scene {
  constructor() {
    super({ key: 'GameScene' });
  }
  
  preload() {
    this.load.tilemapTiledJSON('map', 'assets/map.json');
    this.load.image('tiles', 'assets/tiles.png');
    this.load.spritesheet('player', 'assets/player.png', {
      frameWidth: 32,
      frameHeight: 48
    });
  }
  
  create() {
    const map = this.make.tilemap({ key: 'map' });
    const tileset = map.addTilesetImage('tiles', 'tiles');
    
    const backgroundLayer = map.createLayer('Background', tileset);
    backgroundLayer.setScrollFactor(0.5);
    
    const groundLayer = map.createLayer('Ground', tileset);
    groundLayer.setCollisionByProperty({ collides: true });
    
    const decorationLayer = map.createLayer('Decoration', tileset);
    
    const spawnPoint = map.findObject('Objects', obj => obj.name === 'Spawn Point');
    this.player = this.physics.add.sprite(spawnPoint.x, spawnPoint.y, 'player');
    this.player.setCollideWorldBounds(true);
    
    this.physics.add.collider(this.player, groundLayer);
    
    this.physics.world.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
    this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
    this.cameras.main.startFollow(this.player, true, 0.08, 0.08);
    
    this.coins = this.physics.add.group();
    const coinObjects = map.filterObjects('Objects', obj => obj.type === 'coin');
    coinObjects.forEach(obj => {
      const coin = this.coins.create(obj.x + 16, obj.y + 16, 'coin');
      coin.body.setAllowGravity(false);
    });
    
    this.physics.add.overlap(this.player, this.coins, this.collectCoin, null, this);
    
    this.cursors = this.input.keyboard.createCursorKeys();
  }
  
  update() {
    if (this.cursors.left.isDown) {
      this.player.setVelocityX(-160);
    } else if (this.cursors.right.isDown) {
      this.player.setVelocityX(160);
    } else {
      this.player.setVelocityX(0);
    }
    
    if (this.cursors.up.isDown && this.player.body.blocked.down) {
      this.player.setVelocityY(-330);
    }
  }
  
  collectCoin(player, coin) {
    coin.destroy();
    this.score += 10;
  }
}

下一步 #

现在你已经掌握了瓦片地图,接下来学习 音频系统,了解如何为游戏添加音效和背景音乐!

最后更新:2026-03-29