Unity中常用的序列化方式有三种,二进制序列化,xml,json
二进制序列化因为不能维护字段,通常用于图片,音频,视频等文件的传输
Xml相比Json有阅读直白的优势,通常用于配置表
Json效率比Xml高,常用于网络传输
Unity还提供了PlayerPrefs类进行数据持久化,只适用于一些小游戏.
Xml序列化与反序列化
代码
using System;
using System.IO;
using System.Xml.Serialization;
using UnityEngine;
public class SerializeTool {
/// <summary>
/// xml序列化
/// </summary>
/// <param name="path"></param>
/// <param name="obj"></param>
public static void XmlSerialize(string path, object obj) {
try
{
if (File.Exists(path))
{
File.Delete(path);//已经存在同名文件就删除
}
//创建新的文件 using语法可以在范围结束后自动调用Dispose方法
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
{
//设置utf8编码 防止乱码
using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
{
XmlSerializer xs = new XmlSerializer(obj.GetType());
xs.Serialize(sw, obj);//序列化
}
}
}
catch (Exception e)
{
Debug.Log(path + ":" + e);
}
}
/// <summary>
/// xml反序列化
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="path"></param>
/// <returns></returns>
public static T XmlDeserialize<T>(string path) where T :class {
T t = default(T);
try
{
//using语法可以在范围结束后自动调用Dispose方法
using (FileStream fs = new FileStream(path,FileMode.Open,FileAccess.ReadWrite,FileShare.ReadWrite))
{
//反序列化
XmlSerializer xs = new XmlSerializer(typeof(T));
t = (T)xs.Deserialize(fs);
//fs.Dispose();
}
}
catch (Exception e)
{
Debug.Log(path + ":" + e);
}
return t;
}
}
调用一下代码生成xml文件测试,觉得exsl给策划,然后转回xml作为物品道具之类的属性表,非常合适.
<?xml version="1.0" encoding="ISO-8859-1"?>
<StarData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<position>
<x>104.39492</x>
<y>110.7</y>
<z>101.098129</z>
</position>
<rotation>
<x>0</x>
<y>0</y>
<z>0</z>
<w>1</w>
<eulerAngles>
<x>-0</x>
<y>0</y>
<z>0</z>
</eulerAngles>
</rotation>
<entityName>Star(Clone)147</entityName>
<speedX>22.6322365</speedX>
<speedY>-5.658059</speedY>
<speedZ>-56.58059</speedZ>
<lowestHeight>35</lowestHeight>
<duration>2</duration>
</StarData>
xml标签
此外xml有一些标签可供选择,会对生成的xml格式有一些影响
[XmlElement(“testString”)]//设置属性名字
[XmlAttribute(“testString”)]//设置该属性为特性
[XmlType(“testString”)]//打在类名前,设置类的名字
[XmlArray(elementName: “testString”)]//设置列表名
[XmlArrayItem(elementName: “testString”)]//设置列表中的元素名
[XmlIgnore]//忽略该字段
Json
目前比较流行的,效率高的json框架是litjson.
litjson如果想使用自动序列化/解析时,有几个需要注意的地方
1.它不支持float类型数据
2.字典第一个数据类型必须为string
3.类与js串上的字段必须完全一致,导致了无法更新字段
不过我们可以为每一个类编写序列化/反序列化方法,缺点是工作量有点大.
介于以上种种原因,我们使用litjson为每一个类编写序列化和反序列化方法
代码
我们将json序列化和反序列化的方法封装成下面的格式.
注意不要这样打log,因为sr.ReadToEnd()中的值只能读取一次,下一次sb中就空值了
Debug.Log(sr.ReadToEnd());
StringBuilder sb = new StringBuilder(sr.ReadToEnd());//读取全部数据
/// <summary>
/// json序列化到本地
/// </summary>
/// <param name="path"></param>
/// <param name="json"></param>
public static void JsonSerialize(string path, string json)
{
try
{
if (File.Exists(path))
{
File.Delete(path);//已经存在同名文件就删除
}
//创建新的文件 using语法可以在范围结束后自动调用Dispose方法
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
{
//设置utf8编码 防止乱码
using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
{
sw.WriteLine(json);
}
}
}
catch (Exception e)
{
Debug.Log(path + ":" + e);
}
}
/// <summary>
/// 读取json文件
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static JsonData JsonDeserialize(string path)
{
try
{
//using语法可以在范围结束后自动调用Dispose方法
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
using (StreamReader sr = new StreamReader(fs, Encoding.UTF8))
{
StringBuilder sb = new StringBuilder(sr.ReadToEnd());//读取全部数据
JsonData jsonData = JsonMapper.ToObject(sb.ToString());//获取jsonData
return jsonData;
}
}
}
catch (Exception e)
{
Debug.Log(path + ":" + e);
}
return null;
}
我们在基类中定义两个方法,一个用于获取完整的json序列化,一个用于续写序列化方法
再定义一个反序列化的方法
public abstract class BaseData {
public Vector3 position;//物体的位置
public Quaternion rotation;//物体的旋转
public string entityName;//实体的名字
/// <summary>
/// 获取序列化js串
/// </summary>
/// <returns></returns>
public string GetJson() {
StringBuilder sb = new StringBuilder();
JsonWriter jw = new JsonWriter(sb);
jw.WriteObjectStart();//开始写入
ToJson(jw);
jw.WriteObjectEnd();//结束写入
return sb.ToString();
}
/// <summary>
/// 序列化方法 可续写
/// </summary>
/// <param name="jw"></param>
public virtual void ToJson(JsonWriter jw) {
if (jw==null)
{
throw new Exception("null");
}
jw.WritePropertyName("position");//写入第一个属性
jw.WriteObjectStart();//开始写入集合
jw.WritePropertyName("x");
jw.Write(position.x);
jw.WritePropertyName("y");
jw.Write(position.y);
jw.WritePropertyName("z");
jw.Write(position.z);
jw.WriteObjectEnd();//结束写入集合
jw.WritePropertyName("rotation");
jw.WriteObjectStart();//开始写入集合
jw.WritePropertyName("w");
jw.Write(rotation.w);
jw.WritePropertyName("x");
jw.Write(rotation.x);
jw.WritePropertyName("y");
jw.Write(rotation.y);
jw.WritePropertyName("z");
jw.Write(rotation.z);
jw.WriteObjectEnd();//结束写入集合
jw.WritePropertyName("entityName");
jw.Write(entityName);
}
/// <summary>
/// 写入json数据
/// </summary>
public virtual void SetJsonData(JsonData data)
{
if (data == null)
{
throw new Exception("null");
}
JsonData positionJson = data["position"];
position.x = float.Parse(positionJson["x"].ToString());//物体的位置
position.y = float.Parse(positionJson["y"].ToString());//物体的位置
position.z = float.Parse(positionJson["z"].ToString());//物体的位置
JsonData rotationJson = data["rotation"];
rotation.w = float.Parse(rotationJson["w"].ToString());
rotation.x = float.Parse(rotationJson["x"].ToString());
rotation.y = float.Parse(rotationJson["y"].ToString());
rotation.z = float.Parse(rotationJson["z"].ToString());
entityName = data["entityName"].ToString();//实体的名字
}
}
在子类中续写一下序列化和反序列化的方法,规避一下重复敲代码
using LitJson;
public class StarData : BaseData {
public float speedX;//X轴速度
public float speedY;//Y轴速度
public float speedZ;//Z轴速度
public float lowestHeight;//低于lowestHeight高度后开始拖尾特效
public float duration;//持续时间
/// <summary>
/// 续写序列化方法
/// </summary>
/// <param name="jw"></param>
public override void ToJson(JsonWriter jw)
{
base.ToJson(jw);
jw.WritePropertyName("speedX");
jw.Write(speedX);
jw.WritePropertyName("speedY");
jw.Write(speedY);
jw.WritePropertyName("speedZ");
jw.Write(speedZ);
jw.WritePropertyName("lowestHeight");
jw.Write(lowestHeight);
jw.WritePropertyName("duration");
jw.Write(duration);
}
/// <summary>
/// 写入json数据
/// </summary>
public override void SetJsonData(JsonData data)
{
base.SetJsonData(data);
speedX = float.Parse(data["speedX"].ToString());
speedY = float.Parse(data["speedY"].ToString());
speedZ = float.Parse(data["speedZ"].ToString());
lowestHeight = float.Parse(data["lowestHeight"].ToString());
duration = float.Parse(data["duration"].ToString());
}
}
接下来我们随便构造一个数据类 ,然后调用
SerializeTool.JsonSerialize(Application.streamingAssetsPath + "/star.json", star.data.GetJson());
将序列化的json保存到本地
检查一下数据格式
{"position":{"x":135.904724121094,"y":110.699996948242,"z":49.5925445556641},"rotation":{"w":1.0,"x":0.0,"y":0.0,"z":0.0},"entityName":"Star(Clone)5","speedX":-57.2221565246582,"speedY":-5.72221565246582,"speedZ":22.8888626098633,"lowestHeight":35.0,"duration":2.0}
然后定义一个StarData类型的数据,读取本地刚刚保存的js数据文件,并将json解析保存进这个类中.
data.SetJsonData(SerializeTool.JsonDeserialize(Application.streamingAssetsPath + "/star.json"));
最后从基类和子类选几个属性,简单的测试一下,没有问题
我想,还差个加密,就可以用于单机游戏存档了吧.
这个方法写起来还是比较麻烦的,要手动给所有数据类编写序列化,反序列化方法.但是规避了上面的一些缺点.
如果有更好的实现方法,欢迎指教.