Unity数据持久化之XML

news2024/11/20 20:37:03

目录

  • 数据持久化XML概述
  • XML文件格式
    • XML基本语法
    • XML属性
  • C#读取存储XML
    • XML文件存放位置
    • C#读取XML文件
    • C#存储XML文件
  • 实践小项目
    • 必备知识点
      • XML序列化(不支持字典)
      • XML反序列化
      • IXmlSerializable接口
      • 让Dictionary支持序列化反序列化

数据持久化XML概述

什么是数据持久化
数据持久化就是将内存中的数据模型转换为存储模型,以及
将存储模型转换为内存中的数据模型的统称

说人话:将游戏数据存储到硬盘,硬盘中数据读取到游戏
中,也就是传统意义上的存盘

XML是什么?
全称:可拓展标记语言(EXtensible Markup Language)
XML是国际通用的
它是被设计来用于传输和存储数据的一种文本特殊格式
文件后缀一般为.xml
如何理解不同后缀的文件

文件后缀名决定了文件的格式
不同的软件可以根据后缀名用来判断文件的类型
并且在打开文件时以特定的读取规则去解析它

文件后缀名是人为定的规则
可以有无数种,可以自定义
我们一般使用XML文件来记录和传输数据
XML文档就是使用XML格式配置填写的文档
后缀一般为.xml
我们在游戏中可以把游戏数据按照XML的格式标准
存储在XML文档中,再将XML文档存储在硬盘上或者传输给远端
达到数据持久化的目的

XML文件格式

XML基本语法

推荐xml文本编辑器

只要能打开文档的软件都能打开XML文件
常用的一些打开XML文件的方式
1.系统自带 – 记事本、写字板
2.专用XML文本编辑器 – XMLSpear、STDU XML Editor等等
3.通用文本编辑器 – Sublime Text等等
在这里插入图片描述
创建XML文件

直接右键创建文本,后缀改为xml
选择自己喜欢的用于编辑xml的软件
进行文本编辑
注释

<!---->
<!--在其中写明注释内容-->

固定内容
固定内容代表xml的版本 使用的编码

<?xml version="1.0" encoding="UTF-8"?>

基本语法

<!-- xm1的基本语法 就是<元素标签></元素标签>配对出现 -- >
<PlayerInfo>
	<name>唐老狮</name>
	<age>18</age>
	<sex>true</sex>
	<ItemList>
		<Item>
			<id>1</id>
			<num>10</num>
		</Item>
		<Item>
			<id>2</id>
			<num>10</num>
		</Item>
	</ItemList>
</PlayerInfo>

基本规则

1.每个元素都必须有关闭标签
2.元素命名规则基本遵照C#中变量名命名规则
3.XML标签对大小写敏感
4.XML文档必须有根元素
5.特殊的符号应该用实体引用
&lt —— <小于
&gt —— >大于
&amp——&和号
&apos——'单引号
&quot——“引号

XML属性

<!-- 属性就是在元素标签后面空格 添加的内容 -- >
<!-- 注意:属性必须用引号包裹 单引号双引号都可以 -- >
<Friend name="小明”age='8'>我的朋友</Friend>
<!-- 如果使用属性记录信息 不想使用元素记录 可以如下这样写 -- >
<Fater name ="爸爸”age ="50"/>

如何查语法错误

1.元素标签必须配对
2.属性必须有引号
3.注意命名

或者直接复制到https://www.runoob.com/xml/xml-validator.html
网页上进行验证

注意:一般专门编辑xml的软件都会有判错功能

C#读取存储XML

XML文件存放位置

1.只读不写的XML文件可以放在Resources或者StreamingAssets 文件夹下

2.动态存储的XML文件放在Application.persistentDataPath 路径下

C#读取XML文件

C#读取XML的方法有几种

1.XmlDocument:把数据加载到内存中,方便读取
2.XmlTextReader:以流形式加载,内存占用更少,但是是单向只读,使用不是特别方便,除非有特殊需求,否则不会使用
3.Linq:以后专门讲Ling的时候讲
使用XmlDocument类读取是较方便最容易理解和操作的方法
读取xml文件信息

XmlDocument xml = new XmlDocument();

通过XmlDocument读取xml文件 有两个API
1.直接根据xm1字符串内容 来加载xml文件
存放在Resorces文件夹下的xml文件加载处理

TextAsset asset = Resources.Load<TextAsset>("TestXml");
print(asset.text);
//通过这个方法 就能够翻译字符串为xml对象
xml.LoadXml(asset.trxt));

2.是通过xml文件的路径去进行加载

xml. Load(Application.streamingAssetsPath + "/TestXml.xml");

读取元素和属性信息
节点信息类
XmlNode 单个节点信息类
节点列表信息
XmlNodeList 多个节点信息类
在这里插入图片描述

获取xml中的根节点

xmlNode root = xml.SelectSingleNode("Root");

再通过根节点获取下面的子节点

XmlNode nodeName = root.SelectSingleNode("name");

获取节点包裹的元素信息

print(nodeName.InnerText);

获取属性

//两种方式
XmlNode nodeItem = root.SelectSingleNode("Item");
print(nodeItem.Attributes["id"].Value);
print(nodeItem.Attributes.GetNameItem("id").Value);

获取一个节点下的同名节点

XmlNodeList friendList = root.SelectNodes("Friend");
//迭代器遍历
foreach(XmlNode item in friendList)
{

}
//for循环遍历
for(int i = 0;i < friendList.Count;i++)
{
	print(friendList[i].SelectSingleNode("name").InnerText);
}

总结
1.读取XML文件
XmlDocument xml = new XmlDocument();
读取文本方式1-xml.LoadXml(传入xml文本字符串)
读取文本方式2-xml.Load(传入路径)

2.读取元素和属性
获取单个节点:XmlNode node=xml.SelectSingleNode(节点名)
获取多个节点:XmlNodeList nodeList = xml.SelectNodes(节点名)

获取节点元素内容:node.InnerText
获取节点元素属性:
1.item.Attributes[“属性名”].Value
2.item.Attributes.GetNamedItem(“属性名”).Value

通过迭代器遍历或者循环遍历XmlNodeList对象 可以获取到各单个元素节点

C#存储XML文件

决定存储在哪个文件夹下
注意:存储xml文件 在Unity中一定是使用各平台都可读可写可找到的路径
1,Resources 可读 不可写 打包后找不到 ×
2.Application.streamingAssetsPath 可读 PC端可写 找得到 ×
3.Application.dataPath 打包后找不到 ×
4.Application.persistentDataPath 可读可写找得到 √

string path = Application.persistentDataPath + "/PlayerInfo2.xml";
print(Application.persistentDataPath);

存储xml文件
关键类 XmlDocument 用于创建节点 存储文件
关键类 XmlDeclaration 用于添加版本信息
关键类 XmlElement 节点类

存储有5步
1.创建文本对象

XmlDocument xml = new XmlDocument();

2.添加固定版本信息

XmlDeclaration xmlDec = xml.CreateXmlDeclaration("1.0","UTF-8","");
xml.AppendChild(xmlDec);

3.添加根节点

XmlElement root = xml.CreateElement("Root");
xml.AppendChild(root);

4.为根节点添加子节点

XmlElement name = xml.CreateElement("name");
name.InnerText = "chx";
root.AppendChild(name);
XmlElement itemList = xml.CreateElement("itemList");
itemList.SetAttribute("id",i.ToString());//添加属性
root.AppendChild(itemList);

5.保存

xml.Save(path);

修改xml文件
1.先判断是否存在文件

if(File.Exists(path))
{
}

2.加载后 直接添加节点 移除节点即可

XmlDocument newXml = new XmlDocument();
newXml.Load(path);
XmlNode node = newXml.SelectSingleNode("Root").SelectSingleNode("atk");
//或者
XmlNode node = newXml.SelectSingleNode("Root/atk");
//得到自己的父节点
XmlNode root2 = newXml.SelectSingleNode("Root");
//移除子节点方法
root2. RemoveChild(node);
//添加节点
XmlElement speed = newXml.CreateElement("moveSpeed");
speed.InnerText = "20";
root2.AppendChild(speed);
//保存
newXml.Save(path);

实践小项目

必备知识点

XML序列化(不支持字典)

什么是序列化和反序列化
序列化:把对象转化为可传输的字节序列过程称为序列化
反序列化:把字节序列还原为对象的过程称为反序列化

说人话:
序列化就是把想要存储的内容转换为字节序列用于存储或传递
反序列化就是把存储或收到的字节序列信息解析读取出来使用
xml序列化

public class Lesson1Test
{
	public int testPublic = 10;
	private int testPrivate = 11;
	protected int testProtected = 12;
	internal int testInternal = 13;

	public string testPUblicStr = "123";


	public int testPro { get; set; }

	public Lesson1Test2 testClass = new Lesson1Test2();
}
public class Lesson1Test2
{
	public int test1 = 1;
	public float test2 = 1.1f;
	public bool test3 = true;
}

1.第一步准备一个数据结构类

Lesson1Test lt = new Lesson1Test();

2.进行序列化
关键知识点
XmlSerializer 用于序列化对象为xml的关键类
Streamwriter 用于存储文件
using 用于方便流对象释放和销毁
第一步:确定存储路径

string path = Application.persistentDataPath + "/Lesson1Test.xml";

第二步:结合 using知识点 和 Streamwriter这个流对象 来写入文件
括号内的代码:写入一个文件流 如果有该文件 直接打开并修改 如果没有该文件 直接新建一个文件
using 的新用法 括号当中包裹的声明的对象 会在 大括号语句块结束后 自动释放掉。当语句块结束,会自动帮助我们调用对象的Dispose方法,让其进行销毁。using一般都是配合内存占用比较大或者有读写操作时进行使用的。

using(StreamWriter stream = new StreamWriter(path))
{
	//第三步:进行xml文件序列化
	XmlSerializer s = new XmlSerializer(typeof(Lesson1Test));
	//这句代码的含义 就是通过序列化对象 对我们类对象进行翻译 将其翻译成		我们的xml文件 写入到对应的文件中
	//第一个参数:文件流对象
	//第二个参数:想要备翻译 的对象
	//注意:翻译机器的类型 一定要和传入的对象是一致的 不然会报错
	s.Serialize(stream, lt);
}

存储效果:
在这里插入图片描述
自定义节点名或设置属性
可以通过特性设置节点或者设置属性并且修改名字

[XmlAttribute("Test1")]
public int test1 = 1;
[XmlElement("testPublic123123")]
public int testPublic = 10;

XML反序列化

判断文件是否存在

string path = Application.persistentDataPath + "/Lesson1Test.xml";
if(File.Exists(path))
{
}

反序列化
关键知识
1.using 和 StreamReader
2.XmlSerializer的Deserialize反序列化方法

using(StreamReader reader = new StreamReader(path))
{
	XmlSerializer s = new XmlSerializer(typeof(Lesson1Test));
	Lesson1Test lt = s.Deserialize(reader) as Lesson1Test;
}

List对象如果有默认值,反序列化时,不会清空,会往后面添加。

IXmlSerializable接口

IXmlSerializable是什么
c#的XmlSerializer 提供了可拓展内容
可以让一些不能被序列化和反序列化的特殊类能被处理
让特殊类继承 IXmlSerializable 接口 实现其中的方法即可
自定义类实践

//继承接口
public class TestLesson3 : IXmlSerializable
{
	public int test1;
	public string test2;
	//返回结构
	public XmlSchema GetSchema()
	{
		return null;
	}	
	//反序列化自动调用
	public void ReadXml(XmlReader reader)
	{
		//在里面可以自定义反序列化 的规则
		//1.读属性
		this.test1 = int.Parse(reader["test1"]);
		this.test2 = reader["test2"];
		//2.读节点
		reader.Read();//这时是读到的test1节点
		reader.Read();//这时是读到的test1节点包裹的内容
		this.test1 = int.Parse(reader.Value);
		//3.读包裹元素节点
		XmlSerializer s = new XmlSerializer(typeof(int));
		//跳过根节点
		reader.Read();
		reader.ReadStartElement("test1");
		test1 = (int)s.Deserialize(reader);
		reader.ReadEndElement();
		//方式二:
		while(reader.Read())
		{
			if( reader.NodeType == XmlNodeType.Element )
			{
				switch (reader.Name)
				{
					case "test1":
						reader.Read();
						this.test1 = int.Parse(reader.Value);
							break;
					case "test2":
						reader.Read();
						this.test2 = reader.Value;
						break;
				}
			}
		}
	}
	//序列化时自动调用
	public void WriteXml(XmlWriter writer)
	{
		//在里面可以自定义序列化 的规则

		//如果要自定义 序列化的规则 一定会用到 Xmlwriter中的一些方法 来进行序列化
		//1.写属性
		writer.WriteAttributeString("test1", this.test1.ToString());
		writer.WriteAttributeString("test2", this.test2);
		//2.写节点
		writer.WriteElementString("test1", this.test1.ToString());
		writer.WriteElementString("test2", this.test2);
		//3.写包裹节点
		XmlSerializer s = new XmlSerializer(typeof(int));
		writer.WriteStartElement("test1");
		s.Serialize(writer, test1);
		writer.WriteEndElement();
	}
}
//序列化
TestLesson3 t = new TestLesson3();
string path = Application.persistentDataPath + "/TestLesson3.xml";
using(StreamWriter writer = new StreamWriter(path))
{
	XmlSerializer s = new XmlSerializer(typeof(TestLesson3));
	s.Serialize(writer,t);
}
//反序列化
using (StreamReader reader = new StreamReader(path))
{
	//序列化“翻译机器”
	XmlSerializer s= new XmlSerializer(typeof(TestLesson3));
	TestLesson3 t2 = s.Deserialize(reader) as TestLesson3;
}

让Dictionary支持序列化反序列化

思考如何让Dictionary支持xml序列和反序列化
1.我们没办法修改C#自带的类
2.那我们可以重写一个类 继承Dictionary 然后让这个类继承序列化拓展接口IXmlSerializable
3.实现里面的序列化和反序列化方法即可
SerizlizerDictionary.cs

using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using UnityEngine;

public class SerizlizerDictionary<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.EndElement)
        {
            //反序列化键
            TKey key = (TKey)keySer.Deserialize(reader);
            //反序列化值
            TValue value = (TValue)valueSer.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);
        }
    }
}

Test.cs

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using UnityEngine;

public class TestLesson4
{
    public int test1;

    public SerizlizerDictionary<int, string> dic;
}

public class Lesson4 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        #region 知识点二 让Dictionary支持序列化和反序列化
        TestLesson4 tl4 = new TestLesson4();
        //tl4.dic = new SerizlizerDictionary<int, string>();
        //tl4.dic.Add(1, "123");
        //tl4.dic.Add(2, "234");
        //tl4.dic.Add(3, "345");
        string path = Application.persistentDataPath + "/TestLesson4.xml";
        //using(StreamWriter writer = new StreamWriter(path))
        //{
        //    XmlSerializer s = new XmlSerializer(typeof(TestLesson4));
        //    s.Serialize(writer, tl4);
        //}

        using (StreamReader reader = new StreamReader(path))
        {
            XmlSerializer s = new XmlSerializer(typeof(TestLesson4));
            tl4 = s.Deserialize(reader) as TestLesson4;
        }
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1657005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker入门指南:Docker镜像的使用(二)

&#x1f340; 前言 博客地址&#xff1a; CSDN&#xff1a;https://blog.csdn.net/powerbiubiu &#x1f44b; 简介 在本章节中&#xff0c;将深入探讨 Docker 镜像的概念&#xff0c;以及如何使用镜像的一系列操作。 &#x1f4d6; 正文 1 什么是镜像 1.1 Docker镜像的简…

cocos=》带你全面、系统的了解周期函数(含源码分析)

目录 简介 第一 初步了解周期函数 第二 进一步认识周期函数 一、结合节点树来了解一下周期函数 二、节点激活、脚本组件启用 三、node.parent、setParent、addChild 与 周期函数 四、addComponent 与周期函数 五、exectionOrder 与 周期函数 第三 从源码中 学习周期函…

科学碳目标(SBTI)认证是什么?

科学碳目标&#xff08;SBTI&#xff09;认证是一种基于科学的减排目标认证和监测体系&#xff0c;旨在确保企业和国家制定的减排目标符合科学标准&#xff0c;并且能够实现全球气候目标的减缓效应。这个认证体系由全球碳项目和世界资源研究所&#xff08;WRI&#xff09;共同开…

如何判断自己是不是强迫型人格障碍?

什么是强迫型人格障碍&#xff1f; 强迫型人格&#xff0c;也叫强迫固执型人格&#xff0c;当某些强迫型行为严重影响到正常的生活&#xff0c;工作和人际关系&#xff0c;且具有长期稳定的持续性特征&#xff0c;即是强迫型人格障碍。 这类思维和行为特征可以概括为&#x…

leetcode 1235

leetcode 1235 代码 class Solution { public:int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {int n startTime.size();vector<vector<int>> jobs(n);for(int i0; i<n; i){jobs[i] …

【Anaconda】升级Anaconda Navigator提示JSONDecoderError,删除.condarc文件后搞定

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、报错&#xff1a;JSONDecoderError二、错误原因三、解决问题总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 时间长未升级Ana…

本地搭建springboot服务并实现公网远程调试本地接口

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

2024/5/9 QTday4

完成定时器制作 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);connect(&timer2, &QTimer::timeout, this, &Widget::label_begin);connect(&…

数据库(MySQL)—— 索引

数据库&#xff08;MySQL&#xff09;—— 索引 什么是索引创建索引使用 CREATE INDEX 语句使用 ALTER TABLE 语句在创建表时定义索引特殊类型索引注意事项 举个例子无索引的情况有索引的情况为什么索引快索引的结构 今天我们来看看MySQL中的索引&#xff1a; 什么是索引 MyS…

0509_IO4

练习1&#xff1a; 创建一对父子进程&#xff1a; 父进程负责向文件中写入 长方形的长和宽 子进程负责读取文件中的长宽信息后&#xff0c;计算长方形的面积 1 #include <stdio.h>2 #include <string.h>3 #include <stdlib.h>4 #include <sys/types.h>…

PyCharm安装详细教程

PyCharm安装详细教程 PyCharm简介及其下载网站 PyCharm是由JetBrains打造的一款Python IDE(Integrated Development Environment&#xff0c;集成开发环境)&#xff0c;带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具。PyCharm提供了代码编辑、调试、语法高亮…

【BUUCTF】[RoarCTF 2019]Easy Java1

工具&#xff1a;hackbar发包&#xff0c;bp抓包。 解题步骤&#xff1a;【该网站有时候send不了数据&#xff0c;只能销毁靶机重试】 这里的登录界面是个天坑【迷魂弹】 直接点击help&#xff0c;然后进行打开hackbar——通过post请求&#xff0c;再通过bp抓包&#xff0c;…

DetCLIPv3:面向多功能生成开放词汇的目标检测

DetCLIPv3:面向多功能生成开放词汇的目标检测 摘要IntroductionRelated worksMethod DetCLIPv3: Towards Versatile Generative Open-vocabulary Object Detection 摘要 现有的开词汇目标检测器通常需要用户预设一组类别&#xff0c;这大大限制了它们的应用场景。在本文中&…

长难句打卡5.9

For example, the Long Now Foundation has as its flagship project a mechanical clock that is designed to still be marking time thousands of years hence. 例如,今日永存资金会将机械钟表视为旗舰项目,因此该钟表旨在为未来几千年保持计时。 Foundation n.基金会flag…

如何快速注册企业邮箱?只需要三步

快速注册一个企业邮箱&#xff0c;只需要以下三个步骤&#xff1a;一是挑选适合的邮箱版本和邮箱价格&#xff0c;二是填写必要的企业信息&#xff0c;三是完成企业邮箱的基础配置。完成上述三个步骤&#xff0c;企业就能够拥有一个专属的企业邮箱。 一、挑选适合的邮箱版本 …

jpg照片怎么压缩到10k?压缩照片并不难

jpg照片怎么压缩到10k&#xff1f;随着数字摄影的普及&#xff0c;我们手机或电脑中存储的照片数量越来越多&#xff0c;而这些高分辨率的照片往往会占用大量的存储空间。为了节省空间&#xff0c;将JPG照片压缩到较小的文件大小成为了许多人的需求。本文将为您介绍几款可以将J…

《基于GNU-Radio和USRP的雷达通信系统的实现》文献阅读

文章目录 前言一、摘要二、引言三、联合系统实施1、基本原理2、实验方案 四、软件设置1、发射机2、接收机 五、实验结果1、实验设置2、波形3、室内外对比4、不同参数的结果 六、结论七、参考文献八、论文自取九、阅读收获 前言 本文记录《基于GNU-Radio和USRP的雷达通信系统的实…

新手做抖音小店,应该怎么做才挣钱?按照这个思路来!

大家好&#xff0c;我是电商糖果 别人在抖音创业开店&#xff0c;都可以赚到钱&#xff0c;他们是怎么做到的。 作为一个新手&#xff0c;应该怎么做呢&#xff1f; 我相信这是很多人想开店的朋友&#xff0c;前期都会思考的问题。 下面糖果就来说一下&#xff0c;新手做抖…

vue 代码样式问题

部分电脑存在样式错乱问题&#xff0c;部分电脑样式正常。最后发现是样式写在 el-col 里面导致的。 注意&#xff1a;写样式不要放在 el-row 或者 el-row &#xff0c;导致部分电脑会出现莫名其妙的样式问题 <el-row class"detail"><el-col class"it…

达梦数据库限制用户登录IP测试

达梦数据库创建用户时可以限制登录ip和时间段。 创建测试测试用户 create user test1 identified by Test_1234 ALLOW_IP "192.168.100.101"; 限定该用户只能通过192.168.100.101地址登录数据库 连接测试 上图可见&#xff0c;192.168.100.101客户端可以连接上19…