浅谈Unity中的存档系统

Unity中的存档系统用于保存和加载游戏的状态、进度和设置。存档系统通常用于实现游戏的保存和载入功能,以便玩家可以在游戏中保存当前状态,然后稍后重新加载。
Unity读档与存档采用的储存方式主要有PlayerPrefs,外部文件,云储存等,存储形式有直接存储数据、序列化(Binary、Xml、Json),自定义序列化格式等,数据存储过程一般需要加密。本文介绍一些常用的数据存档方式。

Player Prefs

Player Prefs是unity引擎内建的主要用来存储玩家偏好设定的一个类,也是用于存储小型游戏数据和偏好设置的简单键值对存储方式。它将数据存储在本地设备上,通常用于保存玩家的设置、解锁状态、得分等小型数据。
Player Prefs使用的是类似字典的存储方式,玩家进行数据存储时,需要输入一个key参数以及一个value参数;获取数据时只要输入键就可以获取到对应的值了。Player Prefs形式存储的数据保存在当前开发平台的注册表中,详细位置可以查看官方文档。

数据存储和读取

数据存储函数:

1
2
3
4
public static void SetString(string Key, string value);
public static void SetInt(string Key, int value);
public static void SetFloat(string Key, float value);
//示例:PlayerPrefs.SetString("PlayerName", playerName);

设置完后需要保存PlayerPrefs的状态:

1
PlayerPrefs.Save();

数据读取函数:

1
2
3
4
5
public static string GetString(string Key);
public static int GetInt(string Key);
public static float GetFloat(string Key);
//示例:string name = PlayerPrefs.GetString("PlayerName", "Guest");
//第二个参数是默认值,如果指定的键不存在或不是string类型,则返回默认值

适用场景和特点

  • 适用场景: 适合保存游戏设置、解锁状态、用户首选项等小型数据。
  • 优点: 简单易用,适用于少量数据,无需额外文件操作。
  • 缺点:数据类型单一,安全性低。
  • 拓展:JsonUtility
    通过它我们可以将任何Unity序列化程序支持的类转成Json对象,而Json就是一个按一定格式书写的字符串,这样我们就可以使用PlayerPrefs.SetString()函数来存储Json字符串了

JSON存储

JSON是一种轻量级的数据交换格式,支持多种主流编程语言,JSON数据可以轻松地在多种不同的语言之间进行转换,适用于保存和加载游戏状态、配置文件、角色属性等复杂的数据结构。而在本质上,JSON是将数据按一定规则和格式书写的文本文件。
以下是在Unity中使用JSON进行数据存储和读取的一般步骤:

序列化对象到JSON

首先,你需要将游戏数据对象转换为JSON格式,以便将其保存到文件或PlayerPrefs中。Unity提供了JsonUtility类来实现这一目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义一个数据结构
[System.Serializable]
public class PlayerData
{
public string playerName;
public int playerScore;
public bool isPlayerAlive;
}

// 创建一个数据对象
PlayerData playerData = new PlayerData();
playerData.playerName = "John";
playerData.playerScore = 1000;
playerData.isPlayerAlive = true;

// 将数据对象转换为JSON字符串
string jsonData = JsonUtility.ToJson(playerData);

保存JSON数据

接下来,你可以将生成的JSON字符串保存到文件或PlayerPrefs中。如果要保存到文件,你可以使用C#的文件操作方法。
如果要将JSON保存到PlayerPrefs中:

1
2
3
// 保存JSON字符串到PlayerPrefs
PlayerPrefs.SetString("PlayerData", jsonData);
PlayerPrefs.Save(); // 确保数据已保存

如果要将JSON保存到文件中:

1
2
3
4
5
6
7
public static void SaveByJson(string saveFileName, object data)
{
var json = JsonUtility.ToJson(data);
//Application.persistentDataPath是unity提供的存储永久化数据的路径,随着发布平台的不同而自动变更
var path = Path.Combine(Application.persistentDataPath, saveFileName);
File.WriteAllText(path, json);
}

读取JSON数据

当需要读取数据时,你可以从文件或PlayerPrefs中检索JSON字符串,然后将其转换回游戏数据对象。

1
2
3
4
5
6
// 从文件中读取JSON字符串
string jsonData = File.ReadAllText("playerData.json");
// 或者从PlayerPrefs中读取JSON字符串
string jsonData = PlayerPrefs.GetString("PlayerData");
// 将JSON字符串转换为数据对象
PlayerData loadedData = JsonUtility.FromJson<PlayerData>(jsonData);

JSON适用场景和特点

  • 优点:
    相对来说更方便人类阅读和编写;
    适用范围广泛,支持多种主流编程语言;
    轻量级,易于网络传输与机器解析和生成;
  • 缺点:
    支持的数据类型有限;
    数据安全性低;
  • 适用范围:
    联网:优秀的网络数据交换载体、云存档;
    本地存储:非敏感而需要大量读取和修改的数据,玩家的偏好设置等。

ToJson详解

对于ToJson函数,我们需要做一些补充了解。ToJson(object obj, bool x)函数会尝试序列化我们传入到第一个参数的对象里面的可序列化数据(第二个参数代表是否将json数据转换成符合阅读习惯的格式),而不是序列化我们传入的这个参数本身,所以传入的参数为一个data实例就行,结构体和类都可以直接序列化(将类标记为[System.Serializable])。
但ToJson的第一个参数object并不能接收所有类型,如果你传入的参数,或者这个参数所包含的数据中,有一些对象是Unity序列化程序所不支持的,那么这些对象就会被忽略。

那么Unity序列化程序支持哪些类型的对象呢?

  • 整数和浮点数:包括int、float、double等。
  • 布尔值:bool类型。
  • 字符串:string类型。
  • 枚举:枚举类型,如enum。
  • 结构体:用户自定义的结构体。
  • 数组:数组类型,如int[]、string[]等。
  • 表和集合:一些集合类型,如List<T>Dictionary<K, V>等。
  • Unity内置类型: Unity的Vector2、Vector3、Quaternion、Color等类型。
  • 自定义类和对象:你可以自定义类,并使用[Serializable]属性来标记它们以进行序列化。
  • 接口:接口可以在Unity中被序列化,但只有具体实现的类的实例可以被正确序列化。
  • GameObject和Component: Unity中的GameObject和Component也可以被序列化,但需要注意它们的序列化与持久性存储有关,如Prefab和Scene。
  • 序列化嵌套对象: 你可以嵌套序列化对象,即一个序列化对象中包含另一个序列化对象。

Unity的序列化系统在序列化和反序列化对象时具有一些限制和规则,包括:

  • 字段访问级别:只有public的字段可以被Unity的序列化系统正确序列化和反序列化。这些字段通常是被标记为 [Serializable] 特性的字段。
  • 字段类型限制:Unity的序列化系统支持一系列常见的数据类型,如整数、浮点数、字符串、布尔值、枚举、结构体等。对于更复杂的数据类型,如字典、多维数组、泛型、委托等,通常需要进行转换或者使用其他方法来实现序列化和反序列化。
  • MonoBehaviour序列化:Unity的MonoBehaviour脚本中的字段,即使是私有字段,也可以被Unity序列化系统正确序列化和在Inspector中编辑,因为MonoBehaviour继承了Unity的序列化接口。
  • 非静态字段:非静态字段可以被序列化,但静态字段通常不会被序列化。
  • 只读和常量字段:只读和常量字段不能被序列化,因为它们的值不能被更改。
  • 嵌套对象:Unity支持嵌套对象的序列化,允许你在一个序列化对象中包含其他序列化对象。

如果需要在Unity中存储不支持序列化的数据类型,你通常需要将其转换成支持Unity序列化的类型,或者手动将其数据转换成一种支持的数据结构。对于字典、泛型集合等复杂数据结构,你可能需要编写自定义的序列化和反序列化逻辑。Unity的JsonUtility等工具可以帮助你将数据转换成JSON格式以进行存储和加载。

怎样快速知道一个类型的数据能否被序列化存储?

将这些数据标记为公有,或添加[Serializable]属性,能在Inspector窗口观察到,就说明可以进行序列化存储。

Binary Serialization

声明:二进制序列化有重大风险,不建议将其用于数据处理,谨慎使用,详情见
使用 BinaryFormatter 和相关类型时的反序列化风险

XML

在Unity中使用XML序列化和反序列化需要使用System.Xml.Serialization命名空间提供的类。下面是使用XML序列化的基本步骤:

创建可序列化的数据类

首先,你需要创建一个用于存储数据的类,该类需要被标记为 [System.Serializable] 特性,并包含公共字段或属性以保存数据。

1
2
3
4
5
6
7
8
using System.Xml.Serialization;

[System.Serializable]
public class PlayerData
{
public string playerName;
public int playerScore;
}

序列化数据到XML

使用XmlSerializer类将数据对象序列化为XML格式,需要提供一个FileStream或TextWriter来将XML数据写入文件或其他数据源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System.Xml;
using System.Xml.Serialization;
using System.IO;

public void SerializeToXML()
{
PlayerData player = new PlayerData();
player.playerName = "John";
player.playerScore = 1000;

XmlSerializer serializer = new XmlSerializer(typeof(PlayerData));
using (FileStream stream = new FileStream("playerData.xml", FileMode.Create))
{
serializer.Serialize(stream, player);
}
}

反序列化数据

若要读取和还原XML数据,可以使用相同的XmlSerializer类将XML数据反序列化为对象。

1
2
3
4
5
6
7
8
9
public PlayerData DeserializeFromXML()
{
XmlSerializer serializer = new XmlSerializer(typeof(PlayerData));
using (FileStream stream = new FileStream("playerData.xml", FileMode.Open))
{
PlayerData player = (PlayerData)serializer.Deserialize(stream);
return player;
}
}

注意事项:
确保你的数据类和字段/属性都被标记为 [System.Serializable]。XmlSerializer使用属性访问来序列化和反序列化数据,因此需要使用公共字段或属性。要保存和加载XML数据,需要使用文件操作或其他数据源。在示例中,我们使用了FileStream来保存和加载XML文件。

XML序列化是一种强大的数据存储和交换方式,可用于保存和加载游戏状态、配置文件和其他复杂的数据结构。但需要注意,XML文件通常相对较大,因此在性能和存储空间方面要有所考虑。