unity学习笔记#4--读取XML数据用于人物间对话

前言

在我的上一篇文章中,我虽然实现了读取XML文件数据里的对话并将其输出到控制台,但是离实际能用在项目的距离还很远,一个脚本只能用于一段对话上。为了让对话的脚本能适用于其它对话,我在查阅了相关资料后,对相关的xml文件及脚本做了改进。


对XML文件做的改动

首先先看我改后的dialogueTest.xml文件:

<objects>
<Scenes SceneID="classroom_1" First_id="cr_001">
<people NPCid="student_1" id="cr_001" nextid="cr_002">
学生1:起立
people>
<people NPCid="student_2" id="cr_002" nextid="cr_003">
学生2:老师好!
people>
<people NPCid="student_3" id="cr_003" nextid="cr_004">
学生3:老师好!!
people>
<people NPCid="teacher_1" id="cr_004" nextid="over">
老师:同学们好!
people>
Scenes>
<Scenes SceneID="classroom_2" First_id="cr_010">
  <people NPCid="teacher_1" id="cr_010" nextid="cr_011">
老师:下课!
  people>
  <people NPCid="student_2" id="cr_011" nextid="cr_012">
同学2:起立
  people>
  <people NPCid="student_1" id="cr_012" nextid="cr_013">
同学1:老师再见!
  people>
  <people NPCid="student_3" id="cr_013" nextid="over">
同学3:老师再见!!
  people>
Scenes>
objects>

跟我上一篇文章放出的xml文件相比,我现在的xml文件里每句话的属性中,NPCid用于表示讲述此句话的人物ID,id用于表示此句话的id,而nextid则用于表示此句话对应的下一句话的id。通过这3个属性可以把几句零散的句子连接起来成为一段对话,同时又能取得讲话的人物ID,便于获取该人物的头像。而我的NPC2Image.xml文件则用于存放人物NPCid对应的头像,文件如下:

<object>
<NPC NPCid="student_1">Images/同学1NPC>
<NPC NPCid="student_2">Images/同学2NPC>
<NPC NPCid="student_3">Images/同学3NPC>
<NPC NPCid="teacher_1">Images/老师NPC>
object>

通过单例类来存储所有xml文件的路径

为了降低改动xml文件路径的成本,我通过一个单例类来存储所有xml文件的路径,其脚本代码如下:

public class StoryManager
{
    private static StoryManager instance;

    public static StoryManager Instance
    {
        get
        {
            if (instance==null)
            {
                instance=new StoryManager();
            }
            return instance;
        }
    }

    public static string XML_dialogueTest_path = "/XMLData/dialogueTest.xml";  //对话xml文件地址,具体文件地址根据自己的XML文件存放地址更改
    public static string XML_NPC2Image_path = "/XMLData/NPC2Image.xml";  //npc头像地址,具体文件地址根据自己的XML文件存放地址更改

}

注:此脚本由于没有继承自MonoBehaviour,因此不用挂载到游戏对象中。


改进脚本

先上改进后的XMLDialogueTest脚本:

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

public class XMLDialogueTest : MonoBehaviour
{

    public Text dialogue;    //用于显示对话的text对象
    public Image NPCImage;  //对应NPC头像
    public string _sceneID;  //当前场景对话段的ID
    public bool isTalking = false;  //判断是否可以开始对话
    private string curID;  //当前对话的id

    // Use this for initialization
    void Awake () {

        //传递Xml文件路径,注意在之前要加"/"号
        print(StoryManager.XML_dialogueTest_path);
        curID = GetFirstDialogue();
        isTalking = true;

    }

    void Update()
    {
        if (isTalking)
        {
            //通过鼠标左键来切换到下一句对话
            if (Input.GetMouseButtonDown(0))
            {
                //在传入当前句子id后,获取下一句句子的id,从而实现连续对话
                curID = GetCurrentDialogue(curID);
                if (curID == "over")
                {
                    isTalking = false;
                    Debug.Log("当前剧情对话完毕");
                }
            }

        }
    }

    /// 
    /// 通过传来的npcid去NPC2Image.xml文件里取得相应的人物头像
    /// 
    /// npcid
    /// NPC对应的头像
    public Sprite GetNPCImage(string _npcid)
    {
        XmlDocument xml=new XmlDocument();

        Sprite sp=new Sprite();
        //确认此xml文件是否存在
        if (File.Exists(Application.dataPath + StoryManager.XML_NPC2Image_path))
        {
            xml.Load(Application.dataPath + StoryManager.XML_NPC2Image_path);
            XmlNodeList xmlNodeList = xml.SelectSingleNode("object").ChildNodes;  //查找到object节点下的所有子节点
            foreach (XmlElement xmlElement in xmlNodeList)
            {
                //查找符合_npcid名的节点
                if (xmlElement.GetAttribute("NPCid")==_npcid)
                {
                    //Debug.Log("找到相应的图片了");
                    string imagePath = xmlElement.InnerText;   //通过此节点id名取得其保存的image地址
                    print(imagePath);
                    sp=Resources.Load(imagePath,typeof(Sprite))as Sprite;  //通过地址加载图片资源
                    return sp;  //返回图片资源
                }
            }

        }
        else
        {
            Debug.Log("没有找到NPC2Image.xml文件,请确认该文件地址");

        }
        return null; //没有则返回null
    }


    /// 
    /// 将头像输出到场景中
    /// 
    /// npcid
    public void SetNPCImage(string _npcid)
    {

        if (_npcid==null)
        {
            Debug.Log("未取得npc的id,无法设置图片!");
            return;
        }
        //调用GetNPCImage函数取得图片,再设置场景中的头像
        Sprite sp = GetNPCImage(_npcid);
        if (sp==null)       
        {
            Debug.Log("未取得npc的图片,无法设置图片");
            return;
        }
        NPCImage.sprite = sp;
    }

    //取得当前场景对话的第一句对话的id
    /// 
    /// 取得当前场景对话的第一句对话的id
    /// 
    /// 当前对话段的第一句话的id
    public string GetFirstDialogue()
    {
        XmlDocument xml=new XmlDocument();
        string path = Application.dataPath + StoryManager.XML_dialogueTest_path;
        if (File.Exists(path))
        {
            xml.Load(path);

            //通过xpath表达式来表示当前对话段的xml节点
            string xmlNodePath ="//Scenes[@SceneID='"+_sceneID+"']";

            print(xmlNodePath);
            //取得当前对话段的xml节点
            XmlNode xmlSceneNode = xml.SelectSingleNode(xmlNodePath);  
            Debug.Log("当前场景所用的id为:"+xmlSceneNode.Attributes["First_id"].Value);
            //将该节点上储存的对话段的第一句话的id取出
            return xmlSceneNode.Attributes["First_id"].Value;
        }

        return null;
    }

    /// 
    /// 读取通过diaID从xml中读取对话,返回下一句对话的id
    /// 
    /// 当前句子的id
    /// 下一句话的id(nextid)
    public string GetCurrentDialogue(string diaID)
    {
        XmlDocument xml = new XmlDocument();

        string path = Application.dataPath + StoryManager.XML_dialogueTest_path;
        if (File.Exists(path))
        {
            xml.Load(path);

            //取得场景的节点
            XmlNodeList xmlNodeList = xml.SelectSingleNode("objects").ChildNodes;
            foreach (XmlElement xl1 in xmlNodeList)
            {
                //找出当前场景的id
                if (xl1.GetAttribute("SceneID")==_sceneID)
                {

                    //获取当前场景的对话节点
                    foreach (XmlElement xl2 in xl1.ChildNodes)
                    {
                        if (diaID==xl2.GetAttribute("id"))
                        {
                            //调用函数设置对话
                            SetUIText(xl2.InnerText);
                            //调用函数设置头像
                            SetNPCImage(xl2.GetAttribute("NPCid"));
                            Debug.Log("下一句话的id是:"+xl2.GetAttribute("nextid"));
                            //返回下一句话的id
                            return xl2.GetAttribute("nextid");
                        }
                    }
                }
            }

        }

        return null;  //如果得不到对话,则返回null
    }

    //
    /// 
    /// 将对话输出到场景中的text对象上
    /// 
    /// 要传到场景中text的句子
    public void SetUIText(string dialogueText)
    {
        if (dialogueText!=null)
        {
        dialogue.text = dialogueText;
        }
    }
}

使用方法如下:
首先将该脚本挂载到游戏场景中的对象上,然后选定Text及Image对象,最后填写要读取的场景对话段的_sceneID,如图:
unity学习笔记#4--读取XML数据用于人物间对话_第1张图片

运行时截图:
unity学习笔记#4--读取XML数据用于人物间对话_第2张图片

如果前面StoryManager脚本中的xml文件地址填写正确,以及自己的两个xml文件内节点命名与XMLDialogueTest脚本的读取相对应的话,那么每次要读取不同段对话时只需要更改相应的_sceneID即可在游戏运行时实现连续的对话。在刚完成这个脚本时我的想法是将此脚本挂载到每一个需要对话的对象上,再在每个对象上填上所需要的对话段的_sceneID。后来觉得这样的话会使得每一个大场景上有很多脚本都会挂载有此对象,且每个脚本都需要手动指定Text及Image对象,会产生额外的工作量,倒不如将此脚本作为一个管理脚本,自己再重新写一个小的脚本,仅仅负责传递_sceneID给管理脚本,同时启动其工作就行。但因为自己是在文章写到快好时才想到此问题,因而还没有写相应的脚本,大家可以自己实现。


一点想法

其实结合我之前文章可以看出我在利用xml实现角色间对话的做法发生了改变。我之前的做法偏向于先将xml文件内数据间接地读取到数组、列表或字典等数据对象里,再将其输出到游戏中,虽然只要对xml读取一次即可,但却带来了对中间数据对象的操作问题,而现在的做法更偏向于将xml文件内数据直接输出到游戏中,虽然做法直接,但需要反复对xml文件进行读取操作,而这个读取操作的xml文件里的数据量的大小是否会影响读取时间也未可知(好吧,其实就是我没去验证……)。这两种方案都各有优劣,就看自己想要哪种吧。反正我现在更偏向于后一种方案。


参考资料:
Unity中单例模式的使用
XML-XPATH 相关
用XPath精确定位节点元素&selenium使用Xpath定位之完整篇

你可能感兴趣的:(unity,学习笔记,xml)