You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Phaser.js中实现Minecraft/Terraria风格方块光照?

在Phaser中实现类似Minecraft/Terraria的方块光照效果

核心思路验证

你提到的思路完全正确:以天空区域(地图顶部可透光方块)为最高光照源(比如设为15级,和Minecraft一致),光照向下方/周围传播时,每穿过一个可透光方块等级降低1,最终每个方块的光照值由传播后的结果决定。

实现步骤详解

1. 为每个Tile维护光照数值

是的,必须为每个Tile单独存储光照值——这是这类方块光照效果的核心,因为每个位置的光照强度是独立的。你有两种实现方式:

  • 方式一:利用Phaser Tile的内置数据
    用tile.setData('lightLevel', value)给每个Tile附加光照等级数据,后续通过tile.getData('lightLevel')读取。
  • 方式二:独立维护光照数组
    创建一个和地图尺寸一致的二维数组lightMap,比如lightMap[x][y]直接存储对应坐标Tile的光照值,这种方式读取效率更高,适合大地图。

2. 初始化光照源

把地图顶部的可透光方块(比如空气Tile)的光照值设为最高等级(比如15),固体方块(比如石头、泥土)直接设为0(因为不透光,无法传播光照)。

示例代码片段:

// 假设map是你的Phaser Tilemap实例
const layer = map.getLayer('Ground').tilemapLayer;
const lightMap = Array(map.width).fill().map(() => Array(map.height).fill(0));
const queue = [];

// 初始化顶部行的光照
for (let x = 0; x < map.width; x++) {
  const tile = map.getTileAt(x, 0, true);
  if (tile && tile.properties.transparent) { // 假设你给可透光Tile加了transparent属性
    lightMap[x][0] = 15;
    queue.push({x, y: 0});
  }
}

3. 光照传播计算(BFS算法)

用广度优先搜索来实现光照的逐层传播,确保每个方块的光照值是周围能传播过来的最高值:

const directions = [[-1,0], [1,0], [0,-1], [0,1]]; // 上下左右四个方向

while (queue.length > 0) {
  const {x, y} = queue.shift();
  const currentLight = lightMap[x][y];
  
  // 光照等级为0时停止传播
  if (currentLight <= 1) continue;
  
  // 遍历四个方向
  for (const [dx, dy] of directions) {
    const nx = x + dx;
    const ny = y + dy;
    
    // 检查坐标是否在地图范围内
    if (nx < 0 || nx >= map.width || ny < 0 || ny >= map.height) continue;
    
    const tile = map.getTileAt(nx, ny, true);
    // 只有可透光Tile才能接收光照
    if (!tile || !tile.properties.transparent) continue;
    
    // 计算新的光照值:当前光照-1
    const newLight = currentLight - 1;
    // 如果新光照值比现有值高,才更新并加入队列
    if (newLight > lightMap[nx][ny]) {
      lightMap[nx][ny] = newLight;
      queue.push({x: nx, y: ny});
    }
  }
}

4. 渲染时应用光照效果

有两种常用的渲染方式,根据你的需求选择:

  • 方式一:半透明遮罩(简单高效)
    在Tile渲染后,给每个Tile叠加一个黑色遮罩,光照值越低,遮罩透明度越高:

    // 在update或者render回调中更新Tile遮罩
    layer.forEachTile(tile => {
      const lightLevel = lightMap[tile.x][tile.y];
      // 计算透明度:光照15对应0(完全亮),0对应0.8(几乎全暗)
      const maskAlpha = 1 - (lightLevel / 15);
      // 可通过创建独立光照层绘制黑色矩形,避免影响原Tile透明度
      const mask = this.add.rectangle(tile.pixelX, tile.pixelY, tile.width, tile.height, 0x000000, maskAlpha);
      mask.setOrigin(0);
      mask.setDepth(layer.depth + 1);
    });
    
  • 方式二:多亮度纹理(效果更细腻)
    给每个方块预先生成16种亮度等级的纹理(从最亮到最暗),然后根据光照值切换对应的纹理:

    // 假设你已经加载了不同亮度的纹理,比如'dirt_0'到'dirt_15'
    layer.forEachTile(tile => {
      const lightLevel = lightMap[tile.x][tile.y];
      const textureKey = `${tile.texture.key}_${lightLevel}`;
      tile.setTexture(textureKey);
    });
    

额外优化建议

  • 局部更新:如果地图有方块被破坏/放置,只重新计算该区域周围3-5格的光照,不用全图重新计算,提升性能。
  • 自定义透光率:比如玻璃完全透光(光照不减)、树叶衰减2级光照,只需修改传播时的newLight计算逻辑即可。
  • 动态光源:加入火把等动态光源时,只需把火把所在Tile的光照值设为对应等级(比如14),加入BFS队列重新传播即可。

内容的提问来源于stack exchange,提问作者Vatsa Pandey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 13:31:00