我正在编写一个基于图块的小游戏,我想支持光源。但是我的算法太弱了,所以我来找你帮忙。

情况是这样的:有一个基于图块的地图(作为 2D 数组保存),包含单个光源和周围的几个项目。我想计算哪些瓷砖被光源照亮,哪些处于阴影中。

大概的外观的视觉辅助。L 是光源,X 是阻挡光线的项目,0 是照亮的瓷砖,-s 是阴影中的瓷砖。

0 0 0 0 0 0 - - 0
0 0 0 0 0 0 - 0 0
0 0 0 0 0 X 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 L 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 X X X X 0 0
0 0 0 - - - - - 0
0 0 - - - - - - -

当然,分数系统会更好,其中图块可以由于部分被遮挡而处于半阴影中。该算法不必是完美的——只是没有明显错误并且相当快。

(当然,会有多个光源,但这只是一个循环。)

有接受者吗?

有帮助吗?

解决方案

roguelike开发社区对视线,视野算法有一点痴迷。

这是关于该主题的roguelike wiki文章的链接: http://roguebasin.roguelikedevelopment.org/index.php?title=Field_of_Vision

对于我的roguelike游戏,我实施了阴影投射算法( http:// roguebasin。用Python编写的roguelikedevelopment.org/index.php?title=Shadow_casting )。放在一起有点复杂,但运行合理(即使在纯Python中)并产生了很好的结果。

“许可视野”似乎也越来越受欢迎: http://roguebasin.roguelikedevelopment.org/index.php?title=Permissive_Field_of_View

其他提示

您可以通过计算遮挡等来解决各种复杂问题,或者您可以选择简单的强力方法:对于每个单元格,使用线条绘制算法,例如 Bresenham Line算法检查当前单元和光源之间的每个单元。如果任何已填充的单元格或(如果您只有一个光源)已经过测试并发现处于阴影中的单元格,则您的单元格处于阴影中。如果您遇到已知点亮的电池,您的电池也会被点亮。对此进行简单的优化是将沿线遇到的任何单元格的状态设置为最终结果。

这或多或少是我在 2004 IOCCC获奖作品中使用的内容。但显然,这并不是很好的示例代码。 ;)

编辑:正如洛伦指出的那样,通过这些优化,您只需要沿地图边缘选择要跟踪的像素。

这里提出的算法似乎比我认为需要做更多的计算。我没有测试过这个,但我认为它会起作用:

最初,将所有像素标记为亮起。

对于地图边缘的每个像素:正如Arachnid建议的那样,使用Bresenham来跟踪从像素到光线的一条线。如果该线撞击障碍物,则将所有像素从边缘标记为刚好超出障碍物的阴影。

又快又脏:

(取决于数组有多大)

  • 循环遍历每个图块
  • 向光画一条线
  • 如果该线的任何一方碰到 X,则该线处于阴影中
  • (选修的):计算线穿过的 X 量,并进行复杂的数学计算以确定阴影中图块的比例。注意:这可以通过在阈值处理过程中对图块和光之间的线进行抗锯齿处理(因此沿着返回光源的路线查看其他图块)来完成,这些将显示为小异常。根据所使用的逻辑,您可能会确定图块处于阴影中的程度(如果有的话)。

您还可以跟踪已测试的像素,因此稍微优化解决方案,而不是重新测试像素两次。

如果线条是半透明的并且 X 块再次是半透明的,那么通过使用图像处理和在像素(图块)之间绘制直线可以很好地实现圆顶。您可以对图像进行阈值以确定该线是否与“X”相交

如果您可以选择使用第三方工具,那么我可能会选择它。从长远来看,它可能会更快,但你对游戏的了解会更少。

这只是为了好玩:

如果您先执行将图块转换为线条的步骤,则可以在2D中复制Doom 3方法。例如,

- - - - -
- X X X -
- X X - -
- X - - -
- - - - L

...将被缩减为三条线,将三角形中的实体对象的角连接起来。

然后,做Doom 3引擎所做的事情:从光源的角度来看,考虑每个“墙壁”。面对光明。 (在这个场景中,只考虑对角线。)对于每条这样的线,将其投影到一个梯形,其前边缘是原始线,其边缘位于从光源到每个端点的线上,其背面是很远,过去了整个场景。所以,它是一个“指向”的梯形。光明。它包含墙壁投射阴影的所有空间。用黑暗填充这个梯形中的每个瓷砖。

继续完成所有这些行,你最终会得到一个“模板”。包括从光源可见的所有瓷砖。用浅色填充这些瓷砖。您可能希望在离开光源(“衰减”)或做其他奇特的东西时稍微点亮瓷砖。

对场景中的每个光源重复。

要检查图块是否在阴影中,您需要将直线绘制回光源。如果该线与另一个已占用的图块相交,则您正在测试的图块处于阴影中。光线跟踪算法对视图中的每个对象(在您的情况下为tile)执行此操作。

维基百科上的光线追踪文章具有伪代码。

这是一种非常简单但相当有效的方法,它使用屏幕上图块数量的线性时间。每个瓷砖都是不透明的或透明的(给我们提供),每个瓷砖都可以是可见的或阴影的(这就是我们想要计算的东西)。

我们首先将头像本身标记为“可见”。

然后,我们应用此递归规则来确定剩余切片的可见性。

  1. 如果图块与头像位于同一行或列上,则只有在靠近头像的相邻图块可见且透明时才会显示。
  2. 如果瓷砖与化身在45度对角线上,那么只有当相邻的对角线瓷砖(朝向化身)可见且透明时才可见。
  3. 在所有其他情况下,请考虑与相关图块相比更接近化身的三个相邻图块。例如,如果此图块位于(x,y)并位于头像的右上方,那么要考虑的三个图块是(x-1,y),(x,y-1)和(x- 1,y-1)。如果这三个图块中的任何可见且透明,则可以看到相关图块。
  4. 为了使这项工作,必须按特定顺序检查磁贴,以确保已经计算了递归情况。这是一个工作排序的例子,从0开始(这是化身本身)并且计数:

    9876789
    8543458
    7421247
    6310136
    7421247
    8543458
    9876789
    

    具有相同编号的瓷砖可以按任何顺序进行检查。

    结果不是美丽的阴影投射,而是计算可信的瓷砖可见度。

TK的解决方案是您通常用于此类事情的解决方案。

对于局部照明场景,您可以使用它,以便如果图块导致处于阴影中,则该图块将被拆分为4个图块,并且每个图块都会进行测试。然后你可以根据需要将其分开吗?

编辑:

你也可以通过不测试光线附近的任何瓷砖来优化它 - 当你拥有多个光源时,这会更重要,我想......

我实际上最近才将这个功能写入我的一个项目中。

void Battle::CheckSensorRange(Unit* unit,bool fog){
    int sensorRange = 0;
    for(int i=0; i < unit->GetSensorSlots(); i++){
        if(unit->GetSensorSlot(i)->GetSlotEmpty() == false){
            sensorRange += unit->GetSensorSlot(i)->GetSensor()->GetRange()+1;
        }
    }
    int originX = unit->GetUnitX();
    int originY = unit->GetUnitY();

    float lineLength;
    vector <Place> maxCircle;

    //get a circle around the unit
    for(int i = originX - sensorRange; i < originX + sensorRange; i++){
        if(i < 0){
            continue;
        }
        for(int j = originY - sensorRange; j < originY + sensorRange; j++){
            if(j < 0){
                continue;
            }
            lineLength = sqrt( (float)((originX - i)*(originX - i)) + (float)((originY - j)*(originY - j)));
            if(lineLength < (float)sensorRange){
                Place tmp;
                tmp.x = i;
                tmp.y = j;
                maxCircle.push_back(tmp);
            }
        }
    }

    //if we're supposed to fog everything we don't have to do any fancy calculations
    if(fog){
        for(int circleI = 0; circleI < (int) maxCircle.size(); circleI++){
            Map->GetGrid(maxCircle[circleI].x,maxCircle[circleI].y)->SetFog(fog);
        }
    }else{

        bool LOSCheck = true;
        vector <bool> placeCheck;

        //have to check all of the tiles to begin with 
        for(int circleI = 0; circleI < (int) maxCircle.size(); circleI++){
            placeCheck.push_back(true);
        }

        //for all tiles in the circle, check LOS
        for(int circleI = 0; circleI < (int) maxCircle.size(); circleI++){
            vector<Place> lineTiles;
            lineTiles = line(originX, originY, maxCircle[circleI].x, maxCircle[circleI].y);

            //check each tile in the line for LOS
            for(int lineI = 0; lineI < (int) lineTiles.size(); lineI++){
                if(false == CheckPlaceLOS(lineTiles[lineI], unit)){
                    LOSCheck = false;

                    //mark this tile not to be checked again
                    placeCheck[circleI] = false;
                }
                if(false == LOSCheck){
                    break;
                }
            }

            if(LOSCheck){
                Map->GetGrid(maxCircle[circleI].x,maxCircle[circleI].y)->SetFog(fog);
            }else{
                LOSCheck = true;
            }
        }
    }

}

如果您正在调整它以供自己使用,那么您需要一些额外的东西。为方便起见,Place类型被定义为x和y位置。

线条功能来自维基百科,只做很少的修改。我没有打印出x坐标,而是将其更改为返回一个包含该行中所有点的位置矢量。 CheckPlaceLOS函数只是根据tile上是否有对象返回true或false。可以通过这种方式进行更多优化,但这对我的需求来说很好。

我知道这是多年前的问题,但对于那些寻找这种风格的人来说,我想提供一个解决方案,我曾经使用过一次我自己的roguelike;手动地“预先计算” FOV。如果光源的视野具有最大外部距离,则手工绘制由阻挡对象创建的阴影实际上并不是很费力。你只需要绘制圆的1/8(加上直线和对角线方向);你可以使用对称的其他eigths。你将拥有与1/8圈中的正方形一样多的阴影贴图。然后根据对象将它们组合在一起。

这方面的三个主要优点是: 如果实施得当,它会很快 2.你要决定如何投射阴影,不要比较哪种算法处理哪种情况最好 3.没有奇怪的算法引发的边缘情况,你必须以某种方式修复

骗局是你真的没有实现一个有趣的算法。

我在单个C函数中实现了基于tile的视野。这里是: https://gist.github.com/zloedi/9551625

如果你不想花时间重新发明/重新实现它,那里有很多游戏引擎。 Ogre3D 是一款开源游戏引擎,完全支持照明,声音和游戏控制。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top