具有2000万个图块的地图会使游戏内存不足,如何避免呢?


11

在加载额外的巨大地图时,加载方法会在创建新的地图图块实例时抛出内存不足异常。我希望至少在服务器应用程序(如果可能的话,在客户端)上处理整个地图。我应该如何解决这个问题?

UPD:这里的问题是,当仍有可用的可用内存时,如何使游戏停止崩溃。至于将地图分成几块,这是一个好方法,但不是我想要的。

UPD2:最初,我去给每个新的tile类实例分配了一个纹理,这就是占用大量内存(也需要加载时间)的原因。现在,它占用的空间减少了大约四倍。多亏了每个人,现在我可以运行巨大的地图,而无需考虑将它们分解成几块。

UPD3:重写代码以查看图块属性的数组是否运行得更快或消耗的内存更少(比相应的图块中的对象属性表示的对象属性要小),我发现不仅花了很多时间去尝试,而且并没有带来任何性能上的改进,并且使游戏难以调试。


8
只加载播放器周围的区域。
MichaelHouse

4
2000万个图块(每个图块4个字节)仅约80MB-听起来好像你的图块不是那么小。我们无法神奇地为您提供更多的内存,因此我们必须找出如何使您的数据更小的方法。因此,向我们展示这些瓷砖中的内容。
Kylotan '07年

加载方法有什么作用?邮政编码,您是否使用Content管道?它是将纹理加载到内存还是仅加载元数据?什么元数据?XNA内容类型通常引用非托管DirectX内容,因此托管对象非常小,但是在非托管方面,除非您也正在运行DirectX探查器,否则您可能看不到很多负载。stackoverflow.com/questions/3050188/…–
奥斯卡·杜夫伯恩

2
如果系统抛出内存不足异常,那么尽管您可能会想到,但没有足够的可用内存来使用。我们不会为您提供一些秘密的代码行来启用额外的内存。每个图块的内容以及分配它们的方式都很重要。
Kylotan '07年

3
是什么让您认为自己有空闲的内存?您是否在64位操作系统中运行32位进程?
达林·塞维赖特

Answers:


58

达到1.5Gb时,应用程序崩溃。

这强烈表明您未正确表示图块,因为这意味着每个图块的大小约为80个字节。

您需要了解的是,图块的游戏概念与用户看到的可视图块之间必须有分隔。这两个概念不是一回事

以Terraria为例。最小的Terraria世界占用了4200x1200的图块,即500万个图块。现在,代表那个世界需要多少内存?

好吧,每个瓷砖都有一个前景层,一个背景层(背景墙),一个用于放置电线的“电线层”和一个用于放置家具的“家具层”。每个磁贴占用多少内存?再说一次,我们只是在概念上而不是视觉上说话。

前景图块可以轻松存储在无符号的short中。最多有65536种前景图块类型,因此使用更多的内存没有意义。背景图块很容易以无符号字节为单位,因为少于256种不同类型的背景图块。导线层是纯二进制的:图块中有导线,也可能没有。因此,每个磁贴只有一比特。家具层又可以是一个无符号字节,具体取决于有多少种不同的家具。

每个图块的总内存大小:2字节+1字节+1位+1字节:4字节+1位。因此,小的Terraria映射的总大小为20790000字节,即〜20MB。(注意:这些计算基于Terraria 1.1。自那时以来,游戏进行了很多扩展,但即使是现代Terraria,每个图块位置也可以容纳8个字节,约40MB。仍然可以忍受)。

永远不要将这种表示形式存储为C#类的数组。它们应该是整数数组或类似的东西。AC#结构也可以正常工作。

现在,当需要绘制地图的一部分时(请注意重点),Terraria需要将这些概念图块转换为实际图块。每个磁贴实际上需要选择一个前景图像,背景图像,一个可选的家具图像,并具有一个有线图像。这就是XNA带有各种Sprite表等的地方。

您需要做的是将概念图的可见部分转换为实际的XNA Sprite工作表图块。您不应该尝试一次转换整个事情。您存储的每个图块都应该只是一个索引,表明“我是图块类型X”,其中X是整数。您可以使用该整数索引来获取用于显示它的精灵。而且,您可以使用XNA的Sprite工作表,使其比绘制单个四边形更快。

现在,需要将图块的可见区域分解为不同的块,以使您不必在相机移动时就不断构建精灵表。因此,您可能拥有64x64的精灵块。从玩家当前摄像机位置可见的世界上任何64x64块都是您绘制的块。其他任何块都没有子画面。如果某个块从屏幕上掉落,则将其扔掉(注意:您并未真正删除它;而是保留它并为以后可能会看到的新块重新指定它)。

我希望至少在服务器应用程序(如果可能的话,在客户端)上处理整个地图。

您的服务器不需要了解或关心图块的外观。它只需要关心概念表示即可。用户在此处添加图块,因此它会更改该图块索引。


4
+1出色的答案。需要比我更多的投票。
MichaelHouse

22

将地形分为多个区域或大块。然后仅加载玩家可见的块,并卸载不可见的块。您可以将其想象为一条传送带,您可以在一端装载块,而在播放器移动时在另一端卸载。始终领先于玩家。

您还可以使用实例化等技巧。如果一种类型的所有图块在内存中只有一个实例,并且在所有需要的位置绘制了多次。

您可以在这里找到更多类似的想法:

他们是如何做到的:Terraria中的数百万块瓷砖

在大型瓷砖地图以及地牢上“分区”区域


问题是这种方法对客户端应用程序是好的,但对服务器而言却不是。当世界上的一些瓷砖掉了并且连锁反应开始(并且发生了很多次)时,整个地图都应该存储在内存中,而当它扔出内存时就是一个问题。
user1306322

6
每个磁贴包含什么?每个图块使用多少内存?即使每个10字节,您也只有190兆字节。服务器不应加载不需要的纹理或其他任何信息。如果需要对2000万个图块进行仿真,则需要尽可能地从这些图块上剥离下来,以形成仅包含链反应所需信息的仿真集。
MichaelHouse

5

Byte56的答案很好,我开始写这篇文章作为对此的评论,但是它太长了,其中包含一些提示,这些提示可能对答案更有帮助。

对于服务器来说,这种方法绝对与对客户端一样好。实际上,考虑到服务器将要比客户端关心更多的区域,这可能合适。服务器对需要加载的内容的想法与客户端的想法不同(服务器关心所有连接的客户端关心的所有区域),但是它同样能够管理工作区域集。

即使您可以在内存中放入如此巨大的地图(很可能也无法容纳),也不想这样做。加载不需要立即使用的数据绝对是零点,它效率低且速度慢。即使您可以将其放入内存中,也很可能无法在任何合理的时间内处理所有内容。

我怀疑您反对卸载某些区域是因为您的世界更新正在所有图块上进行迭代并对其进行处理?这当然是正确的,但并不排除仅加载某些零件。而是在加载区域时,应用错过的所有更新以使其保持最新状态。这在处理方面也更加有效(将大量精力集中在较小的内存区域上)。如果存在任何可能跨越区域边界的处理效果(例如,熔岩流或水流会延续到另一个区域),则将这两个区域绑定在一起,以便在加载一个区域时将另一个区域绑定在一起。理想情况下,应将这些情况降到最低,否则您将很快回到“必须始终加载所有区域”的情况。


3

我在XNA游戏中使用的一种有效方式是:

  • 使用Spritesheets,而不是为每个图块/对象使用图片,只需在该地图上加载所需的内容即可;
  • 将地图分成较小的部分,例如,如果地图大小是此大小的4倍,则说成500 x 200瓦片的块状地图;
  • 将此块加载到内存中,并在屏幕外缓冲区中仅绘制地图的可见部分(例如,可见图块加上玩家移动的每个方向的1个图块);
  • 清除视口并从缓冲区复制像素(这称为双缓冲区并平滑运动);
  • 当您移动char时,请跟踪地图的boud,并在下一个块接近时加载该块并将其附加到当前块;
  • 一旦玩家完全进入了新地图,您就可以卸载最后一张地图。

如果不是很大,您也可以以这种方式使用单个大地图,并使用提供的逻辑。

当然,必须平衡纹理的大小,并且分辨率为此付出了巨大的代价。尝试以较低的分辨率工作,如果需要,请在配置设置设为的情况下装入高分辨率的文件包。

您每次都只需要循环映射的相关部分并仅渲染所需的内容。这样,您将大大提高性能。

服务器可以更快地处理巨大的地图,因为它不必渲染游戏(最慢的操作之一),因此其逻辑可以是使用更大的块甚至整个地图,而仅处理逻辑围绕玩家,例如在玩家视口周围2倍的视口中。

这里的建议是,我绝不是游戏开发专家,我的游戏是为毕业的最终项目(我得分最高)而制作的,所以我可能在每个方面都不太正确,但是我已经研究了网站和http://www.gamasutra.com和XNA创建者网站creators.xna.com(目前为http://create.msdn.com)等网站,以收集知识和技能,对我来说很好。


2

要么

  • 买更多的记忆
  • 更加紧凑地表示您的数据结构
  • 不要一次将所有数据保存在内存中。

我在win7 64bit上有8 Gb ram,当达到1.5 Gb时应用程序崩溃。因此,除非我错过了一些东西,否则实际上没有必要购买更多的公羊。
user1306322 2012年

1
@ user1306322:如果您有2000万个图块,并且占用了约1.5GB的内存,则意味着您的图块大约为每个图块80个字节。您在这些东西中到底存储了什么?
Nicol Bolas 2012年

就像我说的@NicolBolas,一些整数,字节和一个texture2d。同样,它们并没有全部占用1.5GB,当应用程序达到此内存成本时,应用程序将崩溃。
user1306322 2012年

2
@ user1306322:仍然远远超过了需要。一个多大texture2d?每个磁贴是否具有唯一的磁贴,或者它们共享纹理?还有,你哪说的?您当然没有把它放在您的问题中,即信息应该在哪里。请更好地说明您的具体情况。
Nicol Bolas 2012年

@ user1306322:坦白地说,我不太明白为什么我的答案被否决了。我列举了针对内存不足情况的三种补救措施-当然,这并不意味着它们都必须适用。但是我仍然认为它们是正确的。不过,我可能应该写“扩展您的内存”而不是“购买”来包括虚拟机的情况。我是否因为我的回答简洁而不是冗长而被否决?我认为他们的观点很简单,无需进一步解释就可以理解?!
Thomas

1

这里的问题是,当仍有可用的可用内存时,如何使游戏停止崩溃。至于将地图分成几块,这是一个好方法,但不是我想要的。

为了响应您的更新,您无法分配Int32.MaxValue大小超出的数组。您必须将其拆分为大块。您甚至可以将其封装在一个包装器类中,该包装器类公开类似数组的外观:

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.