游戏如何处理已保存的内容?

16

我在SO上没有看到对这个问题的回答,这让我担心它可能非常简单,而我却错过了什么。

背景(可以跳过):多年前我需要一门本科课程,那是计算机图形学,但由于我离开后,它变成了更多的游戏开发。对我来说这很好,因为它比以前的填充算法和转换等更有趣。这是一门只在第四年才开设的课程,每两年开设一次,但我已经成功地说服系部让我独立学习同样主题的第四年级课程,并认为这足够了。

独立学习的教授不教授实际的计算机图形学课程,所以虽然他是个聪明人,但这并不是他的领域。因此,大部分问题都留给我自己、一本教材和互联网。就像一个独立学习应该做的那样。 :)

/背景

我的朋友喜欢为娱乐开发游戏系统。我计划使用XNA将他的桌面游戏制作成电脑游戏。

我预见到游戏机制中不会有什么难以克服的挑战,但有一件事我很好奇,那就是大多数游戏是如何保存它们的内容的?我的意思是几个方面,希望我能表达清楚。

以你玩过的任何RPG为例。您可以点击“保存”按钮保存世界、角色信息和其他必要信息。然后稍后可以点击“加载”按钮并将其恢复。

或者是NPC对话的情况。当我撞到853号商贩时,他会随机吐出3个不同的问候语。

还有其他的例子,但它们只是同一个主题的变化。即使是在这两个例子中,似乎也可以使用相同的机制,但是这个机制是什么呢?

我做网页开发已经好几年了,所以我的脑海中自然会跳转到“数据库”!无论出现什么问题,数据库都是解决方案。我可以看到它在这里能够起作用,但是开销似乎相当大。“这是我 6mb 的编译游戏...噢,还有 68mb 的MySQL安装程序。” 或者更糟糕的是,因为我使用 XNA,也许我需要找到一种方法来捆绑SQL Server。

我想也许可以使用XML,但这对我来说也不太合适。如果我想在XBox上运行怎么办?或者Zune呢?(虽然这些并不是我所需要的,但肯定有一种解决方案可以考虑到它们。)

有人知道秘笈吗?或者至少有一些想法吗?

谢谢 Jeff

6个回答

12
有两种主要的游戏存储方式,一种简单,一种复杂。第一种方式只是存储当前关卡、当前得分和少量其他统计数据。这种方式在超级马里奥银河等早期基于模块的游戏中可见。保存的游戏不会恢复您的确切位置,而只是已经完成的关卡。这些保存游戏通常非常简单,需要很少的内存。
第二种方式不仅存储您的整体进度,还存储每一个细节,例如敌人的位置、其当前动画帧等,因此加载保存的游戏将使您停留在准确的位置,并且所有敌人都在原处,而不是回到关卡起点。这些保存游戏往往比其他版本更大,因此通常出现在PC游戏中。
数据库在这两种方案中都没有使用,因为数据库的目的是提供动态查询数据结构的能力,而游戏所需的不是查询各个单独的数据,而是静态存储它们的方法。当保存游戏被加载时,它会完全加载到内存中,从那里开始游戏引擎使用数据进行操作。有一些例外情况,例如MMORPG可能会使用数据库,但单人游戏通常不会使用。
实际存储数据的方式取决于游戏。最常见的似乎是简单的二进制数据格式,因为它们在磁盘空间方面比XML要好得多。在早期的游戏中,这些二进制格式通常是游戏进程内存块的原始转储,因此它们没有任何经过深思熟虑的结构,并且通常会在发布补丁或不同版本的游戏时损坏,在一些现代游戏中仍然如此。XML也可以使用,以及任何其他基于文本的文件格式。
在很大程度上,这更多是一个游戏设计问题而不是编程问题,因为游戏保存的方式可以极大地改变游戏的玩法。简单的方式,只需保存关卡编号和一些统计数据,但要实施它要容易得多,因为只需要几行代码即可。而第二种方式则需要序列化大部分类,对于复杂的游戏来说可能是一个相当棘手的问题,并且可能导致许多微妙的错误。

6

一种方法是使用 .net 序列化。

确保你的游戏状态是一个完全连接的图形,并且该图形中的每个类都标记为可序列化 (带有 SerializableAttribute),然后可以使用普通的 .net 序列化进行保存(和加载)。

你可以查看 Project Xenocide 的代码库(开源 XNA 游戏)来了解它是如何实现的。


谢谢Oded,我会仔细研究的。 - JeffMc
我建议您查看保存游戏屏幕并观察保存起点时会发生什么。 - Oded
序列化是一个很好的起点,但事情变得有点混乱,因为您不想序列化像纹理这样的资源,但必须以聪明的方式保存和重新加载哪些引用。 - Cecil Has a Name

5
您可以使用SQLite数据库和SQLite.NET包装器。我使用过这个,发现它非常简单。整个DLL只有850KB,数据库本身位于单个文件中(需要时创建临时文件)。所以您的用户不应该有问题。
但是您也可以使用一个简单的XML文件或自制的二进制格式。这完全取决于您将如何查询数据以及涉及多少数据。没有一个标准答案。

首先,他说XBox和Zune不是必需品。 其次,SQLite(尽管可能不是SQLite.NET)可以与XBox一起使用(请参阅http://www.mail-archive.com/sqlite-users@sqlite.org/msg17898.html)。 我不确定Zune是否可以使用。 - Matthew Flaschen
1
许多游戏使用SQLite来保存它们的状态。这也是我的首选方式。 - gradbot

3

正如其他人指出的那样,序列化是正确的方式。而Gamasutra刚刚发表了一篇关于data baking的文章。


谢谢,我会仔细阅读那篇文章的。 - JeffMc
我绝对不认为自动序列化是唯一的选择。这会严格限制在.NET框架中(如果以后想要将其移植到其他平台怎么办?),并且使您对格式的控制更少。它还倾向于包含一些冗余数据。 - Matthew Flaschen
@Matthew:如果你说的是一个可以在不同平台之间交换的数据格式,那么这就更加困难了。但如果这是个问题的话,那么使用.NET本身可能不是最好的选择。 - Nikhil
@Matthew:除了编写自己的序列化算法之外,另一种选择是明文或基于文件的数据库,就像你所建议的那样。然而,这种方法可能会变得复杂,因为你需要自己转换格式。虽然具有更多的控制权,但同时也更麻烦。此外,序列化数据并不需要浪费太多空间,例如枚举类型等所需的位数比多个ASCII字符少得多。 - Nikhil
我并不是说“永远不要使用自动序列化”。事实上,我刚在一个项目中使用了它。我只是希望OP意识到其利弊。请注意,序列化的冗余性与二进制/文本无关(至少对于二进制序列化而言)。相反,我主要是指意外序列化缓存、临时数据等常见情况。 - Matthew Flaschen

1

从我有限的游戏开发经验来看,保存游戏时真正使用的存储空间并不多。正如tvanfosson所说,您通常在玩游戏时将大部分内容存储在内存中,因此将状态保存到磁盘上并不是一个问题。

这里是一个简短的例子。假设是单人角色扮演游戏(RPG),如果只需要保存您的角色位置,您可能只需要一个级别号、xyz坐标和面向方向。那只是几个字节。

现在假设您需要保存诸如生命补给、箱子、敌人、角色的生命和拾取的物品等状态/位置。最多可能会有几百个,总共不到10KB。

显然,在更复杂的游戏中情况可能会变得非常复杂。关键是仅存储重建玩家体验所需的内容。许多游戏只允许您在特定位置保存,例如在关卡结束时。在这种情况下,您只需要存储新的级别号以及先前级别的结果(例如剩余生命、拾取的物品)。

即使您允许任意保存点,也可以忽略无法返回的地方/级别的状态。你可能不希望用户在跳跃中保存。

编辑:关于文件格式...使用任何方便的数据类型!XML是一种非常好的处理方式。不确定数据库会有多有效,因为对于RPG,每个数据片段都可能非常不同;你可能最终会得到一堆只有一行的表。

大多数游戏使用自己的二进制文件格式。首先,这可以大大减少存储量。其次,它有助于防止用户通过手动编辑保存游戏来作弊-如果您有像<health value="10"/>这样的XML,很容易将文件编辑为<health value="100"/>。二进制的缺点是它更难调试。


1
我认为《辐射3》的存档大约是3MB... 这是我见过的最大的。但它们存储了所有东西。你杀死的每个人,你扔掉的每个物品都会永远存在那里。在其他游戏中,尸体会在10秒内消失... 疯狂!但离题很远 :D - mpen
你真的不能一概而论保存游戏的大小都很小。正如@Mark所提到的,像《辐射3》(和运行在同一引擎上的《湮灭》)这样的游戏实际上保存了很多东西。因此,引擎使用高效的存储机制变得至关重要。 - Peter Lillevold
但是如果对于一个游戏来说,3MB是“很多”,那实际上很好,因为无论是在内存中还是在磁盘上,3MB都不算多的空间。网络浏览器的缓存至少有这个大小的十倍。就像我之前说的,如果你实施一些规则,比如不能返回到先前的关卡,那么你可以将存储空间减少到几乎为零。 - DisgruntledGoat
真的取决于这3兆是什么。无论是“您上次位置的高分辨率截图”还是“接近20万个带状态的对象列表”,存储格式都非常重要。处理如此多的事物时,数据库引擎开始变得相当可行。我们现在看到很多RPG游戏内容,为什么不利用可以快速处理大量数据的经过验证的数据库平台呢?...《遗忘》肯定会从中受益 :) - Peter Lillevold
其实我刚意识到,对于游戏来说,数据库并不是前进的正确方式。当然,它们可以以惊人的速度检索数据段,但对于游戏来说这并不是你想要的——你需要将整个数据集加载到内存中。如果你有数十兆字节的数据不需要100%的时间访问,那么数据库可能会起作用,否则加载它的成本将太高。 - DisgruntledGoat
也许如果你孤立地看待保存的数据,这个观点是正确的。但是游戏的其他数据呢?那些数据明显匹配“数十兆字节的数据,不需要100%的时间都可以访问”。 如果你将游戏世界数据与保存的数据结合起来,这是在加载存档后的目标视图,就像一个数据库...你可以缩短那些讨厌的加载时间。状态已经到位了。 - Peter Lillevold

0
游戏运行时,我会尽量将与当前上下文相关的所有内容保存在内存中。您的初始化可以以适当的序列化格式保存,并在启动时读取。XML 可以使用,但它有点冗长。自定义的紧凑二进制格式可能更合适。保存状态也是如此。任何需要在加载保存的游戏时重新初始化的对象都应序列化为自定义二进制格式,然后在加载时重新构建。如果遇到了内存问题,一个小的、针对速度优化的自定义数据库将是另一种选择。它可以在安装时预先填充。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接