UnityXML存档与加密

前言

不管开发什么游戏,游戏存档是个必不可少的功能,你可能需要保存玩家的一些信息,比如身上穿戴的装备,玩家角色所处的场景等各种信息,对于存档功能(数据持久化),Unity提供了原生技术Playerprefs,它的优点是理解和使用起来十分简单,缺点是对于大型数据存储时会力不从心,所以本文会介绍如何使用XML来实现游戏存档和存档加密的功能。

编程环境

  • Unity 5.2.2
  • OS X EI Capitan 10.11.6

你将学到什么?

  • 如何使用XML对数据进行序列化和反序列化操作。
  • 如何对数据进行加密和解密操作。
  • 不同平台下文件存放的具体路径和规则。

整体思路解析

数据存储和加密的主要逻辑思路:

  1. 使用XmlSerializer类对需要保存的数据类进行序列化操作,得到一串字符串。
  2. 将得到的字符串使用RijndaeManaged类和ICrytoTransform类进行加密操作,获得加密之后的字符串。
  3. 根据平台类型,确定文件保存的路径。
  4. 使用StreamWriter类将字符串保存到文件中。

数据加载和解密的主要逻辑思路:

  1. 根据存档文件的路径,使用StreamReader类读取文件中的内容(一串加密过的字符串)。
  2. 使用RijndaeManaged类和ICrytoTransform类对读取的文件内容进行解密操作,获得一串解密后的字符串。
  3. 使用XmlSerializer类对解密后的字符串进行反序列化操作,获得具体的游戏数据,并使用数据对游戏中的数据进行转换操作。

一、准备工作

在实现具体保存操作前,我们需要先实现我们要保存的游戏数据,本文使用一个简化的数据,假设我们保存的数据是MyPlayer类,里面记录着以下信息:

  • 玩家的名字
  • 玩家的等级
  • 玩家的武器(包括物品ID,物品名字信息)
  • 玩家的衣服(包括物品ID,物品名字信息)

下面我们就来具体实现这些数据,首先我们新建一个Unity工程,工程名字大家可以自定义,然后我们新建一个C#文件,命名为Item,打开item文件,并进行如下编辑:

image
  • 在Item类中,我们定义了两个公共成员,_itemID和_name,分别代表物品的ID和名字。
  • 然后我们在构造函数里面初始化了物品ID和名称。
  • 最后我们又创建了一个构造函数,可以通过参数来指定Item的id和名字。

接下来我们再新建一个C#文件,命名为MyPlayer,打开Myplayer文件,并进行如下编辑:

image
  • 在MyPlayer类中,我们定义了4个公共成员,他们是_id,_name,_weapon和_clothes,他们分别代表玩家的ID,名字,所拥有的武器和衣服。
  • 然后我们实现了MyPlayer的构造函数,在构造函数里面我们实例化了他的4个公共成员。

二、实现XmlManager的序列化与写入操作

有了需要存储的数据,下面我们可以来实现如何将数据用XML来序列化,并且将序列化的数据写入到文件中。

首先我们新建一个C#脚本,命名为XmlManager,然后打开脚本进行如下编辑:

image
  • 通过上图我们可以看到,在XmlManager脚本中,我们实现了3个方法
  • 在serializeObject方法中我们首先创建MemoryStream对象,这是为了后面我们创建XmlTextWriter类时的准备,因为我们会通过指定流和编码方式来创建XmlTextWriter的实例对象。
  • 接着我们通过指定数据类型创建了一个Xmlserializer的实例对象xs,然后通过调用xs的Serialize方法对传入的pObject进行序列化。
  • 然后我们把xmlTextWriter.BaseStream强制转换成MemoryStream类型,并赋值给mStream。
  • 最后通过调用UTF8ByteArrayToString方法将mStream数据转换成string类型,并返回数据。
  • 在CreateXMl方法中,我们通过传入的参数,指定了文件保存的位置,以及需要保存的具体数据,然后通过StreamWriter类将数据写入到文件中。
  • 在UTF8ByteArrayToString方法中,我们通过UTF8Encoding将byte[]类型数据转换成了String类型。

三、实现GameDataManager保存操作

首先在场景中新增一个空的对象,然后将其命名为DataController,然后在上面挂载一个我们新建的C#脚本GameDataManager

image

然后我们打开GameDataManager脚本进行如下的编辑:

image

编写上图中的代码后,我们回到Unity编辑器,然后运行,之后我们便会发现Project视窗中多了一个名为ZuiData的文件,如下图:

image

然后我们打开ZuiData文件,就会发现里面保存着_myPlayer对象的数据,如下图:

image

到此,我们学会了将游戏数据序列化并写入到文件中的操作了。

四、实现XmlManager的反序列化和读取操作

学会了保存数据后,下一步我们就要来实现读取文件数据,并且将其反序列化,成为我们可以使用的对象。

我们再次打开XmlManager脚本,新增以下代码:

image
  • 首先我们看deserializeObject方法,我们通过传入的参数ty,确定XmlSerializer需要反序列化的类型,然后需要反序列化的内容从string转换成byte[]类型,最后调用xs.Deserialize方法进行反序列化操作,并返回其结果。
  • 接着我们看loadXML方法,有一个参数,是需要读取的文件名称,然后创建一个StreamReader类型的对象,然后调用其方法ReadToEnd进行读取操作,最后返回读取的内容
  • stringToUTF8ByteArrayhasFile就很好理解了,一个是将string类型装换成byte[]类型,一个是通过文件名判断该文件是否存在。

五、实现GameDataManager的读取数据操作

实现了XmlManager的反序列化和读取操作后,我们就可以在GameDataManager中实现将xml的数据读取,并且把这些数据转换成我们需要使用的类型,比如转换成我们_myPlayer的信息。

接下来,我们打开GameDataManager脚本,并新增以下代码:

image
  • 首先在load方法中,先获取文件存储的路径,然后判断文件是否存在,如果不存在,则在后台打印提示信息,接下来,调用xm.loadXML方法读取文件中的数据,读取出来的数据是一段字符串,然后我们在调用xm.deserializeObject方法把数据转换成MyPlayer类型的数据,最后如果数据不为空,我们就把这些数据赋值给_myPlayer对象。
  • pressLoadButton方法是后面我们制作读取按钮时会用的方法,里面主要做了两件事情,一是调用load方法,读取数据,二是调用printData方法打印_myPlayer的部分属性。
  • printData方法中,我们调用Debug.log方法打印出我们想要看的_myPlayer的属性,而这里我们打印的属性,是后面我们修改过具体内容的几个数据,打印出来就是为了查看是否修改成功。

制作读取按钮

有了上面这些方法后,为了在实际演示中,让我们可以看到数据的读取后的改变,我们在项目中新建一个按钮,来触发数据读取的操作。

image

我们在项目中新建一个名为“LoadButton”的按钮,然后将其内容改为“读取数据”,然后我们在按钮的点击逻辑上挂载GameDataManager中的pressLoadButton方法,如下图:

image

实现按钮后,我们打开ZuiData文件,将玩家的ID改为99,玩家名字改为“ZuiPlayer”,武器的名称改为“Eagles”,如下图:

image

修改之后,我们再次打开GameDataManager脚本,把Start方法中的内容全部注释掉,如下图:

image

最后我们回到Unity编辑器,然后运行程序,点击读取数据按钮,然后查看后台打印出来的数据是否与我们修改过后的数据一致,如无意外,效果如下图:

image

到此,我们学会了XML数据的读取和反序列化。

六、对文件数据进行加密和解密

虽然我们现在学会了使用xml进行数据的存储和读取,但是就想我们上面读取操作时一样,我们可以直接通过改写ZuiData文件里面的内容,从而改变游戏的数据,这样对于游戏数据来说是很不安全的,所以我们最好对游戏最终保存的数据进行一些加密操作,这样就无法通过文件直接修改游戏的数据了。

我们再次打开XmlManager脚本,然后新增以下代码:

image
  • 首先我们在前面引入了新的头文件System.Security.Cryptography,我们下面需要用的RijndaelManagedICryptTransform类,都是属于其中。
  • 接下来我们定义了我们加密和解密所需要用的密钥,具体的数字可以自定义,但是必须一共是32位。
  • 然后我们先跳到上图最后的getRijndaelManaged方法,这里面我们主要是创建并定义我们加密和解密的方式,我们定义了一个RijndaelManaged对象,然后设置密钥为_keyArray(也就是我们之前定义的密钥),然后设置对称解密算法的运算模式和填充模式(关于运算模式和填充模式的详细说明,大家可以参见最后面的参考链接),最后我们返回RijndaelManaged的实例化对象。
  • 接下来我们看到encrypt方法,这里面我们主要是把传入的数据,进行加密操作,然后返回加密后的数据,首先我们定义了一个ICryptTransform类型的对象,并且他是加密模式的,然后我们把需要加密的数据类型转换成byte[]类型,调用TransformFinalBlock方法得到加密后的数据,最后将这个数据转换成string类型并返回。
  • 最后我们来看decrypt方法,这里我们主要把传入的数据,进行解密操作,然后返回解密后的数据,首先我们定义了一个ICryptTransform类型的对象,并且他是解密模式的,然后我们把需要解密的数据转换成byte[]类型,调用TransformFinalBlock方法获得解密后的数据,最后将这个数据转换成string类型并返回。

完成了这些主要的方法后,我们还需要对XmlManager脚本进行一些小修改,具体见下图:

image

做完这些改动后,我们再把GameDataManager脚本中的Start方法中的代码注释取消掉,让其恢复作用,最后我们回到Unity编辑器,运行程序,可以看到我们save和load操作都是正常运行的,而这个时候我们再次打开ZuiData文件,就会发现文件中的内容变成了一些乱码,这样就无法通过修改存档来改变游戏的数据了。

image

参考阅读

MSDN 关于RijndaelManaged类的说明
MSDN 关于ICryptoTransform类的说明

补充内容

  • 需要序列化的成员必须是public的,私有的是不会被序列化的,如果某个成员变量不想序列化,有两种方法,一是设置为私有,二是使用[XmlIgnore]修饰,如下图:
image
  • 如果一个成员变量的值为null,序列化时不会记录任何信息。
  • 自定义序列化类,必须要有默认的构造函数(即不带任何参数的构造函数),否则会报错。

作者:Zui
链接:https://www.jianshu.com/p/29e3b50deac1
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,849评论 6 13
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,141评论 0 9
  • 前言 不管开发什么游戏,游戏存档是个必不可少的功能,你可能需要保存玩家的一些信息,比如身上穿戴的装备,玩家角色所处...
    Zui阅读 7,033评论 6 33
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,082评论 1 32
  • JAVA序列化机制的深入研究 对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整...
    时待吾阅读 10,834评论 0 24