游戏状态保存/加载?


12

什么是保存游戏状态(配置文件),数据库文本文件的最常用方法或算法,加密方式以及相关内容。

我已经看到Caesar IV使用了mySQL。

有什么建议。

Answers:


20

我敢肯定,它通常是采用专有(二进制)格式完成的,只需将您使用的任何游戏状态结构的每个变量写入文件中,或将该文件读回到结构的实例中即可。例如,在C ++中,您可以将对象像字节数组一样对待,只需将其fwrite()放入文件中,或从文件中读取fread()并将其转换回其对象形式。

如果您使用的是Java或其他语言,则可以选择使用序列化来简化任务。Java的解释在此答案的底部。当然,其他语言(比C ++更高的语言)也都有自己的序列化库或内置函数,您应该选择使用它们。无论序列化例程的效率如何低下,如今的硬盘空间都很便宜,尤其是在游戏状态不应该太大的情况下,这会阻止您编写和维护自己的序列化例程。

您绝对不应使用MySQL(只需阅读本评论的第一句话)。如果出于某种原因确实需要关系数据库,请使用SQLite。它是一个轻量级的数据库系统,并且存在于单个数据库文件中。但是对于大多数游戏而言,关系数据库并不是走的路,而尝试使用它们的公司通常最终会将它们用作键值查找表,而不是真正的关系数据库。

对本地磁盘文件的任何形式的加密都是模糊的 ; 程序解密后,任何黑客都只会嗅探数据。我个人反对这种事情,尤其是单人游戏。我的观点是,游戏所有者(付费客户,请注意,您可以)可以随意入侵游戏。在某些情况下,它可以带来更大的社区感,并且可以为您的游戏开发“ mod”,从而吸引更多的客户。我想到的最新示例是最近发布的《我的世界》中的Portal mod。它在互联网上的游戏新闻网站上发布,您可以打赌它推动了Minecraft的销售。


如果由于某种原因您真的疯狂到像我一样使用Java进行游戏开发,以下是Java序列化的简要说明:

将所有游戏状态数据保存在一个类中,通过实现使该类可序列化Serializabletransient如果将特殊的实例化变量混合到该类中(临时对象未序列化),则使用关键字,并仅用于ObjectOutputStream写入文件和ObjectInputStream从文件读取。而已。


2
呵呵,巫师没有加密他们的数据。在十六进制编辑器中打开保存游戏文件非常容易,将其弄乱并得到所有裸露的小鸡卡...哦,天哪,对不起!
安东尼2010年

我已经使用.NET二进制序列化来保存游戏,并且C#可能比Java稍微更合理的平台来开发游戏。我喜欢它如何自动保存整个对象图。过去要困难得多。当然,现在存在排除诸如纹理之类的不必要碎片的挑战,但这并不是一件大事。
BlueMonkMN 2010年

我不同意C#在开发游戏方面比Java 更为流行。更为合理的做法意味着一定的主观性,作为XNA和Java的开发人员,我更喜欢Java。如果愿意,也可以随意列出C#中的序列化说明,我从没做过,但是我可以将其编辑为答案。Java的序列化确实保存了整个对象图,变量上的'transient'关键字排除了不必要的部分,例如纹理;任何非临时成员都必须是可序列化的,否则您可以编写一个自定义writeObject方法来解决该问题。
莉吉特

Python的pickle也会为您保存整个对象图,并且易于使用。当需要进行反序列化时,您会得到完全水合的对象,就像馅饼一样容易:docs.python.org/library/pickle.html
drhayes 2010年

使用内置二进制格式化程序时,从C#/。NET的序列化中排除某些字段就像用[NonSerialized]属性修饰它们一样简单。但是,容易忽略的是,编译器生成的事件(即,没有自定义添加/删除运算符的任何事件)都会创建一个后备字段来存储其订户。因此,很容易在序列化对象图中意外地(并且在不知不觉中)包括事件处理程序。要解决此问题,您需要向事件声明中添加NonSerialized属性,但是您需要添加“ field:”限定符:[field: NonSerialized]
Mike Strobel,2010年

3

如果您正在编写自己的代码(例如使用C ++),则可以使用二进制文件。二进制文件通过混淆为您提供了一种加密形式。但是,正如互联网上记录的那样,这是一种非常薄弱的​​安全性。

或者,如果您想要人类可读的解决方案,则可以使用RapidXML之类的东西。

如果您使用某种框架,请查看它的文件支持功能。高温超导


0

我见过的大多数游戏都只使用手写代码从二进制文件读取/写入。通常,光盘上的格式将完全是对象的内存布局的副本,因此加载只是:

  1. 将文件读入内存。
  2. 将指针强制转换为对象类型的缓冲区。

它的速度很快,但要维护,针对特定平台且缺乏灵活性是一项繁琐的工作。

最近,我看到一些游戏使用SQLite。我与之交谈的人似乎都喜欢。


0

在其他一些答案中提到了序列化,我同意这是一个合理的解决方案。它失败的一种方法是版本控制。

假设您发布了游戏,然后人们开始玩并创建了一些保存游戏。然后,在以后的补丁中,您要修复错误或添加一些功能。如果您使用简单的二进制序列化方法,并且向类中添加了成员​​,则当客户安装补丁时,序列化可能与旧的保存游戏不兼容。

因此,无论使用哪种方法,请确保在首次发布游戏之前就考虑到这个问题!如果客户在应用补丁程序后必须重新开始,他们将不满意。

避免这种情况的一种方法是让每种基本数据类型实现Save(version)和Load(version)方法,这些方法足够聪明,可以知道要为每个游戏版本保存和加载哪些数据。这样,您可以支持保存游戏的向后兼容性,并且如果用户尝试从比其正在运行的新版本的游戏中加载保存游戏,则可以优雅地失败。


1
快速说明:为游戏的每个版本实现“保存”和“加载”功能迅速变成了维护的噩梦。我发现更好的解决方案是嵌入一个版本号,为游戏的当前版本编写“保存/加载”功能,并从最新的非当前版本编写一个“转换”功能到当前版本。然后,只需保留所有转换功能即可。尝试加载具有十个版本的旧游戏将依次运行十个转换功能,然后执行本机“加载当前游戏版本”功能。
ZorbaTHut 2010年

长期维护要容易得多-保持一系列“加载每个以前的游戏文件”功能很快就会变成多项式时间操作。
ZorbaTHut 2010年

在我研究过的游戏中,它并不那么复杂-而不是保留加载/保存方法的多个副本,一个方法使用版本号来确定要读取和写入的字节。如果您不只是编写结构,而是在原始数据类型级别(例如ReadByte / ReadFload / etc)上实现加载和保存为操作,则这将变得更加容易。然后,如果添加新成员,则可以执行类似if(version> 10){someNewByte = ReadByte();的操作。}以这种方式实施也使支持不同的字节序平台变得更加容易。
kevin42

避免改进版本控制的另一种方法是避免对象的序列化/反序列化方法直接从流中写入/读取单个值。相反,对象可以使用键/值对将其拥有的数据写入属性包,然后将其写出到流中。反序列化期间,对象可以按名称读取包装袋中的值。但是,除了建议像kevin42这样的特定版本号检查外,您还可以具有关于如何处理缺失值的规则(即,在袋中没有所需值时使用标准默认值)。
Mike Strobel,2010年

0

如果您可以使用与存储关卡相同的格式,那将是一个好处。

例如,像Peggle这样的游戏可能具有初始棋盘布局,球数,当前得分(初始棋盘为0)等。然后,保存的游戏可以使用完全相同的格式。

如果您使用相同的格式,那么保存游戏和关卡加载可以共享代码,使您的工作更加轻松!

对于具有很大地图文件的游戏,保存游戏可以通过引用包括地图文件。初始级别的文件甚至可能做同样的事情,即使故事由于某种原因使角色回到同一位置,也使返回地图更加容易,除了某些NPC已死并且尸体全部躺在过度。

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.