Unity笔记-json和xml
Unity笔记-json和xml
Wang ChenHan数据格式
在一个公司里,老板给了员工A一些任务,叫员工A按时去完成这些任务。
但这个员工A很健忘,经常忘记老板吩咐的任务。
员工A为了防止这种情况再次发生,想出了一种办法:拿出一个小本本在上面按一定的格式记录下每次老板吩咐的任务。
这样,员工A就不会再忘记工作了。
在员工A记录任务时他所使用的固定格式,就叫做————
数据格式-为什么需要数据格式
以前在编写数据时,人们用空格隔开数据,以实现数据的分类,一碰上比较复杂的情况时,这种分隔方式就会遇到一种问题:
这啥啊?
由于数据的庞大与复杂,不仅人看不清楚,机器也看不懂这一坨究竟是啥。
于是人们开始用逗号”,”分隔数据。这种用逗号分隔属性的格式叫做CSV格式。
office支持csv格式,可以读取xxx.csv。
1 | //Name Id price |
一些网游会出现包含逗号的语句数据,于是把分隔的逗号换成**|**这样能避免混淆,且看起来更像表格:
1 | //Name Id price |
但这样依旧不够清晰,而且也不够统一,于是就出现了两种常用的格式:XML和Json
玩游戏的观众想必知道:在游戏文件夹里,常常出现后缀是json和xml的文件,这些文件储存了游戏中需要存储的数据,方便下次使用。
游戏中需要储存的数据有背包物品,角色血量,关卡进度,怪物生成等,其中Json大多数存储的是游戏配置的数据(如怪物生成,Npc数据,游戏进度,背包内容),而XML可储存物品或玩家的数据(如角色面板,物品信息,大世界数据,怪物面板)我们先来看Json是什么样的:
JSON
json是一种常用的数据格式,是数组+字典的嵌套
这是一个简单的json:
1 | { |
我们来分析一下这段代码:
第一行有一个括号这是一个字典,所有数据都储存在这个字典中。
第二行是字典中的一个键值对,键是persons,值是一个包含字典的数组,也就是一个字典类型的数组。
第三行是一个括号,这是数组里的第一个字典。
之后写了三个键值对,表示这个字典里有三个元素,第二个字典同理。这就是这串json表达的信息。
可以看出,这个Json将这个类的数据通过字典+数组的格式存储在文件里,机器一看到后缀是json,就会想到哦!这是json数据格式!,便会用json”字典+数组”的格式来读取数据。
在csharp(c#)里,json数组映射成c#数组,json字典映射为c#对象
Json-存储数据
在知道了Json的相关基础后,我们该如何去存储和读取json呢?
对于json的读写操作,unity提供了一个十分方便的工具:JsonUtility,它在unity项目的工程包里,不需要任何插件来使用。
在上面的例子中,员工A记录任务时需要一支笔来记录。JsonUtility里也有担任这项任务的工具:ToJson。
ToJson返回的是一个json格式的字符串,它会将传入的对象转化成json(注意,现在只是拿到了json格式的数据,并没有写入!)
打开一个unity项目,在Assets下创建脚本JsonUtilityTest。
1 | using System.Collections; |
大家可能会注意到,在声明记录的类的上面,有一个[Serializable]标签,该标签表示这个类/字段/方法是可序列化的在本例中,它向unity表明类Person可以序列化为json,如果没有,unity可能无法将数据保存。
现在我们只是获取了一个json字符串,并没有将数据保存,我们可以这样做:
1 | using System.IO; |
在Assets文件夹下创建StreamingAssets文件夹,之后运行脚本,几秒后,会发现刚刚创建的文件夹下有一个JsonTest.json文件,里面包含着一个刚生成的字符串。
现在我们已经学会了如何用JsonUtility存储数据
例如:这是一个跑酷游戏,现在想将玩家进度存档:
1 | using System.Collections; |
###FromJson(读档)
FromJson可以将Json字符串转化为对象。
格式:v = JsonUtility.FromJson<要转化的对象类型(v的类型)>(v);
注意:进行Json转化对象,对象必须可序列化([Serialzable])
按照上面的跑酷例子,写出以下代码:
1 | PlayerState players = JsonUtility.FromJson<PlayerState>(str); |
如果控制台输出角色名字,则说明代码正确
第三方Json工具
进入第三方工具dll文件下载然后在你unity项目的根目录(Assets)新建Plugins文件夹,把dll文件拖进去。
拖进去后会显示一个拼图图标。
优势
- LitJson在创建类时不需要加上**[Serializable]标签**
- LitJson有两种解析和创建Json的方式
创建与解析 一
创建
创建要用到LitJson里的JsonMapper类里的ToJson方法:
创建LitJsons脚本(注意脚本不要和LitJson重名,在里面导入命名空间:然后编写代码:1
using LitJson;
创建空对象LitJson,将脚本挂载到对象上,运行,显示1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void Start()
{
fun1();
}
void fun1(){
JsonTest jsontest1 = new JsonTest();
jsontest1.name = "小明";
jsontest1.age = 11;
JsonTest jsontest2 = new JsonTest();
jsontest2.name = "小红";
jsontest2.age = 10;
JsonList jsonlist = new JsonList();
jsonlist.jsonlist = new JsonTest[]{jsontest1,jsontest2};
string jsonstr = JsonMapper.ToJson(jsonlist);
Debug.Log(jsonstr);
}
{“jsonlist”:[{“name”:”\u5C0F\u660E”,”age”:11},{“name”:”\u5C0F\u7EA2”,”age”:10}]}
Json字符串的一些位置出现了一些乱码,这是因为LitJson将中文转成了Unicode编码。但这样的Json仍然可以直接使用,不影响效果。如果想知道这些Unicode究竟是什么,可以试着搜索网站,将Unicode转成中文。解析
解析使用JsonMapper里的ToObject方法:这次,控制台打印出了”小明”二字,再次说明Unicode编码不影响实际效果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void fun1(){
JsonTest jsontest1 = new JsonTest();
jsontest1.name = "小明";
jsontest1.age = 11;
JsonTest jsontest2 = new JsonTest();
jsontest2.name = "小红";
jsontest2.age = 10;
JsonList jsonlist = new JsonList();
jsonlist.jsonlist = new JsonTest[]{jsontest1,jsontest2};
string jsonstr = JsonMapper.ToJson(jsonlist);
Debug.Log(jsonstr);
//解析
JsonList listobject = JsonMapper.ToObject<JsonList>(jsonstr);
Debug.Log(listobject.jsonlist[0].name);//小明
}创建与解析 二
这种Json创建方法不再依靠指定的类,而是使用一个JsonData类表示Json中各种类型创建
最后控制台打印1
2
3
4
5
6
7
8
9
10
11
12void Start(){
fun2();
}
void fun2(){
JsonData jd1 = new JsonData();
jd1.SetJsonType(JsonType.Object);//一般常用的是JsonType.Array(数组,Json中的中括号)和JsonType.Object(字典,Json中的大括号)
//上句话不写也行,JsonData可以自动解析当前类型
jd1["name"] = "小明";//JsonData自动推断类型,只要写属性名和属性值
jd1["age"] = 11;
Debug.Log(jd1.ToJson());//直接用ToJson转化成Json
}
{“name”:”\u5C0F\u660E”,”age”:11}
这样,我们可以实现刚才的样例:控制台显示1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21void fun2(){
void fun2(){
JsonData jd1 = new JsonData();
jd1.SetJsonType(JsonType.Object);//一般常用的是JsonType.Array(数组,Json中的中括号)和JsonType.Object(字典,Json中的大括号)
//上句话不写也行,JsonData可以自动解析当前类型
jd1["name"] = "小明";//JsonData自动推断类型,只要写属性名和属性值
jd1["age"] = 11;
//Debug.Log(jd1.ToJson());直接用ToJson转化成Json
JsonData jd2 = new JsonData();
jd2.SetJsonType(JsonType.Object);
jd2["name"] = "小红";
jd2["age"] = 10;
JsonData jds = new JsonData();
jds.SetJsonType(JsonType.Array);//储存两个对象,使用Array类型
jds.Add(jd1);//添加数组元素
jds.Add(jd2);
JsonData jd3 = new JsonData();//最外层的大括号
jd3.SetJsonType(JsonType.Object);
jd3["List"] = jds;
Debug.Log(jd3.ToJson());
}
{“List”:[{“name”:”\u5C0F\u660E”,”age”:11},{“name”:”\u5C0F\u7EA2”,”age”:10}]}读取
读取的方式是先创建Json字符串,然后解析成JsonData类,获取最外层的大括号,然后遍历获取属性控制台输出: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
28void fun2(){
JsonData jd1 = new JsonData();
jd1.SetJsonType(JsonType.Object);//一般常用的是JsonType.Array(数组,Json中的中括号)和JsonType.Object(字典,Json中的大括号)
//上句话不写也行,JsonData可以自动解析当前类型
jd1["name"] = "小明";//JsonData自动推断类型,只要写属性名和属性值
jd1["age"] = 11;
//Debug.Log(jd1.ToJson());直接用ToJson转化成Json
JsonData jd2 = new JsonData();
jd2.SetJsonType(JsonType.Object);
jd2["name"] = "小红";
jd2["age"] = 10;
JsonData jds = new JsonData();
jds.SetJsonType(JsonType.Array);//储存两个对象,使用Array类型
jds.Add(jd1);//添加数组元素
jds.Add(jd2);
JsonData jd3 = new JsonData();//最外层的大括号
jd3.SetJsonType(JsonType.Object);
jd3["List"] = jds;
Debug.Log(jd3.ToJson());
//解析
string jsonstr = jd3.ToJson();//提取Json字符串
JsonData jsons3D = JsonMapper.ToObject(jsonstr);//解析不需要限制泛型,因为类型都是JsonData
JsonData jd4 = jsons3D["List"];//最外层的大括号
foreach(JsonData js in jd4){
Debug.Log(js["name"].ToString()+ " "+(int)js["age"]);//里面的属性都是JsonData类型,所以需要转型
}
}
小明 11
小红 10
完整代码:##XML1
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
66using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LitJson;
public class JsonTest{
public string name;
public int age;
}
public class JsonList{
public JsonTest[] jsonlist;
}
public class LitJsons : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
fun1();
fun2();
}
void fun1(){
JsonTest jsontest1 = new JsonTest();
jsontest1.name = "小明";
jsontest1.age = 11;
JsonTest jsontest2 = new JsonTest();
jsontest2.name = "小红";
jsontest2.age = 10;
JsonList jsonlist = new JsonList();
jsonlist.jsonlist = new JsonTest[]{jsontest1,jsontest2};
string jsonstr = JsonMapper.ToJson(jsonlist);
Debug.Log(jsonstr);
//解析
JsonList listobject = JsonMapper.ToObject<JsonList>(jsonstr);
Debug.Log(listobject.jsonlist[0].name);//小明
}
void fun2(){
JsonData jd1 = new JsonData();
jd1.SetJsonType(JsonType.Object);//一般常用的是JsonType.Array(数组,Json中的中括号)和JsonType.Object(字典,Json中的大括号)
//上句话不写也行,JsonData可以自动解析当前类型
jd1["name"] = "小明";//JsonData自动推断类型,只要写属性名和属性值
jd1["age"] = 11;
//Debug.Log(jd1.ToJson());直接用ToJson转化成Json
JsonData jd2 = new JsonData();
jd2.SetJsonType(JsonType.Object);
jd2["name"] = "小红";
jd2["age"] = 10;
JsonData jds = new JsonData();
jds.SetJsonType(JsonType.Array);//储存两个对象,使用Array类型
jds.Add(jd1);//添加数组元素
jds.Add(jd2);
JsonData jd3 = new JsonData();//最外层的大括号
jd3.SetJsonType(JsonType.Object);
jd3["List"] = jds;
Debug.Log(jd3.ToJson());
//解析
string jsonstr = jd3.ToJson();//提取Json字符串
JsonData jsons3D = JsonMapper.ToObject(jsonstr);//解析不需要限制泛型,因为类型都是JsonData
JsonData jd4 = jsons3D["List"];//最外层的大括号
foreach(JsonData js in jd4){
Debug.Log(js["name"].ToString()+ " "+(int)js["age"]);//里面的属性都是JsonData类型,所以需要转型
}
}
}
XML的格式和HTML很像,有一个节点(如)和一个与之对应的节点(如 ),如下:一对节点里还可以包含其他节点,如下:1
<name>"小明"</name> //节点名称 name 节点值 "小明"
1
2<name><p>"小明"</p></name> //正确格式
<name>"小"<p>"明"</p></name>//错误格式
节点只能包含节点或值,不能两者同时包含
有时候节点不够用,于是XML还有属性:
1 | <name id="1">"小红"</name>//name后面的id就是节点的属性,**属性必须以键值对格式出现,并且属性值必须加上双引号""**,一个节点可以有多个属性 |
如果遇到只有属性值没有节点值的时候,可以简写,格式如下:
1 | //未简写的格式: |
在Assets文件夹下创建一个新的xml文档,命名为test.xml
1 | <!--声明文档类型和版本,编码--> |
XML解析
创建XMLTest脚本,添加命名空间:
1 | using System.Xml;//引用XML命名空间 |
1 | using System.Collections; |
注意!在解析时,不要在XML文件里添加任何注释或其他类型的东西!比如:注释类型为System.Xml.Comment,在转化成XmlElement时,FirstChild检测到的是注释!所以会报NullReferenceException,而在遍历节点时也会NullReferenceException,所以尽量不要使用注释!
必要时,可以采用如下方案:
1 | foreach(object storyEle in booksEle.ChildNotes){ |
打印XML结果:
XML解析·二
XPath
XPath是一种路径语法,拿test.xml举例,XPath语法下的test.xml路径如下:
root/books/story/name
第二种方法依靠的就是XPath语法,内容如下:
1 | void ParseXML2(){ |
运行结果:
![XPath](https://www.freeimg.cn/i/2024/03/24/65ffa32e88fc2.png)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
XPath:
路径语法:
例如:
/root/books/story/name
相对路径: //name 直接存放name节点
结合:
//books/story/name 通过相对路径找到books,再通过绝对路径找到name
寻找单个节点:
//booke/story[2]/name 找到第二个story节点下的name
//books/story[last()-1]/name 找到倒数第二个story节点下的name
//books/story[position()<3]/name 获取前两个story节点下的name
寻找拥有指定属性的节点:
//books/story[@id]/name 只获取有id属性的story节点下的name
寻找拥有指定id值的节点:
//books/story[@id=2]/name 只获取有id属性且id值为2的story节点下的name
*/
### xml创建
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
void CreateXML(){
/// 创建XML
//创建文档类
XmlDocument doc = new XmlDocument();
//创建文档声明
XmlDeclaration dec = doc.CreateXmlDeclaration("1.0","utf-8","");
//文档声明类 XmlDeclaration 用文档类的CreateXmlDeclaration方法创建文档声明
doc.AppendChild(dec);//把文档声明添加到文档里
//root节点
XmlElement rootEle = doc.CreateElement("root");
//用文档类的CreateElement方法创建root元素
doc.AppendChild(rootEle);//把root添加到文档里
//books节点
XmlElement booksEle = doc.CreateElement("books");
rootEle.AppendChild(booksEle);//把books节点添加到root节点里
//循环数据(name)
string[] names = new string[]{"北京的春节","腊八粥","藏戏"};
//循环创建节点
for(int i=0;i<3;i++){
XmlElement storyele = doc.CreateElement("story");
booksEle.AppendChild(storyele);
//name节点
XmlElement nameEle = doc.CreateElement("name");
nameEle.InnerText = names[i];//赋值
storyele.AppendChild(nameEle);
//添加属性
storyele.SetAttribute("id",i+ "");//添加属性,属性名:id,值:i 属性值必须是字符串
/*创建属性方法2:
XmlAttribute att = doc.CreateAttribute("id");//创建id属性
att.value = i + "";//属性值为i,属性值必须是字符串
storyEle.Attrbutes.Append(att);//添加属性至story
*/
}
//保存xml文件
doc.Save(Application.dataPath + "/XML/test2.xml");
}
保存后运行,几秒后Assets下的XML文件夹会出现test2.xml文件,文件内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<root>
<books>
<story id="0">
<name>北京的春节</name>
</story>
<story id="1">
<name>腊八粥</name>
</story>
<story id="2">
<name>藏戏</name>
</story>
</books>
</root>
生成的与编写的XML文件大致一样
拓展·RSS
RSS:简易信息聚合(也叫聚合内容)是一种基于XML的标准,在互联网上被广泛采用的内容包装和投递协议。RSS(Really Simple Syndication)是一种描述和同步网站内容的格式,是使用最广泛的XML应用。RSS搭建了信息迅速传播的一个技术平台,使得每个人都成为潜在的信息提供者。发布一个RSS文件后,这个RSS Feed中包含的信息就能直接被其他站点调用,而且由于这些数据都是标准的XML格式,所以也能在其他的终端和服务中使用,是一种描述和同步网站内容的格式。 [1]RSS可以是以下三个解释的其中一个: Really Simple Syndication;RDF (Resource Description Framework) Site Summary; Rich Site Summary。但其实这三个解释都是指同一种Syndication的技术。
RSS广泛用于网上新闻频道,blog和wiki,主要的版本有0.91, 1.0, 2.0。使用RSS订阅能更快地获取信息,网站提供RSS输出,有利于让用户获取网站内容的最新更新。网络用户可以在客户端借助于支持RSS的聚合工具软件,在不打开网站内容页面的情况下阅读支持RSS输出的网站内容。