游戏运行时,我们的游戏数据都是存在内存 中的,一旦退出游戏,内存将被释放,这时游戏数据并没有保留下来。因此,利用数据持久化 的操作可以将游戏数据从内存 存储到硬盘 中,从而实现游戏数据的保存。
这篇文章将会介绍几种数据持久化的方案。
值得一提的是,独立开发游戏一般选择Json,优点是人类可读 ,易调试 ;而大型联机游戏一般选择二进制存储 ,人类虽然不可读不易调试,但是它的解析速度最快 ,也更安全 。
PlayerPrefs PlayerPrefs是Unity官方封装好的键值对 本地存储工具类 。
基本操作 存储 键:string 值:int、float、string
PlayerPrefs的存储方式是用类名PlayerPrefs.Setxxx来实现,其中xxx中填入要存储的值类型,参数依次传入键名 、要存储的值 。下面是用PlayerPrefs存储的几个例子:
1 PlayerPrefs.SetInt(“myAge”, 18 );
1 PlayerPrefs.SetFloat(“myHeight”, 165.5f );
1 PlayerPrefs.SetString(“myName”, "大鱼飞九草" );
当游戏结束时,Unity会自动 把数据存在硬盘 中。如果游戏不是正常结束而是崩溃,数据是不会存到硬盘中的。因此,可以调用这个方法立马存储到硬盘中:
另外,PlayerPrefs有一定的局限性,因为只存在三种类型的存储方式,因此,在存储其他数据时会降低精度。
注意:
读取 读取的顺序是先从内存找,再到硬盘中找。
PlayerPrefs的读取方式是用类名PlayerPrefs.Getxxx来实现,其中xxx中填入要读取的值类型,参数传入键名 。下面是用PlayerPrefs读取的例子:
1 int age = PlayerPrefs.GetInt("myAge" );
还有一种重载:
1 2 age = PlayerPrefs.GetInt("myAge" , 100 );
第二个参数默认值对于我们的作用:在得到没有的数据的时候,可以用它来进行基础数据的初始化 。
读取float、string类型的也都相似。
注意:当里面没有值时,会返回一个默认值。int对应0,float对应0,string对应""。
判断键是否存在 1 2 3 4 if (PlayerPrefs.HasKey("myName" )){ }
删除
删除指定键值对
1 PlayerPrefs.DeleteKey("myKey" );
删除所有存储的信息
1 PlayerPrefs.DeleteAll();
存储位置 不同平台PlayerPrefs的存储位置不一样。
Windows
位置:HKCU\SoftWare\[公司名称]\[产品名称]项下的注册表中
Android
位置:/data/data/包名/shared_prefs/包名.xml
IOS
位置:/Library/Preferences/[应用ID].plist
XML XML是一种特殊格式的文件 ,用于传输和存储数据 。XML是一种树形结构根节点。
XML基本语法
创建XML文件:把后缀名改为.xml
注释:
固定内容:一定要写在第一行
1 2 3 <?xml version = "1.0" encoding = "UTF-8" ?>
基本语法
xml的基本语法是<元素标签> </元素标签>配对出现。可以理解为树形结构 :必须要有且仅有一个 根节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <PlayerInfo > <name > meimei酱</name > <age > 19</age > <sex > false</sex > <ItemList > <Item > <id > 1</id > <num > 10</num > </Item > <Item > <id > 2</id > <num > 20</num > </Item > </ItemList > </PlayerInfo >
注意:特殊符号在xml中的写法
符号
xml中写作
<
<
>
>
&
&
‘
&apos
‘’
"
XML属性 区别属性和元素:
元素:一个节点之中包裹的东西,即<>元素</>
属性:写在节点内部的东西,即<Friend 属性>
它俩表示的意思一样,只是两种写法。
1 2 <Friend name = "小明" age = '8' > 我的朋友</Friend >
如果使用属性记录信息,不想使用元素记录,如下:
1 <Frind name = "小明" age = "8" />
验证是否有错 复制到该网址验证:xml - 菜鸟教程
C#读取存储Xml
Xml文件存放的位置:
只读不写:Resource / StreamingAssets 文件夹下
动态存储:Application.persistentDataPath 路径下
读取方法:
XmlDocument(较方便且容易操作)
XmlTextReader
Linq
读取 读取Xml文件信息 1 2 3 4 5 6 7 8 9 10 11 12 using System.Xml; XmlDocument xml = new XmlDocument(); TextAsset asset = Resources.Load<TextAsset>("TestXml" ); xml.LoadXml(asset.text); xml.Load(Application.streamingAssetsPath + "/TestXml.xml" );
读取元素和属性信息
关键的两个类:
节点信息类(单个) XmlNode
节点列表信息类(多个)XmlNodeList
读取元素:
1 2 3 4 5 6 XmlNode root = xml.SelectSingleNode("Root" ); XmlNode nodeName = root.SelectSingleNode("name" ); nodeName.InnerText
读取属性:
1 2 3 4 5 6 7 XmlNode nodeItem = root.SelectSingleNode("Item" ); nodeItem.Attributes["id" ].Value; nodeItem.Attributes.GetNameItem("id" ).Value
注意:如果要得到一个同名节点的信息,不要通过XmlNode,这样只能得到第一个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 XmlNodeList friendLit = root.SlectNodes("Friend" ); foreach (XmlNode item in friendList){ print(item.SelectSingleNode("name" ).InnerText); } for (int i = 0 ; i < friendList.Count; i ++){ print(friendList[i].SelectSingleNode("name" ).InnerText); }
存储
存储路径:Application.persistentDataPath
关键类:
XmlDocument 用于创建节点 存储文件
XmlDeclaration 用于添加版本信息
XmlElement 节点类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 using System.Xml;string path = Application.persistentDataPath + "/PlayerInfo2.xml" ;XmlDocument xml = new XmlDocument(); XmlDeclaration xmlDec = xml.CreateXmlDeclaration("1.0" , "UTF-8" , "" ); xml.AppendChild(xmlDec); XmlElement root = xml.CreateElement("Root" ); xml.AppendChild(root); XmlElement name = xml.CreateElement("name" ); name.InnerText = "小明" ; root.AppendChild(name); XmlElement listInt = xml.CreateElement("listInt" ); for (int i = 1 ; i <= 3 ; i ++){ XmlElement childNode = xml.CreateElement("int" ); childNode.InnerText = i.ToString(); listInt.AppendChild(childNode); } root.AppendChild(listInt); XmlElement itemList = xml.CreateElement("itemList" ); for (int i = 1 ; i = 3 ; i ++){ XmlElement childNode = xml.CreateElement("Item" ); childNode.SetAttribute("id" , i.ToString()); itemList.AppendChild(childNode); } root.AppendChild(itemList); xml.Save(path);
修改 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 if ( File.Exists(path) ) { XmlDocument newXml = new XmlDocument(); newXml.Load(path); XmlNode node = newXml.SelectStingleNode("Root" ).SelectStingleNode("name" ); XmlNode node = newXml.SelectStingleNode("Root/name" ); XmlNode root2 = newXml.SelectSingleNode("Root" ); root2.RemoveChild(node); XmlElement speed = newXml.CreateElement("moveSpeed" ); speed.InnerText = "20" ; root2.AppendChild(speed); newXml.Save(path); }
XML序列化与反序列化 序列化 序列化 是把内存中的数据 存储到硬盘 的过程
几个关键类:
XmlSerializer 用于序列化对象为xml
StreamWriter 用于存储文件
using 用于方便流对象释放和销毁
序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 using System.IO; using System.Xml.Serialization; public class Lesson1Test { } public class Main : MonoBehaviour { void Start () { Lesson1Test lt = new Lesson1Test(); string path = Application.persistentDataPath + "/Test.xml" ; using ( StreamWriter stream = new StreamWriter(path) ) { XmlSerializer s = new XmlSerializer(typeof (Lesson1Test)); s.Serialize(stream, lt); } } }
注意:序列化只支持public,不支持字典
以属性方式存储: 加上一个特性:[XmlAttribute()] ,括号里面可以传想要的属性名(字符串形式)
给元素改名字:
普通元素:[XmlElement()]
List:[XmlArray()]
List里面的元素:[XmlArrayItem()]
反序列化 反序列化 是把硬盘数据 还原为内存上数据 的过程
反序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using System.IO;using System.Xml.Serialization; string path = Application.persistentDataPath + "/Lesson1Test.xml" ;if ( File.Exists(path) ){ using (StreamReader reader = new StreamReader(path)) { XmlSerializer s = new XmlSerializer(typeof (Lesson1Test)); Lesson1Test lt = s.DeSerialize(reader) as Lesson1Test; } }
注意:List对象如果有默认值,反序列化时不会清空,会在后面添加
Ixmlserializable 接口
定义:Ixmlserializable是XmlSerializer提供的可拓展内容。他能让一些不能被序列化和反序列化的特殊类能被处理。
自定义类实践:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public class Test1 : Ixmlserializable { public int test1; public XmlSchema GetSchema () { return null ; } public void ReadXml (XmlReader reader ) { this .test1 = int .Parse(reader["test1" ]); reader.Read(); reader.Read(); this .test1 = int .Parse(reaser.Value()); reader.Read(); while ( reader.Read() ) { if ( reader.NodeType == XmlNodeType.Element ) { switch (reader.Name) { case "test1" : reader.Read(); this .test1 = int .Parse(reader.Value); break ; } } } XmlSerializer s = new XmlSerializer(typeof (int )); reader.Read(); reader.ReadStartElement("test1" ); test1 = (int )s.Deserialize(reader); reader.ReadEndElement(); } public void WriteXml (XmlWriter writer ) { writer.WriteAttributeString("test1" , this .test1.toString()); writer.WriteElementString("test1" , this .test1.toString()); Xmlserializer s = new Xmlserializer(typeof (int )); writer.WriteStartElement("test1" ); s.Serialize(writer, test1); writer.WriteEndElement(); } } public class Main : MonoBehaviour { void Start () { Test1 t = new Test1(); } }
让Dictionary支持序列化 思路:继承Dictionary,然后实现Ixmlserializable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class SerializerDictionary <TKey , TValue > : Dictionary <TKey , TValue >, Ixmlserializable { public XmlSchema GetSchema () { return null ; } public void ReadXml (XmlReader reader ) { XmlSerializer keySer = new XmlSerializer(typeof (TKey)); XmlSerializer ValueSer = new XmlSerializer(typeof (TValue)); reader.Read(); while (reader.NodeType != XmlNodeType.EndElemenrt) { TKey key = (TKey)keySer.Deserialize(reader); TValue value = (TValue)keySer.Deserialize(reader); this .Add(key, value ); } reader.Read(); } public void WriteXml (XmlWriter writer ) { XmlSerializer keySer = new XmlSerializer(typeof (TKey)); XmlSerializer ValueSer = new XmlSerializer(typeof (TValue)); foreach (KeyValuePair<TKey, TValue> kv in this ) { keySer.Serialize(writer, kv.Key); ValueSer.Serialize(writer, kv.Value); } } } public class Main : MonoBehaviour { void Start () { TestLesson4 tl4 = new TestLesso4() tl4.dic = new SerializerDictionary<int , string >(); } }
Json Json是一种特殊的文件格式,主要用于传输数据 、本地数据存储 和读取 。 与xml的区别是Json配置更简单、某些情况下读写更快速。
Json基本语法
创建Json文件:把后缀名改为.json
注释:
基本语法:
Json格式是一种键值对结构 。
符号
含义
{}
对象
[]
数组
:
键值对 对应关系
,
数据分割
""
键名、字符串
键值对表示:"键名" : 值内容,其中,值可以是数字、字符串、bool值、数组、对象、null。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { "name" : "meimei酱" , "age" : 19 , "sex" : false , "height" : 165.5 , "ids" : [ 1 , 2 , 3 , 4 ] , "students" : [ { "name" : "玛丽亚" , "age" : 20 , "sex" : true } , { "name" : "小明" , "age" : 18 , "sex" : false } ] , "home" : { "address" : "成都" , "street" : "春熙路" } , "son" : null }
在线转Json网站:bejson
C#读取存储Json 存读字符串
1 2 3 4 using System.IO;File.WriteAllText(Application.persistentDataPath + "/Test.json" , "小草存储的json文件" );
注意:直接写文件名和后缀,不能自己加一个文件夹
1 2 3 using System.IO;string str = File.ReadAllText(Application.persistentDataPath + "/Test.json" );
JsonUtility Jsonutility是Unity自带的用于解析Json的公共类 。它可以将内存中的对象序列化为Json格式的字符串,也可以将Json字符串反序列化为类对象。
JsonUtility提供了现成的方法,可以把类对象 序列化为 json字符串 ,下面是一个实例:
1 2 3 4 5 using System.IO;Person p = new Person(); string jsonStr = JsonUtility.ToJson(p);File.WriteAllText(Application.persistentDataPath + "/Person.json" , jsonStr);
注意:
float序列化时看起来会有一些误差,不过读出时不影响。
自定义类 需要加上序列化特性[System.Serializable]
想要序列化私有变量,需要加上特性[SerializeField]
JsonUtility不支持字典
存储null时会有一个默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 using System.IO;jsonStr = File.ReadAllText(Application.persistentDataPath + "/Person.json" ); Person p = JsonUtility.FromJson(jsonStr, typeof (Person)) as Person; Person p1 = JsonUtility.FromJson<Person>(jsonStr);
注意:如果json中数据少了,不会报错
另外,JsonUtility无法直接读取数据集合,需要用类对象包裹一层,编码格式必须是UTF-8
LitJson LitJson是一个第三方库 ,用于处理Json的序列化和反序列化。它体积小、速度快、易于使用,使用时只需要把他的代码拷贝到工程中即可。建议使用LitJson。
1 2 3 4 5 6 7 using LitJson;using System.IO;Animal a = new Animal(); string jsonStr = JsonMapper.ToJson(a);File.WriteAllText(Application.persistentDataPath + "/Animal.json" , jsonStr);
注意:
通过LitJson存储的字符串表现上有点不一样,不过反序列化回来不影响
可以存储字典,字典的键建议用字符串
不能序列化私有变量
需要引用LitJson命名空间
能够准确的存储null
1 2 3 4 5 6 7 8 9 10 11 using LitJson;using System.IO;jsonStr = File.ReadAllText(Application.persistentDataPath + "/Animal.json" ); jsonData data = jsonMapper.ToObject(jsonStr); print(data["name" ]); Animal a = JsonMapper.ToObject<Animal>(jsonStr); print(a.name);
注意:
虽然支持字典类型,但是键一定是字符串类型才能使用。
类结构需要无参构造函数 ,否则反序列化的时候会报错。
与JsonUtility不同的是,LitJson可以直接读取数据集合。相同点是文本编码格式是UTF-8
二进制 (未完待续)