【问题标题】:Performance issue looping through big tilemap in unity 2D C#在统一 2D C# 中循环通过大瓷砖地图的性能问题
【发布时间】:2021-12-01 07:54:48
【问题描述】:

(解决方案在最后)

我有一个瓦片地图,它可以根据我存储在字典中的数据按位置传播到相邻的瓦片。

虽然检查很快就完成了,但它确实需要相当多的资源,导致我每次调用函数时都会出现延迟。

目前,我通过每次完成循环迭代时实际等待一点来确保代码不会执行得太快。虽然我可以接受我的代码没有尽可能快地执行,但这并不是正确的方法。

所以基本上:有没有办法限制单一脚本/函数在 unity/c# 中的执行不使用超过一定百分比的游戏资源(或类似的东西)?或者也许有一种方法可以显着提高我找不到的功能性能?

提前致谢!

解决方案

感谢来自Michael UrvanakaBase 的伟大建议,我能够通过使用块大大提高性能。我无法实施所有建议的改进,因此请查看(并投票)他们各自的答案以获得更高的性能。

如何:我每隔几秒重置一个布尔值。设置该布尔值后,我循环遍历Update()-方法中的部分必要图块(块)并记住下一个Update()-调用的最后一个索引。当我到达 tile-list 的末尾时,我就完成了,可以将 bool 设置为 false 并重置索引。

这是我完成的代码的简化版本:

{
    [SerializeField] private Tilemap tilemap;
    [SerializeField] private List<TileType> tileTypes;
    [SerializeField] private List<TileType> growthTileTypes;
    private Dictionary<Vector3Int, TileData> tileDataByPosition;
    private List<TileData> spreadingTileDatas;
    private Dictionary<TileBase, TileType> tileTypeByTile;
    private bool spreading;
    private spreadIndex = 0;
    private int chunkSize = 10;
    private void Awake()
    {
        // set up stuff here
        InvokeRepeating("ResetSpreading", 10, 10);
    }
    private void Update()
    {
        if (spreading == true)
        {
            Spread();
        }
    }
    private void Spread()
    {
        for (
            int index = spreadIndex;
            index < spreadingTileDatas.Count && index < spreadIndex + chunkSize;
            index++
        )
        {
           // do stuff here
        }
        spreadIndex += chunkSize;
        if (spreadIndex >= spreadingTileDatas.Count)
        {
            spreadIndex = 0;
            spreading = false;
        }
    }
}

【问题讨论】:

    标签: c# unity3d


    【解决方案1】:

    回答您的问题:“有没有办法限制在 unity/c# 中执行单个脚本/函数,以不使用超过一定百分比的游戏资源(或类似的东西)?” - 你想要做的是让这个循环只执行每个循环总项目的一小部分,并更频繁地调用它。您可以分块处理它,也可以使用我在本文末尾指出的时间测量。

    我没有专门检查您的代码以确切了解正在执行的操作,但这是随着时间的推移拆分处理的常用方法,而不是您如何在协程中添加微小的 WaitForSeconds()。具有如此小的 WaitForSeconds 的协程可能对性能很不利,并且简单地使用 Update() (每帧调用)并按照我描述的方式拆分处理将产生更好的性能。如果您在谷歌上搜索协程性能,您可以找到相关信息和基准。

    您将创建一个变量来跟踪索引的当前位置,以及一个变量,该变量是每帧要处理的最大索引数 - 当处理的项目总数达到最大限度。您也不会执行从 0 到 X 的循环 - 它将从 currentIndex 到结尾,然后通过将其设置为 0 来循环。每次达到最大值时您就会爆发。

    总结一下:

    1. 从 Spread() 中删除 IEnumerator 并使其成为常规函数,而不是 foreach(position in tilePosition) 使用 for(int i=0;) 等
    2. 添加像 MaxToProcessPerFrame 这样的变量
    3. 添加另一个变量,例如 currentIndex
    4. 添加第三个变量,如 totalProcessedThisFrame,每次输入 Spread() 时设置为零
    5. 例如 if (totalProcessedThisFrame++ >= MaxToProcessPerFrame) 中断;在你的循环中
    6. 添加 if (currentIndex >= tilePositions.Count) currentIndex = 0 以便循环回到开头

    我还注意到,Spread() 在最后调用了 UpdateTiles(),然后它会执行另一个 StartCoroutine(Spread()),这将导致它运行两次,我敢肯定它在某种程度上是低效的,因为它会变好每次迭代可能有双重过程。

    在 if (currentIndex >= tilePositions.Count) 上可能是调用 UpdateTiles() 的好地方,因为那将是在更新整个列表的一次迭代结束时。

    设置完成后,您可以测试 MaxToProcessPerFrame 的不同值,从一个较大的数字开始,然后将其降低,直到它看起来运行平稳,并且会中断多个帧的处理。

    第二种方法是使用 System.Diagnostics Stopwatch() 并在它超过处理的总毫秒数时退出循环。

    p>

    这两种方法可以帮助你在玩不同数量的游戏时顺利进行。

    最后,你不断调用 StartCoroutine() 的方法也不高效。您应该启动协程一次或所需的最少次数,然后在其中循环。通常使用 while (Application.isRunning) {} 循环作为 Unity 中大多数协程的基础。

    也供参考: Unity 2017 Game Optimization: Optimize all aspects of Unity performance By Chris Dickinson

    【讨论】:

      【解决方案2】:

      我建议将数据保存在Multidimensional Array 中并调用您需要的瓷砖地图的特定区域。

      示例伪代码

      TileData[,] tileMapDatas;
      
      void Start()
      {
          int rows = 5;
          int columns = 5;
          
          tileMapDatas = new TileData[rows, columns];
          
          for(int r = 0; r < rows, r++)
          {
              for(int c = 0; c < columns, c++)
              {
                  tileMapDatas[r, c] = new TileData();//Change to how you create them
              }
          }
      }
      
      void Spread(Tile tile)
      {
          if(tile.CanSpread)
          {
              //Check the mapdata for the tile above, (add check for array bounds)
              if(tileMapDatas[tile.Row + 1, tile.Column].CanChange)
              {
                  //Change
              }
              
              //Repeat for other directions
          }
      }
      

      使用这种方法效率更高,因为您只需要检查目标图块的邻居。

      【讨论】:

      • 你说性能来自于更少的检查,而不是来自于数组比字典更高效?因为那时我认为这无济于事,因为我已经有一个功能可以为我提供目标图块的邻居,所以我只需要检查那些。还是我在这里误解了什么?
      • 数组的性能会更高,我建议考虑一种方法来做到这一点,而不必遍历整个 TileMap,尽管随着地图变大,这只会变得更糟,也许添加一个参考那些可能已经改变的人
      • @LucasVenturini 是的,您有该功能,但您在每个图块上都调用它。而 akaBase 建议您检查当前目标图块的邻居。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-08
      • 1970-01-01
      相关资源
      最近更新 更多