【问题标题】:How to load/Unload tile chunks faster(C#)如何更快地加载/卸载平铺块(C#)
【发布时间】:2013-08-30 22:48:32
【问题描述】:

好吧,所以基本上在我的游戏中,世界是由瓷砖组成的,瓷砖被分成块(50x50 瓷砖,瓷砖是 16x16),所以整个世界都不会加载到内存中。块被保存到文件中,并且只加载需要的块(屏幕上的块和屏幕两侧的 1 个屏幕外(顶部、左侧、底部、右侧)),其他块被卸载等。这一切都有效但是很好,当加载实际包含图块的块时,游戏逻辑会冻结很多(你知道要加载文件吗?),基本上我的问题是如何使我当前的方法更快,所以几乎没有明显的滞后?

基本上每个块只包含一个 2D 字节数组,包含 tile 类型而不是实际的 tile 类,并且该字节数组保存到文件中以供以后加载。 我的块加载/保存功能:

保存:

    public void SaveChunk(int cx, int cy)
    {

        String chunkname = "";
        chunkname = WorldChunks[cx, cy].X + "_" + WorldChunks[cx, cy].Y;

        FileStream fileStream = File.Create(Main.ChunkPath + "\\Chunks\\" + chunkname + ".chnk");
        StreamWriter writer = new StreamWriter(fileStream);
        String json = JsonConvert.SerializeObject(WorldChunks[cx, cy].myTiles, Formatting.None);
        writer.Write(json);
        writer.Close();
    }

加载中:

    public void LoadChunk(int xx, int yy)
    {

        string chunkname = xx + "_" + yy;
        if (File.Exists(Main.ChunkPath + "\\Chunks\\" + chunkname + ".chnk"))
        {
            //If the chunk is not loaded, Create a new chunk and begin loading it's tiles.
            if (WorldChunks[xx, yy] == null)
            {
                WorldChunks[xx, yy] = new Chunk(xx, yy);
            }

            System.Diagnostics.Debug.WriteLine("Loading chunk [" + xx + "," + yy + "]");
            Stream stream = File.Open(Main.ChunkPath + "\\Chunks\\" + chunkname + ".chnk", FileMode.Open);
            StreamReader reader = new StreamReader(stream);
            JsonSerializerSettings settings = new JsonSerializerSettings();
            settings.TypeNameHandling = TypeNameHandling.Objects;
            WorldChunks[xx, yy].myTiles = JsonConvert.DeserializeObject<byte[,]>(reader.ReadToEnd(), settings);
            stream.Close();
            System.Diagnostics.Debug.WriteLine("Chunk loaded.. [" + xx + "," + yy + "]");
            int x, y;
            for (x = 0; x < Main.ChunkSizeX; x++)
            {
                for (y = 0; y < Main.ChunkSizeY; y++)
                {
                    byte _Type = WorldChunks[xx, yy].myTiles[x, y];
                    int tx = (xx * ChunkSizeX) + x;
                    int ty = (yy * ChunkSizeY) + y;
                    if (_Type > 0)
                    {
                        if (Tiles[tx, ty] == null && tx < MaxTilesX && ty < MaxTilesY)
                        {
                            Tiles[x + (xx * ChunkSizeX), (yy * ChunkSizeY) + y] = new Tile();
                            Tiles[(xx * ChunkSizeX) + x, (yy * ChunkSizeY) + y].Type = _Type;
                            if (ty > GROUND_LEVEL[tx] + 1)
                            {
                                //System.Diagnostics.Debug.Write("Below level:" + x + "|" + tx);
                                Tiles[tx, ty].HasBG = true;
                            }

                        }
                        else continue;
                    }

                }
            }

        }

    }

我尝试过更改块大小,我尝试过 25x25、16x16、35x35 等。但仍然无济于事。 那么我怎样才能使这些功能更快呢?任何其他提示/建议将不胜感激!谢谢!

编辑:我的代码中的瓶颈来自秒表和其他测试,是从文件中实际加载我什至删除了瓷砖的放置,它仍然锁定了游戏逻辑,但是我意识到最大的明显延迟是当我' m 在块之间,它必须加载比平时更多的块,如下所示:

现在我的块实际上可能比这更大,但同样的事情是可能的。

我的保存功能只是为每个图块保存 1 个字节,我的文件大小仅为每个块 2.5kb 左右,因为每个块有 2,500 个图块,每个图块 1 个字节(只是保存图块的类型)。

所以我认为保存和加载它们不会有太大影响,我之前也设置了异步文件流,但性能几乎相同。

这是我加载/卸载块的代码:

    public void CheckChunks()
    {
        int StartX = (int)Math.Floor(ScreenPosition.X / (ChunkSizeX * 16)) - 2;
        int StartY = (int)Math.Floor(ScreenPosition.Y / (ChunkSizeY * 16)) - 2;

        if (StartX < 0) StartX = 0;
        if (StartY < 0) StartY = 0;

        int EndX = (int)Math.Floor((ScreenPosition.X + GraphicsDevice.Viewport.Width) / (ChunkSizeX * 16)) + 2;
        int EndY = (int)Math.Floor((ScreenPosition.Y + GraphicsDevice.Viewport.Height) / (ChunkSizeY * 16)) + 2;
        if (EndX > MaxChunksX) EndX = MaxChunksX;
        if (EndY > MaxChunksY) EndY = MaxChunksY;

        for (int x = StartX; x < EndX; x++)
        {
            for (int y = StartY; y < EndY; y++)
            {
                if (WorldChunks[x, y] == null)
                {

                    LoadChunk(x, y, true);

                    if (WorldChunks[x, y] != null)
                    {

                        LoadedChunks.Add(WorldChunks[x, y]);
                    }
                     break;
                }

            }
        }

            foreach (Chunk cc in LoadedChunks)
            {

                if (cc.X < StartX  || cc.Y < StartY || cc.X >EndX || cc.Y > EndY)
                {
                    System.Diagnostics.Debug.WriteLine("Chunk to be unloaded..");
                    ChunkBuffer.Add(cc);
                    UnloadChunk(cc);

                }
            }

            foreach (Chunk c in ChunkBuffer)
            {
                LoadedChunks.Remove(c);
            }
        ChunkBuffer.Clear();

    }

像我的世界这样的游戏如何如此快速地加载和卸载它们的区块?

【问题讨论】:

  • 要让事情变得更快,您需要知道代码中的慢点。配置文件(或至少使用Stopwatch 测量时间)并从那里开始。
  • 谢谢你,但我忘了说我用过秒表,甚至拆掉零件只是为了看看瓶颈在哪里。它是从文件中实际加载的,但是我不确定是反序列化还是从文件中读取(很可能是读取)。我会做进一步的测试,看看它到底在哪里。
  • 我也在做类似的游戏。请问你的“WorldChunks”是什么,我猜是一个类?此外,您如何跟踪加载/卸载了哪些块?我为此使用了一个列表,但我一直在寻找其他从事类似工作的人的意见,看看他们是如何做到的。谢谢!

标签: c# performance loading tiles chunks


【解决方案1】:

磁盘操作通常过于繁重,无法在单个游戏帧内执行而不会明显影响帧速率。您的磁盘操作可以在单独的线程中运行。您需要确保在加载过程中正确锁定内存。如果玩家碰巧在下一个块完成加载之前到达块的边缘,这仍然可能导致可见的延迟,例如如果玩家在块的底角,然后同时向右和向下移动到未加载的对角块中怎么办?这可以通过加载 4 个相邻块以及 4 个对角块来解决,并确保这些块足够大以使玩家不会很快到达边缘。如果您不熟悉多线程,则需要进行一些研究。

http://www.yoda.arachsys.com/csharp/threads/

另一种可能的方法是将您的游戏世界拆分为多个地图,并将整个地图加载到内存中,而不是更小的块。这需要更长的加载时间,但仅在玩家在地图之间移动时才需要执行,因此通常对玩家体验的影响较小。如有必要,可以在此处使用加载屏幕。

【讨论】:

  • 感谢您的回复!我一定会研究多线程!我之前已经为块设置了异步加载,但是在使用它时没有明显的性能提升,也许我做错了?但是你知道像我的世界这样的游戏是如何在不严重中断游戏逻辑的情况下如此顺利地加载它们的块的吗?(我假设它使用异步在不同的线程上运行加载函数?)
  • 很难说,但我的猜测是主线程需要的资源被锁定。当前块的内存是否与相邻块分开?
  • 文件总共有多大?如果它只是一个 tile id 的二维数组,是否可以加载整个文件?
  • 编辑了我的主帖,但是是的,当前块的内存与相邻块是分开的,每个块的总文件大小约为 2kb(每块 1 字节,每块 50x50 块)。
【解决方案2】:

LoadChunk 只做两件事,磁盘 IO 和反序列化。您可以尝试独立加速它们。根据您加载的数据量,只需添加 BufferedStream 可能会有所帮助。一个完全不同的选择是使用异步 I/O。使用起来有点困难,但它不会阻塞你的 UI 线程,而且你会得到一些简单的并行性。

我不认为 JSON 反序列化是最快的,您应该将其性能与您自己的反序列化例程进行比较。

16 x 16 的瓷砖看起来很小,有理由不把它们做大吗?瓷砖越大,对 LoadChunk 的调用就越少。一旦你尽可能地加快加载速度,你就会知道何时加载下一个屏幕外图块。例如。如果加载一个图块需要 3 个“滴答声”,您应该在玩家位于边缘 3 个方格内时加载它。

SaveChunk 可以移动到 ThreadPool 线程或重写以使用异步 IO。

【讨论】:

    【解决方案3】:

    正如之前的最后一条评论所说,JSON 反序列化并不是最快的方法。您可以尝试改用二进制文件。这可以加快反序列化的过程。

    Convert.ToString(myByte, 2).PadLeft(a, c);
    

    其中 a 是字符串中的字符数量,c 是用来填充剩余未使用空间的字符。

    在您的情况下,您可以指定 a 为 8 且 c = '0'。这将适用于一个字节。

    【讨论】:

      猜你喜欢
      • 2019-02-19
      • 1970-01-01
      • 2012-03-08
      • 1970-01-01
      • 2022-11-10
      • 2015-12-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多