我正在写一个基于平铺的游戏,我想支持光源。但是我的算法太弱了,所以我来找你帮忙。
情况是这样的:有一个基于图块的地图(作为二维阵列保存),包含一个光源和几个站在周围的物品。我想计算哪些瓷砖被光源点亮,哪些是阴影。
大致看起来像它的外观的视觉辅助。 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 - - - - - - -
当然,分数系统甚至会更好,因为部分模糊,瓷砖可能处于半阴影中。算法不一定非常完美 - 只是没有明显的错误和合理的快速。
(当然,会有多个光源,但这只是一个循环。)
任何参赛者?
答案 0 :(得分:21)
roguelike开发社区对视线,视野算法有一点痴迷。
这是关于该主题的roguelike wiki文章的链接: http://roguebasin.roguelikedevelopment.org/index.php?title=Field_of_Vision
对于我的roguelike游戏,我在Python中实现了阴影投射算法(http://roguebasin.roguelikedevelopment.org/index.php?title=Shadow_casting)。放在一起有点复杂,但运行合理(即使在纯Python中)并产生了很好的结果。
“宽容视野”似乎也越来越受欢迎: http://roguebasin.roguelikedevelopment.org/index.php?title=Permissive_Field_of_View
答案 1 :(得分:16)
您可以通过计算遮挡等来解决各种复杂问题,或者您可以选择简单的强力方法:对于每个单元格,使用线条绘制算法(例如Bresenham Line Algorithm)来检查每个单元格之间的每个单元格。当前的一个和光源。如果任何已填充的单元格或(如果您只有一个光源)已经过测试并发现处于阴影中的单元格,则您的单元格处于阴影中。如果您遇到已知点亮的电池,您的电池也会被点亮。对此进行简单的优化是将沿线遇到的任何单元格的状态设置为最终结果。
这或多或少是我在2004 IOCCC winning entry中使用的内容。但显然,这并不是很好的示例代码。 ;)
编辑:正如洛伦指出的那样,通过这些优化,您只需要沿地图边缘选择要跟踪的像素。
答案 2 :(得分:6)
这里提出的算法似乎比我认为需要做更多的计算。我没有测试过这个,但我认为它会起作用:
最初,将所有像素标记为亮起。
对于地图边缘的每个像素:正如Arachnid建议的那样,使用Bresenham来跟踪从像素到光线的一条线。如果该线撞击障碍物,则将所有像素从边缘标记为刚好超出障碍物的阴影。
答案 3 :(得分:5)
又快又脏:
(取决于阵列的大小)
您还可以跟踪哪些像素已经过测试,因此可以稍微优化解决方案,而不是重新测试像素两次。
通过使用图像处理和在像素(瓷砖)之间绘制直线,可以很好地使用圆顶。如果线条是半透明的,则X块再次是半透明的。您可以对图像进行阈值处理,以确定线条是否与“X”
相交如果您可以选择使用第三方工具,那么Id可能会接受它。从长远来看,它可能会更快,但你对游戏的了解较少。
答案 4 :(得分:4)
这只是为了好玩:
如果您先执行将图块转换为线条的步骤,则可以在2D中复制Doom 3方法。例如,
- - - - -
- X X X -
- X X - -
- X - - -
- - - - L
...将被缩减为三条线,将三角形中的实体对象的角连接起来。
然后,做Doom 3引擎的工作:从光源的角度来看,考虑面向光线的每个“墙”。 (在这个场景中,只考虑对角线。)对于每条这样的线,将其投影到一个梯形,其前边缘是原始线,其边缘位于从光源到每个端点的线上,其背面是很远,过去了整个场景。所以,它是一个“指向”光线的梯形。它包含墙壁投射阴影的所有空间。用黑暗填充这个梯形中的每个瓷砖。
继续完成所有这些线条,你将得到一个“模板”,其中包括从光源可见的所有瓷砖。用浅色填充这些瓷砖。您可能希望在离开光源(“衰减”)或做其他花哨的东西时稍微点亮瓷砖。
对场景中的每个光源重复。
答案 5 :(得分:3)
要检查图块是否在阴影中,您需要将直线绘制回光源。如果该线与另一个已占用的图块相交,则您正在测试的图块处于阴影中。光线跟踪算法对视图中的每个对象(在您的情况下为tile)执行此操作。
维基百科上的Raytracing article有伪代码。
答案 6 :(得分:3)
这是一种非常简单但相当有效的方法,它使用屏幕上图块数量的线性时间。每个瓷砖都是不透明的或透明的(给我们提供),每个瓷砖都可以是可见的或阴影的(这就是我们想要计算的东西)。
我们首先将头像本身标记为“可见”。
然后,我们应用此递归规则来确定剩余切片的可见性。
为了使这项工作,必须按特定顺序检查磁贴,以确保已经计算了递归情况。这是一个工作排序的例子,从0开始(这是化身本身)并且计数:
9876789
8543458
7421247
6310136
7421247
8543458
9876789
具有相同编号的瓷砖可以按任何顺序进行检查。
结果不是美丽的阴影投射,而是计算可信的瓷砖可见度。
答案 7 :(得分:2)
TK的解决方案是您通常用于此类事情的解决方案。
对于局部照明场景,您可以使用它,以便如果图块导致处于阴影中,则该图块将被拆分为4个图块,并且每个图块都会进行测试。然后你可以根据需要将其分开吗?
编辑:
你也可以通过不测试光线附近的任何瓷砖来优化它 - 当你拥有多个光源时,这会更重要,我猜...
答案 8 :(得分:2)
我实际上最近才将这个功能写入我的一个项目中。
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。可以通过这种方式进行更多优化,但这对我的需求来说很好。
答案 9 :(得分:2)
我知道这是多年前的问题,但对于那些寻找这种风格的人来说,我想提供一种解决方案,我曾经使用过一次我自己的roguelike;手动&#34;预先计算&#34; FOV。如果光源的视野具有最大外部距离,那么手绘阴影对象所产生的阴影并不是很费力。你只需要绘制圆的1/8(加上直线和对角线方向);你可以使用对称的其他eigths。你将拥有与1/8圈中的正方形一样多的阴影贴图。然后根据对象将它们组合在一起。
这方面的三个主要优点是: 如果实施得当,它会很快 2.你要决定如何投射阴影,不要比较哪种算法处理哪种情况最好 3.没有奇怪的算法引发的边缘情况,你必须以某种方式修复
你不能真正实现一个有趣的算法。
答案 10 :(得分:1)
我在单个C函数中实现了基于tile的视野。这里是: https://gist.github.com/zloedi/9551625
答案 11 :(得分:0)
如果你不想花时间重新发明/重新实现它,那里有很多游戏引擎。 Ogre3D是一个开源游戏引擎,完全支持照明,声音和游戏控制。