在 Unity 中简单地应用 XML 实现数据持久化
本文最后更新于42 天前,其中的信息可能已经过时,如有错误请发送邮件到1077511618@qq.com

在 Unity 中简单地应用 XML 实现数据持久化

通过鄙人先前发布的XML入门笔记,现在已经对XML的语法规则、注意事项等有了初步了解,是时候尝试将其应用于我们Unity的实际开发中了

示例项目目标

  1. 能进行对用户账户信息的读取以及打印输出;
  2. 能进行新用户的注册;
  3. 能进行对用户的注销;
  4. 实现数据更新及持久化。

    开始

    准备好存储用户账户信息的XML文件

    示例:

    <!-- Unity XML测试 (test.xml) -->
    <users>
    <user account="TokaiTeio" password="1993"></user>
    <user account="MejiroMcQueen" password="1992"></user>
    <user account="SpecialWeek" password="1998"></user>
    <user account="ScilenceSuzuka" password="1991"></user>
    </users>

    有了XML文件后,我们可以将其拖入Unity编辑器中的内容区,这样我们的XML文件就成为了一个 TextAsset:

    导入XML文件

    准备测试代码编写

    新创建一个C#脚本,将其命名为 XMLTest:

    新建C#脚本

    为了实现对XML文件的各种操作,我们需要引入以下两个命名空间:

    using System.Xml;
    using System.IO;

    C#中XML节点相关的类

    XML 文档本质上是一棵树,树上的每一个”点”都叫节点(Node),有关于XML的节点有以下几种常见类型:

  5. XmlNode:所有节点的基类,代表 XML 树里任意一个节点;
  6. XmlElement:XmlNode的子类,专门代表元素节点(例如、这种标签);
  7. XmlText:文本节点,代表元素里的纯文本内容;
  8. XmlComment:注释节点,代表XML里面的注释;
  9. XmlAttribute:属性节点,代表元素的属性;
  10. XmlDocument:文档节点,代表整个XML文档,是整棵树的根,也是操作入口;
  11. ……
XML节点的共有属性/方法: 成员 含义
Name 节点名称
Value 节点值
InnerText 节点内所有文本
ChildNodes 子节点列表
ParentNode 父节点
Attributes 属性集合(部分节点才有)
AppendChild() 追加子节点
RemoveChild() 移除子节点
SelectSingleNode() XPath 查单个
SelectNodes() XPath 查多个

编写测试脚本

迈出简单的第一步————读取并打印XML文件中的数据

示例代码:

using UnityEngine;
using System.Xml;
using System.IO;
using System.Text;

public class XMLTest : MonoBehaviour
{
    public TextAsset xmlFile;
    private XmlDocument xmlDocument;

    void Start()
    {
        if (xmlFile == null) return;

        xmlDocument = new XmlDocument();
        // 使用 StringReader 读取 TextAsset 的文本
        using (StringReader reader = new StringReader(xmlFile.text))
        {
            xmlDocument.Load(reader);
        }

        // 获取根节点下的所有子节点
        XmlNodeList nodes = xmlDocument.SelectSingleNode("users").ChildNodes;
        foreach (XmlElement item in nodes)
        {
            string account = item.GetAttribute("account");
            string password = item.GetAttribute("password");
            Debug.Log("Account: " + account + " Password: " + password);
        }
    }
}

保存并编译代码后运行项目,可以看到控制台输出了从文件中读取到的账户信息:

控制台输出示例

实现账户注册功能

更改文件读取方式

在 Unity 中,TextAsset 是只读的,所以这里开始我们需要进行修改,从持久化路径创建并读取文件
将上面的代码进行修改,示例(为了将两种读取文件方式进行一同展示并方便文件创建,我保留了从TextAsset读取的方法并直接将我们刚刚添加TextAsset文件进行复制):

// 修改后的代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Xml;
using System.IO;
using System.Text;
using Org.BouncyCastle.Asn1.X509;
using System.Threading.Tasks;

public class XMLTest : MonoBehaviour
{
    public TextAsset xmlFile;
    [Header("XML文件读取方式")]
    [Tooltip("勾选使用TextAsset的方式仅可读取文件,无法进行写入操作!")]
    public bool UseTextAsset = true;
    [Header("准备注册的用户信息")]
    public string newAccount;
    public string newPassword;
    [Header("待删除的用户信息")]
    public string userAccount;

    private string savePath;
    private XmlDocument xmlDocument;

    void Start()
    {
        savePath = Path.Combine(Application.persistentDataPath , "users.xml");
        Debug.Log("存档路径:" + savePath);

        CopyTextAssetToPersistent();

        if (UseTextAsset)
        {
            if (xmlFile == null) return;

            xmlDocument = new XmlDocument();
            // 使用 StringReader 读取 TextAsset 的文本
            using (StringReader reader = new StringReader(xmlFile.text))
            {
                xmlDocument.Load(reader);
            }

            // 获取根节点下的所有子节点
            XmlNodeList nodes = xmlDocument.SelectSingleNode("users").ChildNodes;
            foreach (XmlElement item in nodes)
            {
                string account = item.GetAttribute("account");
                string password = item.GetAttribute("password");
                Debug.Log("Account: " + account + " Password: " + password);
            }
        }

        else if (!UseTextAsset)
        {
            // 读取文件(如果无法读取就新建)
            xmlDocument = LoadXmlFile();

            // 打印文件内容
            foreach (XmlElement item in xmlDocument.SelectNodes("users/user"))
            {
                string account = item.GetAttribute("account");
                string password = item.GetAttribute("password");
                Debug.Log("Account: " + account + " Password: " + password);
            }
        }
    }

    // 从保存路径中加载文件,如果文件不存在则进行创建
    XmlDocument LoadXmlFile()
    {
        xmlDocument = new XmlDocument();
        if (File.Exists(savePath))
        {
            xmlDocument.Load(savePath);
        }
        else
        {
            // 创建根节点 <users>
            XmlElement rootNode = xmlDocument.CreateElement("users");
            xmlDocument.AppendChild(rootNode);
        }
        return xmlDocument;
    }

    // 将已有文件进行拷贝
    void CopyTextAssetToPersistent()
    {
        if (File.Exists(savePath))
        {
            Debug.Log("存档已存在,跳过初始化");
            return;
        }

        // 直接写入文本的简单方式
        File.WriteAllText(savePath , xmlFile.text , new UTF8Encoding(false));
        Debug.Log("已经从 TextAsset 初始化存档:" + savePath);
    }
}

添加账户注册功能

在我们的示例项目中,添加账户注册功能本质是将新的账户信息写入XML文件,所以可以将相关方法添加到类中,这里给出代码示例:

// 在程序结束时保存更新 XML 文件
void OnDestroy()
{
    SaveFile();
}

// 将以下两个方法添加到 XMLTest 类下:
// 由于直接给 xmlDocument 添加元素没有涉及到对磁盘中文件的实际操作,所以在添加元素后我们需要在程序结束时进行写入保存
void SaveFile()
{
    var settings = new XmlWriterSettings
    {
        Indent = true,
        IndentChars = "     ",
        Encoding = new UTF8Encoding(false)
    };

    using(XmlWriter writer = XmlWriter.Create(savePath , settings))
    {
        xmlDocument.Save(writer);
    }
    Debug.Log("用户信息文件已保存:" + savePath);
}

// 新增用户
[ContextMenu("注册用户")]
public void RegisterNewUser()
{
    XmlElement newUser = xmlDocument.CreateElement("user");
    newUser.SetAttribute("account" , newAccount);
    newUser.SetAttribute("password" , newPassword);

    xmlDocument.DocumentElement.AppendChild(newUser);

    Debug.Log("已新增用户信息:nAccount: " + newAccount + " Password: " + newPassword);
}

实现账户注销功能

System.Xml 库中提供了删除节点的方法,可以参考以下示例:

// 将以下方法添加到 XMLTest 类中:
// 删除用户
[ContextMenu("注销用户")]
public void DeletUser()
{
    XmlNode targetNode = null;
    // 查询待删除用户是否存在
    bool isUserExit = false;
    XmlNodeList nodes = xmlDocument.SelectSingleNode("users").ChildNodes;
    foreach (XmlElement item in nodes)
    {
        if (item.GetAttribute("account") == userAccount)
        {
            isUserExit = true;
            targetNode = item;
            break;
        }
    }

    // 删除用户操作
    if (!isUserExit)
    {
        Debug.Log("注销用户失败:无法找到用户账号" + userAccount);
    }
    else
    {
        targetNode.ParentNode.RemoveChild(targetNode); // 通过获取到的目标节点,反向找出其父节点并调用父节点的 RemoveChild 方法移除此目标节点
        Debug.Log("成功注销用户:" + userAccount);
    }
}

完成后的项目示例

完整代码示例

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Xml;
using System.IO;
using System.Text;
using Org.BouncyCastle.Asn1.X509;
using System.Threading.Tasks;

public class XMLTest : MonoBehaviour
{
    public TextAsset xmlFile;
    [Header("XML文件读取方式")]
    [Tooltip("勾选使用TextAsset的方式仅可读取文件,无法进行写入操作!")]
    public bool UseTextAsset = true;
    [Header("准备注册的用户信息")]
    public string newAccount;
    public string newPassword;
    [Header("待删除的用户信息")]
    public string userAccount;

    private string savePath;
    private XmlDocument xmlDocument;

    void Start()
    {
        savePath = Path.Combine(Application.persistentDataPath , "users.xml");
        Debug.Log("存档路径:" + savePath);

        CopyTextAssetToPersistent();

        if (UseTextAsset)
        {
            if (xmlFile == null) return;

            xmlDocument = new XmlDocument();
            // 使用 StringReader 读取 TextAsset 的文本
            using (StringReader reader = new StringReader(xmlFile.text))
            {
                xmlDocument.Load(reader);
            }

            // 获取根节点下的所有子节点
            XmlNodeList nodes = xmlDocument.SelectSingleNode("users").ChildNodes;
            foreach (XmlElement item in nodes)
            {
                string account = item.GetAttribute("account");
                string password = item.GetAttribute("password");
                Debug.Log("Account: " + account + " Password: " + password);
            }
        }

        else if (!UseTextAsset)
        {
            // 读取文件(如果无法读取就新建)
            xmlDocument = LoadXmlFile();

            // 打印文件内容
            foreach (XmlElement item in xmlDocument.SelectNodes("users/user"))
            {
                string account = item.GetAttribute("account");
                string password = item.GetAttribute("password");
                Debug.Log("Account: " + account + " Password: " + password);
            }
        }
    }

    void OnDestroy()
    {
        SaveFile();
    }

    XmlDocument LoadXmlFile()
    {
        xmlDocument = new XmlDocument();
        if (File.Exists(savePath))
        {
            xmlDocument.Load(savePath);
        }
        else
        {
            // 创建根节点 <users>
            XmlElement rootNode = xmlDocument.CreateElement("users");
            xmlDocument.AppendChild(rootNode);
        }
        return xmlDocument;
    }

    void CopyTextAssetToPersistent()
    {
        if (File.Exists(savePath))
        {
            Debug.Log("存档已存在,跳过初始化");
            return;
        }

        // 直接写入文本的简单方式
        File.WriteAllText(savePath , xmlFile.text , new UTF8Encoding(false));
        Debug.Log("已经从 TextAsset 初始化存档:" + savePath);
    }

    void SaveFile()
    {
        var settings = new XmlWriterSettings
        {
            Indent = true,
            IndentChars = "     ",
            Encoding = new UTF8Encoding(false)
        };

        using(XmlWriter writer = XmlWriter.Create(savePath , settings))
        {
            xmlDocument.Save(writer);
        }
        Debug.Log("用户信息文件已保存:" + savePath);
    }

    #region 公共方法(增删改)
    // 新增用户
    [ContextMenu("注册用户")]
    public void RegisterNewUser()
    {
        XmlElement newUser = xmlDocument.CreateElement("user");
        newUser.SetAttribute("account" , newAccount);
        newUser.SetAttribute("password" , newPassword);

        xmlDocument.DocumentElement.AppendChild(newUser);

        Debug.Log("已新增用户信息:nAccount: " + newAccount + " Password: " + newPassword);
    }

    // 删除用户
    [ContextMenu("注销用户")]
    public void DeletUser()
    {
        XmlNode targetNode = null;
        // 查询待删除用户是否存在
        bool isUserExit = false;
        XmlNodeList nodes = xmlDocument.SelectSingleNode("users").ChildNodes;
        foreach (XmlElement item in nodes)
        {
            if (item.GetAttribute("account") == userAccount)
            {
                isUserExit = true;
                targetNode = item;
                break;
            }
        }

        // 删除用户操作
        if (!isUserExit)
        {
            Debug.Log("注销用户失败:无法找到用户账号" + userAccount);
        }
        else
        {
            targetNode.ParentNode.RemoveChild(targetNode);
            Debug.Log("成功注销用户:" + userAccount);
        }
    }
    #endregion
}

项目运行示例

上面忘了说了,编写完脚本后别忘了把脚本挂载到场景中的物体上哦!(我这里是挂载到了一个空物体上)
首次运行项目,我们可以看看原先xml文件中存储的用户账户信息:

运行输出示例

随后我们进行注册和注销操作:
准备注册和注销的账户

进行注册和注销操作

停止运行项目,可以看到文件被写入并保存:
文件被保存

再次重启项目,可以看到更改已经被应用到文件中(新增用户“米浴”,删除了用户“东海帝皇”):
检查更改后的xml文件

如果你成功了,恭喜你~

至此,本教程步入了尾声,此文章仅是鄙人对于XML的初步探索,只是一次非常非常简单的尝试,有关于XML还有许多内容,希望我们一起探索下去。路漫漫其修远兮,吾将上下而求索。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇
正在播放
0:00 0:00