数据持久化就是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称,简单来说就是将游戏数据从内存中存储到硬盘,或从硬盘中读取数据到内存
一、基础知识
1.1 Json文件格式
如何在Json文档中使用注释(注意在C#和Unity中默认不支持Json注释)
双斜杠 和 斜杠加星号包裹注释的Json文档格式,其后缀为.jsonc,不是 .json
在Json中实现一个实例对象,需要用大括弧包裹
如一个学生的实例:{"name":"小明","age":8,"isBoy":true}
在Json中实现一个数组,需要用中括弧包裹
如一个id数组:"ids": 1,2,3,4
在Json中如果存在字典,字典的键仍然要加双引号,其中字典可以当作一个类对象
如一个包含名字,学号的字典:{"name" : "XiaoMing", "Id" : "230901" }
练习题:使用Json语法描述 如下的类

{
"name" : "XiaoMing",
"atk" : 12,
"def" : 10,
"moveSpeed": 4.5f,
"roundSpeed": 3.1415926535,
"weapon": {"id": 101, "num": 1 },
"listInt": [1, 2, 3, 4, 5],
"itemList":
[
{"id": 201,"num": 10},
{"id": 202,"num": 5}
],
"itemDic":
{
"1": {"id": 301, "num": 3},
"2": {"id": 302,"num": 7}
},
"itemDic2": {
"sword": {"id": 401,"num": 1},
"shield": {"id": 402, "num": 1}
}
}
对于非公有字段,序列化与反序列化时需要特性\[\],这里没有添加特性,所以为了避免反序列化出错, 不添加非公有字段对应的Json数据
1.2 C# 读取存储Json文件
1.2.1 JsonUtility
1.JsonUtility是Unity中用于解析Json文档的静态类
2.作用:讲内存中的数据对象序列化为Json格式的字符串或将Json字符串反序列化为类对象
3.对JSON字符串的存读:
将json字符串存到指定路径: File.WriteAllText(文件路径,Json字符串)
#region 将Json字符串存到指定路径
// 语法:File.WriteAllText(文件路径,Json字符串)
// 如 1.1中的Json文档存储到 persistentDataPath中
string json = @"
{
""name"" : ""XiaoMing"",
""atk"" : 12,
""def"" : 10,
""moveSpeed"": 4.5,
""roundSpeed"": 3.1415926535,
""weapon"": {""id"": 101, ""num"": 1 },
""listInt"": [1, 2, 3, 4, 5],
""itemList"":
[
{""id"": 201,""num"": 10},
{""id"": 202,""num"": 5}
],
""itemDic"":
{
""1"": {""id"": 301, ""num"": 3},
""2"": {""id"": 302,""num"": 7}
},
""itemDic2"":
{
""sword"": {""id"": 401,""num"": 1},
""shield"": {""id"": 402, ""num"": 1}
}
}";
File.WriteAllText(Application.persistentDataPath + "/Text.json",json);
Debug.Log("保存成功!路径:" + Application.persistentDataPath + "/Text.json");
EditorUtility.RevealInFinder(Application.persistentDataPath);
// 注意:这个存储路径不会创建新文件夹,所以在存储时需要确保文件路径是否存在
#endregion 从指定路径取出json字符串
运行的结果为


从指定路径取出json字符串: File.ReadAllTest(文件路径)
#region 从指定路径取出json字符串
// 语法: File.ReadAllTest(文件路径)
// 如将上面的Json字符串从指定文件路径中读取出来
string jsonStr = File.ReadAllText(Application.persistentDataPath + "/Text.json");
Debug.Log(jsonStr);
#endregion
读取到的结果为:

4.使用JsonUtlity进行序列化(讲内存中的数据存到硬盘上)
方法: string jsonStr = JsonUtlity.ToJson(类对象实例)
缺点:字典和引用类型的null值无法存储,且float类型变量存储时会有误差
对于非公有字段和自定义类内部使用时,要添加特性System.Serializable才可以序列化与反序列化
#region 使用JsonUtlity进行序列化
// 语法:JsonUtlity.ToJson(类对象实例),返回值为Json字符串
#region 创建一个类对象实例
PlayerInfo player = new PlayerInfo();
player.name = "XiaoMing";
player.atk = 12;
player.def = 10;
player.moveSpeed = 4.5f;
player.roundSpeed = 3.1415926535;
player.weapon = new Item(101, 1);
player.listInt = new List<int>() { 1, 2, 3, 4, 5 };
player.itemList = new List<Item>()
{
new Item(201, 10),
new Item(202, 5)
};
player.itemDic = new Dictionary<int, Item>()
{
{ 1, new Item(301, 3) },
{ 2, new Item(302, 7) }
};
player.itemDic2 = new Dictionary<string, Item>()
{
{ "sword", new Item(401, 1) },
{ "shield", new Item(402, 1) }
};
#endregion
string jsonStr2 = JsonUtility.ToJson(player);
Debug.Log(jsonStr2);
#endregion
运行结果为:

5.使用JsonUtlity进行反序列化(将硬盘中的数据读取到内存)
方法: JsonUtlity.FromJson(Json字符串,Type type)
使用:首先通过File.ReadzText(路径)读取json字符串,然后利用该方法进行反序列化
重载: JsonUtility.FromJson<目标类型>(Json字符串),通过泛型可以直接得到对应类的实例
注意:
-
当json数据缺少时,并不影响反序列化的运行,这里会将其设置为默认值
-
无法读取数据集合,需要将其包裹为对象,且文本格式为UTF-8
#region 使用JsonUtlity进行反序列化
// 语法:JsonUtlity.FromJson(Json字符串,Type type),返回值为Object类型
// 重载:JsonUtility.FromJson<目标类型>(Json字符串)
// 在使用前需要先得到Json字符串,可以通过File.ReadAllTest(文件路径) 从硬盘中读取
PlayerInfo p1 = JsonUtility.FromJson(jsonStr2);
PlayerInfo p2 = JsonUtility.FromJson(jsonStr2,typeof(PlayerInfo)) as PlayerInfo;#endregion
1.2.2ListJson
. 获取途径: LitJson官网:https://litjson.net/ => Source(这里点击后会调转GitHub) =>Releses => 下载压缩包 => 将压缩包解压,并将src中LitJson中的C#脚本导入到Unity,之后就可以正常使用LitJson

1.注意事项:
- 不能序列化私有字段
- 可以序列化字典,但因为在json中字典的键会加上双引号,所以当存在整形的key时,会被当做string序列化
- LitJson可以保存null
- 在反序列化时,要确保类中需要有无参构造函数
- LitJson可以直接读取数据集合
2.使用LitJson进行序列化:
方法:JsonMapper.ToJson(类对象实例),返回值为json字符串
#region 使用ListJson进行序列化
// 语法:JsonMapper.ToJson(类对象实例),返回值为Json字符串
// 如,将PlayerInfo的实例对象player进行序列化得到playerJson
string playerJson = JsonMapper.ToJson(player);
Debug.Log("通过 { JsonMapper.ToJson(player) } 得到的结果为 " + playerJson);
#endregion

3.使用LitJson进行反序列化:
方法:JsonMapper.ToObject(json字符串),返回值为JsonData类对象
JsonDat是LitJson提供的类对象,可以用键值对的形式访问其中的内容
#region 使用LitJson进行反序列化
// 语法:JsonMapper.ToObject(Json字符串),返回值为JsonData类对象
JsonData jsonData = JsonMapper.ToObject(playerJson);
// 这里可以直接通过键来得到其中的信息,如获取玩家信息的名字
string pName = jsonData["name"].ToString();
// 还原为原本的类对象:通过泛型 JsonMapper.ToObject<泛型>(Json字符串)
PlayerInfo p4 = JsonMapper.ToObject<PlayerInfo>(playerJson);
#endregion
1.2.3 JsonUtility和LitJson对比
1.相同点:
- 都是用于json进行序列化和反序列化的静态类工具
- 处理的json文档格式必须是UTF-8
2.不同点:
- JsonUtlity是Unity自带的静态工具类,LitJson是第三方需要引用命名空间
- JsonUtlity使用自定义类需要添加特性,LitJson不需要特性可以直接序列化
- jsonUtlity可以序列化非公有字段但要添加特性,LitJson不需要也不支持非公有字段序列化
- JsonUtility不支持字典,LitJson支持但键只能是字符串
- JsonUtility不能直接将数据反序列化为数据集合(数组字典),LitJson可以
- JsonUtility对自定义类不要求有无参构造,LitJson需要
- JsonUtility存储空对象时会存储默认值而不是null,LitJson会存null
二、实践内容
1.定义 Json 序列化 / 反序列化类型枚举JsonType,包含JsonUtlity和LitJson两种方式,用于切换序列化工具
public enum JsonType
{
JsonUtlity,
LitJson,
}
2.构建JsonMgr数据管理类,采用单例模式设计,保证全局唯一实例,方便外部统一调用
3.实现SaveData存储方法:
-
确定数据存储路径为
Application.persistentDataPath -
根据传入的
JsonType选择对应工具,将内存中的对象序列化为 Json 字符串 -
通过
File.WriteAllText将 Json 字符串写入硬盘指定路径//存储Json数据进行序列化 => 将数据从内存中存入硬盘
public void SaveData(object data, string fileName, JsonType type = JsonType.LitJson)
{
//存储的路径
string path = Application.persistentDataPath + "/" + fileName + ".json";
string jsonStr = ""; //json字符串
switch (type)
{
case JsonType.JsonUtlity:
jsonStr = JsonUtility.ToJson(data);
break;
case JsonType.LitJson:
jsonStr = JsonMapper.ToJson(data);
break;
}
//将Json字符串存储到指定路径中
File.WriteAllText(path, jsonStr);
}
4.实现LoadData读取方法:
-
优先从
Application.streamingAssetsPath路径查找目标 Json 文件 -
若路径不存在,切换到
Application.persistentDataPath路径二次查找 -
若两个路径均无文件,返回对应类型的默认实例对象
-
若存在文件,读取 Json 字符串,根据
JsonType选择对应工具反序列化为对象并返回public T LoadData
(string fileName, JsonType type = JsonType.LitJson) where T : new()
{
//读取的路径
string path = Application.streamingAssetsPath + "/" + fileName + ".json";// 这里如果读取的是两个不同类型的,如配置表 和玩家存档 // 配置表一般放在streamingAssets中 // 玩家存档放在 persistentDataPath // 所以这里要是在streamingAssets中找不到,就到persistentDataPath中找 if (!File.Exists(path)) { path = Application.persistentDataPath + "/" + fileName + ".json"; } //先在指定路径读取json字符串 if (!File.Exists(path)) return new T();//如果没有在指定路径找到就直接返回默认的实例对象 //通过json字符串进行反序列化 string jsonStr = File.ReadAllText(path); T data = default(T); switch (type) { case JsonType.JsonUtlity: data = JsonUtility.FromJson<T>(jsonStr); break; case JsonType.LitJson: data = JsonMapper.ToObject<T>(jsonStr); break; } //将得到的实例对象返回 return data;}
5.进行打包,这里我们写了JsonMgr脚本,但其运行时需要依赖LitJson相关脚本,所以需要一起打包
