问题描述
我正在尝试为我的 2D 自上而下游戏实现视线可见性/战争迷雾,并发现 this article 具有简单而有效的算法,该算法涉及在边缘发射光线矩形以计算要增亮的三角形列表。
但是,我的游戏使用图块,因此在每一帧围绕玩家周围的 150x150 (22,500) 图块运行它会非常低效。相反,最好将瓷砖地图转换为矩形列表,然后针对它运行视线算法。例如,如果这是我的瓷砖地图(其中 1 是实心瓷砖,0 是空闲瓷砖):
1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
0 0 1 1 0 0 1
0 0 1 1 1 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1
然后您可以将其转换为矩形列表,如下所示:
1 1 1 1 1 1 1
5 0 0 0 0 0 2
5 0 0 0 0 0 2
0 0 6 6 0 0 2
0 0 6 6 7 0 2
4 0 0 0 0 0 2
3 3 3 3 3 3 3
在这个结果中,每一个 1
指的是第一个矩形,每一个 2
指的是第二个矩形,等等。这不是唯一可能的结果,而是许多可能的结果之一。基本上,该算法无需检查 7 x 7 = 49
块,而只需检查 7
矩形,这大大加快了视野计算。
矩形将具有类似 x,y,width,height
的属性。在这种情况下,第一个矩形将类似于 x: 0,y: 0,w: 7,h: 1
。第二个矩形将是 x: 6,y: 1,w: 1,h: 5
等。
是否有一种算法可以从 2D 瓦片矩阵生成矩形列表?
解决方法
有一种简单的方法,例如,在宽度优先的填充过程中从左上角开始,这会给您提供与您正在寻找的相似的结果。
宽度优先算法可能如下所示:
结果:
1 1 1 1 1 1 1
2 0 0 0 0 0 3
2 0 0 0 0 0 3
0 0 4 4 0 0 3
0 0 4 4 5 0 3
6 0 0 0 0 0 3
6 7 7 7 7 7 7
- 创建空数组
tileCache
用于存储已处理的图块,用 0 预填充 - 创建一个数组
tileRects
用于存储矩形信息 - 按行和列遍历图块
- 检查 tile 是否等于 1,如果不是,则处理下一个 tile
- 检查坐标处的tileCache是否不等于0; if != 0 处理下一个图块
- 如果是新的图块,则在右侧找到
1
的图块并获取矩形的width
- 使用矩形的宽度(和 x,y)找出我们可以使矩形有多高以获得
height
- 然后使用 x,y,width 和 height 创建矩形对象
- 用数据填充缓存
请注意,此算法有一个怪癖,这应该不是问题,如果有问题可以修复。它将允许矩形重叠。
即
let tiles = [
[1,1,1],[0,0],[1,];
将生成 3 个矩形
1 1 1 1
0 2 2 0
3 3 3 3
0 2 2 0
let tiles = [
[1,];
const tileCache = tiles.map(() => Array(tiles[0].length).fill(0));
const tileRects = [];
function exploreRight(y,x) {
let w = 1;
while (tiles[y][x + w] === 1) {
w++;
}
return w;
}
function exploreDown(y,x,w) {
let pass = true;
let h = 1;
while (pass) {
for (let $x = x; $x < x + w; $x++) {
if (!tiles[y + h] || tiles[y + h][$x] !== 1) {
pass = false;
continue;
}
}
pass && h++;
}
return h;
}
function fillCache(y,h,w,n) {
for (let $y = y; $y < y + h; $y++) {
for (let $x = x; $x < x + w; $x++) {
tileCache[$y][$x] = n;
}
}
}
let n = 1;
for (let y = 0; y < tiles.length; y++) {
for (let x = 0; x < tiles[y].length; x++) {
const tile = tiles[y][x];
const cache = tileCache[y][x];
if (tile === 0) {
continue;
}
if (cache > 0) {
continue;
}
const w = exploreRight(y,x);
const h = exploreDown(y,w);
tileRects.push({ y,h });
fillCache(y,n);
if (w > 1) {
x += w - 1;
}
n++;
}
}
console.log(tileCache.map((r) => r.join(" ")).join("\n"));
console.log(tileRects);
更新
如果重叠的方块是一个问题,唯一需要改变的是 exploreDown
方法也应该检查缓存,而不仅仅是图块。假设是更少的矩形意味着更少的计算,但在某些情况下重叠可能是一个问题。
function exploreDown(y,w) {
let pass = true;
let h = 1;
while (pass) {
for (let $x = x; $x < x + w; $x++) {
if (!tiles[y + h] || tiles[y + h][$x] !== 1) {
pass = false;
continue;
}
/// change ?
if (!tileCache[y + h] || tileCache[y + h][$x] !== 1) {
pass = false;
continue;
}
// change ?
}
pass && h++;
}
return h;
}