Unity进阶技巧 - XML存档与加密

前言

不管开发什么游戏,游戏存档是个必不可少的功能,你可能需要保存玩家的一些信息,比如身上穿戴的装备,玩家角色所处的场景等各种信息,对于存档功能(数据持久化),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文件,并进行如下编辑:

Item代码

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

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

MyPlayer代码

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

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

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

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

XmlManager关于序列化和写入的代码

  • 通过上图我们可以看到,在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

DataController对象与其挂载的脚本

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

GameDataManager的保存和获取路径代码

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

**Project**视窗中**ZuiData**文件

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

ZuiData的文件内容

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

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

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

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

XmlManager中的反序列化和读取代码

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

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

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

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

GameDataManager的读取和打印代码

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

制作读取按钮

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

读取数据按钮

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

挂载pressLoadButton方法到LoadButton上

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

修改ZuiData文件内容

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

注释Start方法中的代码

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

最终效果图

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

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

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

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

XmlManager中加密和解密的代码

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

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

XmlManager中的小修改

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

加密后的ZuiData文件内容

参考阅读

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

补充内容

  • 需要序列化的成员必须是public的,私有的是不会被序列化的,如果某个成员变量不想序列化,有两种方法,一是设置为私有,二是使用[XmlIgnore]修饰,如下图:

_clothes就不会被序列化

  • 如果一个成员变量的值为null,序列化时不会记录任何信息。
  • 自定义序列化类,必须要有默认的构造函数(即不带任何参数的构造函数),否则会报错。



作者:Zui
链接:https://www.jianshu.com/p/29e3b50deac1
來源:简书
 

<think>好的,我现在要为用户介绍Unity中的PlayerPrefs的优缺点。首先,我需要回忆PlayerPrefs的基本概念和用途。PlayerPrefs是Unity提供的一个用于本地存储玩家偏好设置的类,通常用来保存简单的数据,比如音量设置、游戏进度等。 接下来,我得考虑用户可能的背景。用户可能是刚开始学习Unity的新手,想了解在什么情况下使用PlayerPrefs合适,或者在选择存储方案时需要考虑的因素。因此,我需要明确说明其优缺点,帮助用户做出合适的选择。 关于优点,首先想到的是简单易用。PlayerPrefs的API非常简洁,比如SetInt、GetString等方法,不需要复杂的配置。这对于新手来说非常友好,可以快速实现数据存储功能。其次,跨平台支持也是一个优点,Unity会自动处理不同平台(如Windows、Mac、iOS、Android)的存储路径和方式,开发者不需要关心底层实现。另外,轻量级适用于小数据量,比如保存设置或进度,不会对性能造成太大影响。 然后是缺点。数据安全问题,PlayerPrefs存储的是明文数据,容易被玩家修改或破解,特别是PC端。如果存储敏感信息,比如玩家分数或游戏内货币,这会带来风险。存储容量限制也是一个问题,不同平台对PlayerPrefs的大小有限制,比如iOS上每个key不能超过1MB,总数据量过大可能导致问题。数据结构单一,只能存储基本类型(int、float、string),复杂的数据结构需要序列化处理,不如其他存储方案灵活。此外,同步问题,PlayerPrefs在写入时可能会延迟,特别是在移动设备上,频繁写入可能引发数据不同步或丢失。 用户可能想知道替代方案,所以应该简单提到其他存储方式,比如使用文件存储(JSON、XML)、SQLite数据库或Unity的ScriptableObject。同时,需要给出使用建议,帮助用户根据具体需求决定是否使用PlayerPrefs。 需要检查是否有遗漏的点,比如不同平台的具体限制,或者是否有其他安全隐患。另外,确保语言口语化,避免技术术语过多,让新手也能理解。最后,组织内容结构,先优点后缺点,再使用建议,保持逻辑清晰。</think>PlayerPrefs 是 Unity 引擎提供的本地数据存储方案,适用于简单场景。以下是它的优缺点分析及使用建议: --- ### **✅ 优点** 1. **简单易用** - 三行代码即可实现存储/读取: `PlayerPrefs.SetInt("Score", 100);` `int score = PlayerPrefs.GetInt("Score");` `PlayerPrefs.Save();` 2. **跨平台支持** - 自动适配不同操作系统存储路径(Windows注册表、iOS Plist文件、Android SharedPreferences等) 3. **轻量级存储** - 适合保存小数据:音量设置、语言选项、最后一关解锁进度等 --- ### **❌ 缺点** 1. **数据不安全** - 明文存储(如 Windows 存储在注册表 `HKEY_CURRENT_USER\Software\[公司名]\[产品名]`),玩家可直接修改 *示例:把血量值 `"HP":100` 改成 9999 轻而易举* 2. **存储容量限制** - iOS 限制单个 Key 不超过 1MB - Android 总数据量超过 1MB 可能引发卡顿 3. **仅支持基础类型** - 只能存储 `int/float/string`,存储类对象需手动序列化成 JSON 字符串 4. **同步延迟风险** - 移动端调用 `Save()` 时可能因系统延迟导致数据未及时写入 --- ### **🛠 使用建议** - **推荐场景**:保存非关键性设置(如分辨率、背景音乐开关) - **规避场景**:存档、玩家货币等敏感数据存储(建议改用加密文件或数据库) - **进阶技巧**: ```csharp // 存储复杂数据(需自行序列化) string json = JsonUtility.ToJson(playerData); PlayerPrefs.SetString("PlayerData", json); // 加密示例(Base64简单处理) string encrypted = Convert.ToBase64String(Encoding.UTF8.GetBytes(json)); PlayerPrefs.SetString("EncryptedData", encrypted); ``` --- ### **🔀 替代方案** | 方案 | 适用场景 | |---------|---------| | **JSON/XML 文件** | 需要存储复杂对象结构 | | **SQLite** | 需要关系型数据查询(如排行榜历史记录) | | **ScriptableObject** | 存储只读配置数据(如物品属性表) | | **云存储** | 需要跨设备同步(如 PlayFab、Firebase) | 根据数据安全性要求、复杂度和跨平台需求选择合适的存储方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值