如何在2d城市建造者中提高昂贵功能的性能


9

我已经在寻找答案,但是我无法找出处理昂贵函数/计算的最佳方法。

在我当前的游戏(基于2d瓷砖的城市建筑物)中,用户能够放置建筑物,建造道路等。所有建筑物都需要与用户必须放置在地图边界处的路口相连接。如果建筑物未连接到此路口,则在受影响建筑物上方将弹出“未连接到道路”标志(否则必须将其删除)。大多数建筑物都有半径,并且可能彼此也有关联(例如,消防部门可以帮助半径30瓦范围内的所有房屋)。这也是我在公路连接发生变化时也需要更新/检查的内容。

昨天我遇到了一个很大的性能问题。让我们看一下以下情况:用户当然也可以删除建筑物和道路。因此,如果用户现在在连接点之后立即断开连接,则需要同时更新许多建筑物。我认为第一个建议是避免嵌套循环(在这种情况下,这绝对是一个重要原因),但是我必须检查一下...

  1. 如果在拆除路砖的情况下建筑物仍连接到路口(我仅对那条路受影响的建筑物这样做)。(在这种情况下可能是一个较小的问题)
  2. 半径图块列表,并获取半径范围内的建筑物(嵌套循环-大问题!)

    // Go through all buildings affected by erasing this road tile.
    foreach(var affectedBuilding in affectedBuildings) {
        // Get buildings within radius.
        foreach(var radiusTile in affectedBuilding.RadiusTiles) {
            // Get all buildings on Map within this radius (which is technially another foreach).
            var buildingsInRadius = TileMap.Buildings.Where(b => b.TileIndex == radiusTile.TileIndex);  
    
            // Do stuff.
        }
    }

所有这些使我的FPS 从60降低到一秒钟几乎达到了10

我也可以。我的想法是:

  • 没有为此使用主线程(更新功能),而是另一线程。当我开始使用多线程时,可能会遇到锁定问题。
  • 使用队列来处理大量计算(这种情况下最好的方法是什么?)
  • 在我的对象(建筑物)中保留更多信息,以避免进行更多计算(例如,半径范围内的建筑物)。

使用最后一种方法,我可以在此foreach中删除一个嵌套形式:

// Go through all buildings affected by erasing this road tile.
foreach(var affectedBuilding in affectedBuildings) {
    // Go through buildings within radius.
    foreach(var buildingInRadius in affectedBuilding.BuildingsInRadius) {
        // Do stuff.
    }
}

但是我不知道这是否足够。如果玩家拥有大地图,像城市天际线这样的游戏必须处理更多建筑物。他们如何处理这些事情?由于并非所有建筑物都同时更新,因此可能存在更新队列。

我期待您的想法和意见!

非常感谢!


2
使用事件探查器应有助于确定代码的哪一部分有问题。它可能是您找到受影响建筑物的方式,也可能是//做事。附带说明一下,像《城市天际线》这样的大型游戏通过使用四叉树等空间数据结构来解决这些问题,因此所有空间查询的速度远远快于通过for循环遍历数组的速度。例如,在您的情况下,您可以拥有所有建筑物的依赖图,并且通过遵循该图可以立即知道哪些因素会影响哪些因素而无需迭代。
Exaila

感谢您提供详细信息。我喜欢依赖的想法!我来看看那个!
Yheeky

您的建议很棒!我只是使用VS profiler向我展示,我对每个受影响的建筑物都有寻路功能,以检查结点连接是否仍然有效。当然,这真是太贵了!它只有约5 FPS,但总比没有好。我将摆脱它,将建筑物分配给路砖,这样我就不需要一遍又一遍地进行寻路检查。非常感谢!不,我只需要修复半径较大的建筑物。
Yheeky

我很高兴您发现它有用:D
Exaila

Answers:


3

缓存建筑物的覆盖范围

将信息缓存在效果器建筑物范围内的建筑物(可以从效果器或受影响的缓存器中缓存)的想法绝对是个好主意。建筑物(通常)不会移动,因此没有理由重做这些昂贵的计算。仅在创建或删除建筑物时才需要检查“此建筑物会影响什么”和“什么会影响此建筑物”。

这是对内存的CPU周期的经典交换。

按地区处理覆盖率信息

如果事实证明您正在使用太多内存来跟踪此信息,请查看是否可以按地图区域处理此类信息。将地图分为n*的正方形区域n瓷砖。如果某个区域被消防部门完全覆盖,则该区域中的所有建筑物也都将覆盖。因此,您只需要按区域而不是单个建筑物存储覆盖率信息。如果某个区域仅被部分覆盖,则需要退回到处理该区域中的建筑物连接。因此,建筑物的更新功能将首先检查“该建筑物所在的区域是否被消防部门覆盖?” 如果不是,则“该建筑物是否由消防部门单独覆盖?”。这也可以加快更新速度,因为在删除消防部门后,您不再需要更新2000座建筑物的覆盖范围状态,而只需要更新100座建筑物和25个区域。

延迟更新

您可以做的另一种优化是不立即更新所有内容,也不是同时更新所有内容。

建筑物是否仍与道路网络连接并不是您需要检查每一个框架的方法(顺便说一句,您可能还可以通过仔细研究图论来找到一些对此进行优化的方法)。如果建筑物仅在建筑物建成后每隔几秒钟定期检查一次(如果道路网络发生变化),则完全足够。建筑范围效果也是如此。如果建筑物每隔几百帧才检查一次,那是完全可以接受的:“至少有一个影响我的消防部门还在活动吗?”

因此,您可以让您的更新循环一次仅对几百座建筑物进行这些昂贵的计算。您可能希望对当前屏幕上的建筑物设置偏好,以便玩家立即获得有关其动作的反馈。

关于多线程

城市建设者往往在计算上更为昂贵,特别是如果您要允许玩家建造大型建筑,并且希望具有较高的模拟复杂性时,尤其如此。因此,从长远来看,考虑可以异步处理游戏中的哪些计算可能并没有错。


这就解释了为什么SNES上的SimCity需要花费一些时间才能重新连接/连接,我想它也会与其他区域范围的效果一起发生。
lozzajp

感谢您的宝贵意见!我还认为在内存中保存更多信息可以加快我的游戏速度。我也喜欢将TileMap分成多个区域的想法,但是我不知道这种方法是否足以消除我长期以来的最初问题。我有一个关于延迟更新的问题。假设我有一个函数,使我的FPS从60下降到45。什么是最好的拆分计算以处理CPU能够处理的理想量的方法?
Yheeky

@Yheeky对此没有通用的解决方案,因为它在很大程度上取决于情况,您可以延迟哪些计算,不能延迟哪些计算以及什么是明智的计算单位。
菲利普

我试图延迟这些计算的方法是创建一个包含“ CurrentlyUpdating”标志的项目的队列。仅将此标志设置为true的该项被处理。计算完成后,将从列表中删除该项目,并处理下一个项目。这应该工作,对不对?但是,如果您知道一次计算本身会使FPS降低,那么可以使用哪种方法?
Yheeky

1
@Yheeky正如我所说,没有通用的解决方案。我通常会尝试的操作(按该顺序):1.查看是否可以通过使用更合适的算法和/或数据结构来优化计算。2.看看是否可以将其划分为多个子任务,可以分别延迟。3.看看是否可以在单独的威胁中做到这一点。4.摆脱需要该计算的游戏机制,看看是否可以用更便宜的计算代替它。
菲利普

3

1.工作重复

您的affectedBuildings彼此可能彼此靠近,因此不同的半径将重叠。标记需要更新的建筑物,然后更新它们。

var toBeUpdated = new HashSet<Tiles>();
foreach(var affectedBuilding in affectedBuildings) {
    foreach(var radiusTile in affectedBuilding.RadiusTiles) {
         toBeUpdated.Add(radiusTile);

}
foreach (var tile in toBeUpdated)
{
    var buildingsInTile = TileMap.Buildings.Where(b => b.TileIndex == radiusTile.TileIndex);
    // Do stuff.
}

2.不合适的数据结构。

var buildingsInTile = TileMap.Buildings.Where(b => b.TileIndex == radiusTile.TileIndex);

显然应该

var buildingsInRadius = tile.Buildings;

其中Buildings是IEnumerable具有恒定迭代时间的(例如List<Building>


好点子!我猜我尝试使用MoreLINQ在该对象上使用Distinct(),但我同意这可能比检查重复项更快。
Yheeky
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.