游戏的平铺填充算法

时间:2014-08-01 22:44:29

标签: javascript algorithm tile flood-fill

背景

我正在使用Javascript中基于图块的游戏,其中角色在地图周围自由移动(无对角线 - 左/右/上/下)并在他移动时填充图块地图。有三种图块类型 - 您填充的图块(蓝色),当前路径(红色)和空图块(黑色)。也有在地图上移动的敌人(星星),但仅限于空白区域。目标是尽可能多地填充地图。

地图的大小约为40x40平铺。在地图的整个外部周围有一个瓷砖厚的边框,已经填充了#34; (蓝色)。

我已经确定泛洪填充算法可以在需要时填充区块区域。但是,我的问题如下:

问题陈述: 如果没有敌人,我想只填充地图的一部分。

我的问题: 我可以运行泛洪填充算法并在它到达敌人占用的区块时停止它 - 但是,这是最有效的方法(对于实时游戏)吗? 如果是,如何从系统的方式确定从何处开始算法,因为有多个区域需要检查,并且角色不必在完美的直线上移动(可以zig) -zag up / down / right / left,但不能对角移动)。

图片示例1(图片说明更好):

注意:红色区域在到达另一个填充区域时变为蓝色(已填充)。在下面的示例中,包含区域中没有敌人,因此区域已填满。

FillExample1:inprogress FillExample1:filled

图片示例2:

在第二个例子中,在包含区域内(和外部区域 - 未显示)中有一个敌​​人,所以只有线条被填满。

enter image description here enter image description here

摘要: 进行此类填充的最佳方法是什么?洪水填充是确定是否填充的最佳选择 - 40x40是一个非常大的计算。如果是,我如何确定我从哪个瓷砖开始?

3 个答案:

答案 0 :(得分:5)

让我建议一种不同的方式来看待你的问题。

通过对游戏的描述,似乎用户的主要,也许只有“动词”(在游戏设计术语中)是绘制一条线,将场的开放区域分为两个部分。如果这两个部分中的任何一个没有敌人,那么该部分就会被填满;如果两个部分都没有敌人,则该线仍然存在,但两个部分都保持打开状态。没有其他条件来确定某个部分是否被填满,对吧?

因此,我认为,解决这个问题最有效的方法就是画一条连续线,它可以制造角落但只能在水平或垂直方向上移动,从你的一个敌人到另一个敌人。我们称这条线为“探测线”。从这里开始,我们使用Derek建议的“Ray cast算法”的方法:我们看看“探测线”越过“边界线”的次数,如果交叉次数是奇数,则意味着你在这条线的每一边至少有一个敌人,并且没有填充物。

但是请注意,重合交叉这两行之间存在差异。画出从坐标(0,10)(39,10)的探测线,以及从(5,0)(5,10)的边界线,然后右转到(13,10) 。如果它从那里向(13,39)下降,则两条线正在交叉;如果它向上移向(13,0),则它们不是。

经过深思熟虑,我强烈建议您存储“边界线”,并根据线段构建“探测线” - 而不是试图确定从哪些细胞填充了哪些线段创建了它们。这将使它变得比它更难。

最后,要注意一个奇怪的游戏设计注意事项:除非你限制用户的控制权以使无法将边界线带回到自身的一个单元格内,然后绘制一条边框线用户最终可能会将该字段划分为两个以上部分 - 可能会有边界线创建的部分向后循环。如果允许,可能会非常复杂地计算填充位置。检查下面我通过Derek的小提琴制作的图表(谢谢你,Derek!):

a border line which can approach within one cell of itself, and thus creates three sections in one move

如您所见,一条边界线实际上创建了三个部分:一条在线的上侧,一条在线下方,另一条由线本身组成。我仍然在考虑这会如何影响算法,因为这是可能的。

编辑:考虑到a)时间考虑上面创建的多个部分逐个循环,以及b)Derek提出的简化模拟资源,我想我可以概述了您可能获得的最简单,最有效的算法。

我有一个子问题,我将留给你,这就是确定玩家的行动引出新线后你的新部分是什么。我把它留给你,因为在解决你的原始问题之前必须要解决这个问题(如何判断这些部分中是否有敌人)。

这里以伪代码形式呈现的解决方案假设您将每个部分的边界存储为坐标之间的线段。

Create a list of the sections.
Create a list of the enemies.
Continue as long as neither list is empty:
  For each enemy in the enemy list:
  Designate "Point A" as the coordinates of the enemy, PLUS 0.5 to both x and y.
    For each section in the section list:
      Designate "Point B" as the upper-left-most coordinate, PLUS 0.5 to both x and y.
      Count how many of the section border segments cross a line between A and B.
      If the answer is even:  
        remove this section from the section list
        skip forward to the next enemy
If any sections remain in the list, they are free of enemies.  Fill them in.

将“0.5”添加到“探测线”的坐标是得益于Derek的SoS资源;它们消除了线条重合而不是简单地交叉或不交叉的困难情况。

答案 1 :(得分:4)

如果你的形状边界的点与敌人位于同一个y上,那么你可以简单地计算边界的数量,从左边或右边开始计算敌人。如果它奇怪,那么它就在里面。如果它甚至那么它就在外面。

由于您使用的是网格系统,因此应该 easy 来实现(并且非常快)。该算法称为Ray casting algorithm

以下是我创建的一个简单示例:http://jsfiddle.net/DerekL/8QBz6/(无法解决堕落案例)

function testInside(){
    var passedBorder = 0,
        passingBorder = false;
    for(var x = 0; x <= enemy[0]; x++){
        if(board[x][enemy[1]] === 1) passingBorder = true;
        else if(board[x][enemy[1]] === 0 && passingBorder){
            passingBorder = false;
            passedBorder++;
        }
    }
    return !!(passedBorder%2);
}

例如,您已确定此形状:
enter image description here

除去


猜猜what I found,(稍加修改)

//simple enough, it only needs the x,y of your testing point and the wall.
//no direction or anything else
function testInside3() {
    var i, j, c = 0;
    for (i = 0, j = wallList.length-1; i < wallList.length; j = i++) {
        if ( ((wallList[i][1]>enemy[1]) ^ (wallList[j][1]>enemy[1])) &&
            (enemy[0] < (wallList[j][0]-wallList[i][0]) * (enemy[1]-wallList[i][1]) / (wallList[j][1]-wallList[i][1]) + wallList[i][0]) )
        c = !c;
    }
    return !!c;
}

http://jsfiddle.net/DerekL/NvLcK/

这是使用我提到的相同的光线投射算法,但这次是&#34; ray&#34;现在是数学使用以下x的不等式和y的条件:

     (X2 - X1)(Py - Y1)
Px < ────────────────── + X1
          Y2 - Y1

是通过组合这两个来得出的:

Ray:
x(t) = Px + t, y(t) = Py, where t > 0 (the ray goes to the right)

Edge:
x(u) = (X2 - X1)u + X1, y(u) = (Y2 - Y1)u + Y1, where 0 <= u <= 1

y的条件:

(Y1 > Py) ⊕ (Y2 > Py)

相当于:

(Y1 ≥ Py > Y2) ∨ (Y2 ≥ Py > Y1)

和yadi yadi yada其他一些有趣的技术内容。

似乎这是许多本机库中的默认算法。用于处理退化情况的方法称为简单模拟,在this paper(第5.1节)中有描述。

然而,这是通过算法测试每个坐标产生的结果:
enter image description here

答案 2 :(得分:2)

如果可以很容易地确定可能填充的区域的边界,您可以使用以下方法:

  1. 按顺时针方向指定每条边。也就是说,为每个边缘构建一个矢量,从边角开始,其方向使得这些矢量描述了该区域周围的顺时针路径。
  2. 对于每个敌人:
    • 构造一个从敌人开始并在最近边缘结束的向量。我们称之为enemy_vector。
    • 计算enemy_vector与最近边对应的向量的叉积。交叉产品的标志将告诉你敌人是否在该区域内:如果它是正面的,那么敌人就在它之外,如果它是负面的,那就不是!
  3. 示例:

    假设我们有以下区域和敌人来评估内部情况。A region and an enemy

    我们可以将区域编码为一系列向量,使其呈顺时针方向,如下所示: A region as a series of vectors. Now the path has an orientation.

    那么我们如何利用它来确定敌人居住区域的一面呢?我们从它绘制一个矢量(我用红色)到最近的边缘(我用绿色)...

    Side determination process

    ...并取红色矢量和绿色矢量的叉积。右手规则的应用告诉我们(红色)x(绿色)&gt; 0,所以敌人必须在该地区之外!