持久化数据

[Serialization](序列化),可以用来将对象转化为字节流;仅对类,结构,枚举,委托的声明有效 alt

Deserialization(反序列化),可以用来将字节流转换为对象。 Json和XML参考链接

PlayerPrefs

在游戏会话中储存和访问游戏存档。这个是持久化数据储存,比如保存游戏记录 alt

函数

  • DeleteAll
    从游戏存档中删除所有key。请谨慎使用。
  • DeleteKey
    从游戏存档中删除key和它对应的值。
  • GetFloat
    如果存在,返回游戏存档文件中key对应的浮点数值。
  • GetInt
    如果存在,返回游戏存档文件中key对应的整数值。
  • GetString
    如果存在,返回游戏存档文件中key对应的字符串值。
  • HasKey
    如果key在游戏存档中存在,返回true。
  • Save
    写入所有修改参数到硬盘。
  • SetFloat
    设置由key确定的浮点数值。
  • SetInt
    设置由key键确定的整数值。
  • SetString
    设置由key确定的字符串值。

对于Save()函数:unity默认在程序退出时保存参数,以防游戏崩溃或过早退出,可能需要在游戏中合理的“检查点”编写PlayerPrefs。这个函数会将数据写入磁盘,可能会导致稍微的卡顿,因此不建议在实际的游戏过程中调用。

JSON

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,具有自我描述性。它基于JavaScript(Standard ECMA-262 3rd Edition - December 1999)的一个子集。

JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。这些特性使JSON成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成(网络传输速度)。是一种与语言无关的发送和接收数据的常用格式,可以使用它来跨平台的传输数据。

  • JSON序列化:对象--->JSON
  • JSON反序列化:JSON--->对象

JSON语法

JSON 语法是 JavaScript 对象表示语法的子集。

  • 数据在名称/值对中
  • 数据由逗号分隔
  • 花括号{ }保存对象
  • 方括号[ ]保存数组

JSON 名称/值对

  • JSON 数据的书写格式是:名称/值对。
  • 名称/值对组合中的名称写在前面(在双引号中),值对写在后面(同样在双引号中),中间用冒号隔开;
  • 例:"firstName":"John",这很容易理解,等价于这条 JavaScript 语句:firstName="Name"

JSON 值可以是:

  • 数字(整数或浮点数)
  • 字符串(在双引号中)
  • 逻辑值(true 或 false)
  • 数组(在方括号中[])
  • 对象(在花括号中{})
  • null

JSON结构

  • 对象:对象在js中表示为“{}”括起来的内容,数据结构为 {key:value,key:value,...}的键值对的结构,在面向对象的语言中,key为对象的属性,value为对应的属性值,所以很容易理解,取值方法为 对象[key] 获取属性值,这个属性值的类型可以是 数字、字符串、数组、对象几种。

  • 数组:数组在js中是中括号“[]”括起来的内容,数据结构为 [“c",“unity",“mono",...],取值方式和所有语言中一样,使用索引取值,字段值的类型可以是 数字、字符串、数组、对象几种。

LitJson介绍

LitJson是一个开源项目,比较小巧轻便,安装也很简单,在Unity里只需要把LitJson.dll放到Plugins文件夹下,并在代码的最开头添加 “Using LitJson”就可以了。

简单来说,LitJson的用途是实现Json和代码数据之间的转换,一般用于从服务器请求数据,得到返回的Json后进行转换从而在代码里可以访问。

Json生成、解析

  • Json生成
//使用JsonWriter创建JSON复合字符串(包含对象和数组)

JsonWriter writer = new JsonWriter();  //创建JsonWriter对象
jsonWriter.WriteObjectStart();//开始写对象
jsonWriter.WritePropertyName("姓名");//写入键
jsonWriter.Write("wu");//写入值
jsonWriter.WritePropertyName("具体信息");//写入键
jsonWriter.WriteArrayStart();//开始写数组,数组做为键值对中的值
jsonWriter.Write("女");//写数组中的值
jsonWriter.Write("18");//写数组中的值
jsonWriter.WriteArrayEnd();//结束写数组
jsonWriter.WritePropertyName("其他信息");//写入键
jsonWriter.WriteObjectStart();//开始写对象,对象作为键值对的值
jsonWriter.WritePropertyName("年级");
jsonWriter.Write("一年级");
jsonWriter.WritePropertyName("学校");
jsonWriter.WriteArrayStart();
jsonWriter.Write("JSON字符串学习");
jsonWriter.WriteArrayEnd();//结束写数组
jsonWriter.WriteObjectEnd();//结束写对象
jsonWriter.WriteObjectEnd();//结束写对象
Debug.Log(jsonWriter);//直接输出json字符串

//--------------------------------------------------------
生成json对象
{"姓名":"wu","具体信息":["女","18"],"其他信息":{"年级":"一年级","学校":["JSON字符串学习"]}}

//使用JsonData创建JSON复合字符串(包含对象和数组)
 
JsonData jsonObject01 = new JsonData();//创建JsonData对象
jsonObject01["昵称"] = "囍";//为所创建的JsonData对象添加键值对;此时作为Json对象使用
jsonObject01["职业"] = "战士";
JsonData jsonArray = new JsonData();//创建JsonData对象
jsonArray.Add("攻击力");//为所创建的JsonData对象添加值;此时作为Json数组使用
jsonArray.Add(200);
jsonObject01["具体"] = jsonArray;//为Json对象添加键值对;值为一个Json数组
JsonData jsonObject02 = new JsonData();//创建JsonData对象
jsonObject02["性别"] = "男";//为所创建的JsonData对象添加键值对;此时作为Json对象使用
jsonObject01["其他信息"] = jsonObject02;//为Json对象添加键值对;值为一个Json对象
Debug.Log(jsonObject01.ToJson());//将jsonData对象转换为Json字符串后输出
 
//--------------------------------------------------
生成json对象
{"昵称":"囍","职业":"战士","具体":["攻击力",200],"其他信息":{"性别":"男"}}
 
//使用JsonMapper类将序列化的类对象转换为Json字符串
[System.Serializable]//可序列化特性
public class Save
{
    public List<int> monsterTypeList = new List<int>();
    public List<int> targetPosList = new List<int>();

    public int shootNum = 0;
    public int score = 0;
}
Save saveData = new Save();
string saveDataJson = JsonMapper.ToJson(saveData);//将序列化的对象转换为Json字符串
  • Json解析
(JsonMapper类)相关API

JsonData data =JsonMapper.ToObject(string json) //把json数据转化成JsonData对象

data.ToJson(); //把JsonData对象转化成json数据
//------------------------------------------
JsonMapper.ToObject <T> (T t1)  //把json数据转化成对应的类对象  
JsonMapper.ToJson(object o) //把一个对象中的数据转化成json数据
string json01="{"姓名":"wu","具体信息":["女"],"其他":{"年级":"一年级","学校":["JSON字符串学习"]}}";
JsonData jsonData = JsonMapper.ToObject(json01);
Debug.Log(jsonData["姓名"].ToString()); //wu;通过键获取值
Debug.Log(jsonData["具体信息"][0].ToString());//女;通过键获取值后,通过索引获取数组中的值
Debug.Log(jsonData["其他"]["学校"][0].ToString());//JSON字符串学习;通过键获取值后,再次通过键获取值,再通过索引获取数组中的值

JsonUtility

Unity圣典

  • JsonUtility.FromJson 使用Json表达式创建对象

C# => public static T FromJson(string json);

C# => public static object FromJson(string json, Type type);

json:json字符串;type:JSON表示的对象类型。

内部工具,此方法使用Unity序列化器;因此你创建的类型必须支持序列化。它必须是有序列化属性标记的类或结构。对象的域必须支持序列化。不支持的域和私有域或者非序列化属性标记的域一样,将会被忽略。仅支持普通的类和结构;从UnityEngine.Object派生的类(例如MonoBehaviour 或者ScriptableObject)是不支持的。如果JSON表示缺少任何字段,将为它们提供默认值(即T类型域会是默认值-它将不会被指定为字段初始化器的任何值,因为该对象的构造函数不会在反序列化期间执行)。该方法可以被底层线程调用。

using UnityEngine;
[Serializable]
public class PlayerInfo
{
	public string name;
	public int lives;
	public float health;
	public static PlayerInfo CreateFromJSON(string jsonString)
	{
		return JsonUtility.FromJson<PlayerInfo>(jsonString);
	}
	// 传入值:
	// {"name":"Dr Charles","lives":3,"health":0.8}
	// 返回的PlayerInfo对象
	// name == "Dr Charles", lives == 3, and health == 0.8f.
}
  • JsonUtility.FromJsonOverwrite 通过读取JSON字符串来覆盖对象中的数据。

C# => public static void FromJsonOverwrite(string json, object objectToOverwrite);

json:Json字符串;objectToOverwrite:要被重写数据的对象

它将JSON数据加载到一个现有对象中。这允许更新存储在类或对象中的值,而无需进行任何分配。

内部工具,这个方法使用Unity序列化器;因此,你传递的对象必须被序列化器支持:它必须是MonoBehaviour, ScriptableObject,或者应用了Serializable属性的普通类/结构。要覆盖的字段类型必须由序列化程序支持;不支持的字段将被忽略,私有字段、静态字段和应用了NonSerialized属性的字段也将被忽略。支持任何普通类或结构,以及MonoBehaviour或ScriptableObject派生的类。不支持其他引擎类型。仅在编辑器中,您可以使用EditorJsonUtility。FromJsonOverwrite覆盖其他引擎对象。如果一个对象的字段不存在JSON表示,该字段将保持不变。 在方法可以被底层线程调用。当该函数运行使你不该修改或重写该对象。

using UnityEngine; 
public class PlayerState : MonoBehaviour
{
	public string playerName;
  	public int lives;
  	public float health; 
	public void Load(string savedData)	
  	{
		return JsonUtility.FromJsonOverwrite(savedData, this);
    } 
  	// 传入输入值
	// {"lives":3, "health":0.8}
  	// load函数将改变调用它的对象
  	// lives == 3 , health == 0.8
  	// 'playerName' 字段保持不变
}
  • JsonUtility.ToJson 生成一个对象的公共字段的JSON字符串。

C# => public static string ToJson(object obj);

C# =>public static string ToJson(object obj, bool prettyPrint);

obj:要转为Json的对象;prettyPrint:如果为真,格式化输出以提高可读性。如果为false,将输出格式化为最小大小。默认是假的。

内部工具,这个方法使用Unity序列化器;因此,你传递的对象必须被序列化器支持:它必须是MonoBehaviour, ScriptableObject,或者应用了Serializable属性的普通类/结构。您希望包含的字段类型必须由序列化程序支持;不支持的字段将被忽略,私有字段、静态字段和应用了NonSerialized属性的字段也将被忽略。支持任何从MonoBehaviour 和ScriptableObject导出的普通类和结构。其他引擎类型是不支持的。仅在编辑器中你可以使用EditorJsonUtility.FromJsonOverwrite去重写其他引擎对象。 请注意,虽然可以将原始类型传递给该方法,但结果可能不是您所期望的;该方法将尝试序列化它们的公共实例字段,而不是直接序列化它们,结果生成一个空对象。类似地,将数组传递给该方法将不会生成包含每个元素的JSON数组,而是生成一个包含数组对象本身的公共字段(没有公共字段)的对象。要序列化数组或基本类型的实际内容,有必要将其包装在类或结构中。在方法可以被底层线程调用。当该函数运行使你不该修改或重写该对象。

using UnityEngine;
public class PlayerState : MonoBehaviour
{
	public string playerName;
  	public int lives;
  	public float health; 
	public string SaveToString()
  	{
    	return JsonUtility.ToJson(this);
  	} 
  	//该对象的值: playerName = "Dr Charles",lives = 3, health = 0.8f,
  	//返回值: {"playerName":"Dr Charles","lives":3,"health":0.8}
}

XML

扩展标记语言,用于标记电子文件使其具有结构性的标记语言。被设计用来传输和存储数据;结构化、存储以及传输信息。

  • XML有且只有一个根节点

  • XML指可扩展标记语言(Extensible Markup Language)。

  • XML是一种很像HTML的标记语言。

  • XML的设计宗旨是传输数据,而不是显示数据。

  • XML标签没有被预定义,你需要自行定义标签

  • XML被设计为具有自我描述性

  • XML是W3C的推荐标准。

  • 图示:

    alt

XML语法规则

  • 所有 XML 元素都须有关闭标签
	<p>This is a paragraph</p>
  • XML 标签对大小写敏感,例:标签 与标签 是两个不同的标签
	<Message>这是错误的。</message>
	<message>这是正确的。</message> 
  • XML 必须正确地嵌套
	<b><i>This text is bold and italic</b></i>  //错误
	<b><i>This text is bold and italic</i></b>  //正确
  • XML 文档必须有根元素
<root>
  <child>
    <subchild>.....</subchild>
  </child>
</root>
  • XML 的属性值须加引号
	<note date=08/08/2008> //错误
		<to>George</to>
		<from>John</from>
	</note> 
	<note date="08/08/2008"> //正确
		<to>George</to>
		<from>John</from>
	</note> 
  • XML 中的注释
	<!-- This is a comment -->

XML命名规则

  • XML 元素必须遵循以下命名规则:
    名称可以含字母、数字以及其他的字符
    名称不能以数字或者标点符号开始
    名称不能以字符 “xml”(或者 XML、Xml)开始
    名称不能包含空格

  • 可使用任何名称,没有保留的字词。

XmlDocument类

XmlDocument将XML视为树状结构,它装载XML文档,并在内存中构建该文档的树状结构。

创建XML

  • XmlDocument:创建XmlDocument实例,
XmlDocument xmlDocument = new XmlDocument();
  • CreateXmlDeclaration:声明头部文件:
XmlDeclaration head = xmlDocument.CreateXmlDeclaration("1.0", "UTF-8", "");
  • CreateElement:创建XML元素结点
XmlElement 节点名称 = xmlDocument.CreateElement(节点名称);
  • AppendChild:将指定的节点添加到该节点的子节点列表的末尾
xmlDocument.AppendChild(head);      
上一级结点节点.AppendChild(下一级节点);
  • SetAttribute:设置属性
节点名称.SetAttribute(“属性名”,属性值);
  • InnerText :获取或设置节点中的文本值及其所有子节点的串联值。
  • Save :将 XML 文档保存到指定的位置

实例:

private void CreateXML()
{
    string filePath = Application.streamingAssetsPath + "/Test.xml";
	//检查xml是否存在,如果没有文件则创建
	if(File.Exists(filePath)) return;
	XmlDocument xmlDoc = new XmlDocument ();//创建xml实例
	XmlElement root = xmlDoc.CreateElement("root_CharacterTemplateData");//创建根节点
	XmlElement elmNew = xmlDoc.CreateElement("CharacterTemplateData");//创建一级子节点
	elmNew.SetAttribute("id","0");//设置属性,属性名为id,值为0
	elmNew.SetAttribute ("name","xml");//设置属性。属性名为name,值为xml
	XmlElement jobID = xmlDoc.CreateElement("JobID");//创建二级节点,结点名为JobID
	jobID.InnerText = "1";//设置二级节点内的文本值
	elmNew.AppendChild(jobID);//在一级节点中追加二级节点
	XmlElement jobMode = xmlDoc.CreateElement ("JobMode");//创建二级节点,结点名为JobMode
	jobMode.InnerText = "none";//设置二级节点内的文本值
	elmNew.AppendChild (jobMode);//在一级节点中追加二级节点
	XmlElement initForce = xmlDoc.CreateElement ("InitForce");
	initForce.InnerText = "1.1";
	elmNew.AppendChild (initForce);
	root.AppendChild(elmNew);//在跟节点中追加一级节点
	xmlDoc.AppendChild(root);//在xml实例中追加根节点
	xmlDoc.Save(filePath);//保存xml文件
}
  
//----------------------------------------------------
//结果:
  <root_CharacterTemplateData>
  <CharacterTemplateData id="0" name="xml">
    <JobID>1</JobID>
    <JobMode>none</JobMode>
    <InitForce>1.1</InitForce>
  </CharacterTemplateData>
</root_CharacterTemplateData>

添加XML

  • Load:加载指定的 XML 数据。
XmlDocument 实例名称 = new XmlDocument ();//创建XmlDocument实例
XmlDocument 实例名称.Load(XML文件所在的文件路径包含文件名);
  • SelectSingleNode:查找单一节点
XmlDocument 实例名称.SelectSingleNode(节点名称);
如果用于两个节点间的查找:例:B为A的结点,如果想要查找B;A结点.SelectSingleNode("B结点名称")
  • SelectNodes:查找所有结点
XmlNodeList xmlNodeList = XmlNode对象.SelectNodes("PosAndType");
  • GetElementsByTagName:通过结点名称查找元素;可以查找整个Xml中与string相同的结点。
XmlNodeList xmlNodeList = XmlDocument实例.GetElementsByTagName(结点名称);
  • CreateElement:添加元素节点
XmlElement 二级节点= xmlDocument.CreateElement(节点名称)

实例:

private void AddXML()
{
	//检测文件是否存在
	if(!File.Exists(filePath)) return;
	XmlDocument xmlDoc = new XmlDocument ();//创建XmlDocument实例
	xmlDoc.Load(filePath);//通过文件路径加载XML文件
	XmlNode root = xmlDoc.SelectSingleNode ("root_CharacterTemplateData");//查找根节点
	XmlElement elmNew = xmlDoc.CreateElement("CharacterTemplateData");//要添加的新节点
	elmNew.SetAttribute ("id","1");//设置属性
	elmNew.SetAttribute ("name","name1");
	XmlElement jobId = xmlDoc.CreateElement ("JobID");
	jobId.InnerText = "1";//设置结点中的文本文件
	elmNew.AppendChild (jobId);//进行结点的关联;将二级结点追加到一级节点下
	XmlElement jobMode = xmlDoc.CreateElement ("JobMode");
	jobMode.InnerText = "none";
	elmNew.AppendChild (jobMode);
	XmlElement initForce = xmlDoc.CreateElement ("InitForce");
	initForce.InnerText = "2.2";
	elmNew.AppendChild (initForce);
	root.AppendChild (elmNew);//在根节点中添加新建的节点
	xmlDoc.Save(filePath);//保存XML文件
	}
//----------------------------------------------------
//结果:
  <root_CharacterTemplateData>
  <CharacterTemplateData id="0" name="xml">
    <JobID>1</JobID>
    <JobMode>none</JobMode>
    <InitForce>1.1</InitForce>
  </CharacterTemplateData>
    <!-- 添加的部分 -->
  <CharacterTemplateData id="1" name="name1">
    <JobID>1</JobID>
    <JobMode>none</JobMode>
    <InitForce>2.2</InitForce>
  </CharacterTemplateData>
    <!-- 添加的部分 -->
</root_CharacterTemplateData>

更新XMl

  • GetAttribute:获取属性
节点对象. GetAttribute(属性名称)
  • SetAttribute:设置属性
节点对象.SetAttribute(“属性名”,属性值); 

实例:

private void UpdateXML()
{
	//检测文件
	if(!File.Exists(filePath)) return;
	//读取实例
	XmlDocument xmlDoc = new XmlDocument ();
	xmlDoc.Load (filePath);
	//获取根节点下所有子节点
	XmlNodeList nodeList = xmlDoc.SelectSingleNode("root_CharacterTemplateData").ChildNodes;
	//获取结点名为root_CharacterTemplateData的所有子节点
	foreach(XmlElement xe in nodeList)
	{
  		//遍历所有子节点,节点名称为CharacterTemplateData并且属性id=0,修改属性
		if (xe.Name == "CharacterTemplateData" && xe.GetAttribute ("id") == "0") 
  		{
			xe.SetAttribute ("id","1000");
			//修改该节点的子节点,节点名称为JobID=2
			xe.SelectSingleNode("JobID").InnerText = "2";
		}
	}
	//保存
	xmlDoc.Save(filePath);
}
//----------------------------------------------------
//结果:
  <root_CharacterTemplateData>
  <CharacterTemplateData id="1000" name="xml">//id更改
    <JobID>2</JobID>//结点内文本值更改
    <JobMode>none</JobMode>
    <InitForce>1.1</InitForce>
  </CharacterTemplateData>
  <CharacterTemplateData id="1" name="name1">
    <JobID>1</JobID>
    <JobMode>none</JobMode>
    <InitForce>2.2</InitForce>
  </CharacterTemplateData>
</root_CharacterTemplateData>

删除XML

  • ChildNodes:获取所用子节点
节点对象.ChildNodes;
  • RemoveAttribute:删除节点属性
XmlElement对象.RemoveChild(XmlNode类型对象)

实例:

private void DeleteXML()
{
	if (!File.Exists (filePath))
		return;
	XmlDocument xmlDoc = new XmlDocument ();
	xmlDoc.Load (filePath);
	XmlNodeList nodeList = xmlDoc.SelectSingleNode ("root_CharacterTemplateData").ChildNodes;
	foreach(XmlElement xe in nodeList)
	{
		if (xe.Name == "CharacterTemplateData" && xe.GetAttribute ("id") == "1") {
			xe.RemoveAttribute ("id");//移除节点属性值为id的结点
			xe.SelectSingleNode ("JobID").InnerText = "";
		}
	}
	xmlDoc.Save (filePath);
}
//----------------------------------------------------
//结果:
  <root_CharacterTemplateData>
  <CharacterTemplateData id="1000" name="xml">//id更改
    <JobID>2</JobID>//结点内文本值更改
    <JobMode>none</JobMode>
    <InitForce>1.1</InitForce>
  </CharacterTemplateData>
  <CharacterTemplateData name="name1">
    <JobID></JobID>
    <JobMode>none</JobMode>
    <InitForce>2.2</InitForce>
  </CharacterTemplateData>
</root_CharacterTemplateData>

数据库

Sqlite

Sqlite菜鸟教程