在2D游戏中有效地检索视口中的Z个有序对象

时间:2014-11-20 00:40:39

标签: performance algorithm sorting graphics game-engine

想象一下具有较大游戏区域的2D游戏,比如10000x10000像素。现在想象这个区域有成千上万的物体。所有对象都在Z顺序列表中,因此每个对象相对于其他所有对象都具有明确定义的位置,即使它们距离很远。

假设此游戏区域中有一个视口,显示该游戏区域的500x500区域。显然,如果算法是“对于Z顺序列表中的每个对象,如果在视口内部,则渲染它”,那么您将浪费大量时间来迭代远在视口外的所有数千个对象。更好的方法是在视口附近或内部维护Z顺序的对象列表。

如果对象和视口都在移动,那么维护Z-ordered对象列表的有效方法是什么?这是一个通用游戏引擎,因此没有太多其他假设或细节可以添加以利用:问题几乎就是这个。

2 个答案:

答案 0 :(得分:1)

您不需要按Z强烈排序内存布局。而是需要将对象存储在沿着查看表面定向的空间分区结构中。 典型的分区结构是2D中的四叉树。您可以使用二进制树,可以使用网格,也可以使用空间哈希方案。您甚至可以将这些技术混合在一起并将它们相互组合 没有“最佳”,但您可以轻松地编写和维护代码。你可以得到的记忆 让我们考虑网格,它是最简单的实现,访问速度最快,最容易遍历。 (遍历是去邻居小区的事实)

想象一下,考虑到单元格内容只是一个小对象(如std :: vector或c#List),你可以自己为网格骨架使用20MB的RAM,比如50个字节。对于10k像素的正方形表面,您可以:

sqrt(20*1024*1024 / 50) = 647

一维获得647个细胞,因此10k / 647 = 15个像素宽的细胞 仍然非常小,所以我认为完全可以接受。例如,您可以调整数字以获得512像素的单元格。当一些单元格适合视口时,它应该是合适的。

然后,很容易确定视口激活了哪些单元格,通过将左上角除以单元格的大小和结果的地板,这直接在单元格中为您提供索引。 (前提是您的视口空间和网格空间都从0开始。否则您需要偏移)
最后取右下角,确定单元格的网格坐标;你可以在最小值和最大值之间进行双循环(x和y)迭代激活的单元格 处理单元格时,您可以通过浏览先前存放过的对象列表来绘制它包含的对象。

注意跨越2个细胞或更多细胞的物体。您需要做出选择,或者只存储一次,但是您的搜索算法将始终需要知道该区域中最大元素的大小,并且还要搜索邻近单元格的列表(通过尽可能多地搜索到一定要至少覆盖​​这个最大元素的大小。) 或者,您可以多次存储它(我的首选方式),并且只需确保在迭代单元格时,每帧只处理一次对象。通过在对象结构中使用frame id(作为可变成员),可以很容易地实现这一点。

同样的逻辑适用于更灵活的分区,如二叉树。

我已经在我的引擎中实现了两种功能,检查代码,它可以帮助您了解详细信息:http://sourceforge.net/projects/carnage-engine/

关于你的Z Ordering的最后一句话,如果每个Z有多个内存存储器,那么你已经进行了空间分区,而不是沿着良好的轴。
这可以称为分层 你可以做什么作为优化,而不是在你的单元格中存储对象列表,你可以存储(有序)map个对象,它们的键是它们的Z,因此迭代将沿着Z排序。 / p>

答案 1 :(得分:0)

此类问题的典型解决方案是根据对象的近似XY位置对对象进行分组。例如,您可以将它们存储到500x500区域,即交叉[0,500] x [0,500]的对象,交叉[500,1000] x [0,500]等的对象。非常大的对象可能会列在多个存储桶中,但可能存在没有太多非常大的物体。

对于每个视口,您最多需要检查4个桶以查找要渲染的对象。通常,您只需要查看所需的渲染对象,因此它应该是高效的。当您重新定位对象时,这确实需要更多工作更新。但是,假设一个典型的对象只在一个桶中,它仍然应该很快。