这次我们来编写一个比较简单的模块——DataNode(数据结点)
下面是官网的介绍
首先新建一个DataNode文件夹,在其中新建DataNode类与DataManager类
DataNode是数据结点类,存储结点数据以及父子结点信息。DataNodeManager是数据结点管理器,负责管理根结点
打开DataNode类,为其添加对应的字段与属性
/// <summary> /// 数据结点 /// </summary> public class DataNode { public static readonly DataNode[] s_EmptyArray = new DataNode[] { }; public static readonly string[] s_PathSplit = new string[] { ".", "/", "\\" }; /// <summary> /// 结点名称 /// </summary> public string Name { get; private set; } /// <summary> /// 结点全名 /// </summary> public string FullName { get { return Parent == null ? Name : string.Format("{0}{1}{2}", Parent.FullName,s_PathSplit[0], Name); } } /// <summary> /// 结点数据 /// </summary> private object m_Data; /// <summary> /// 父结点 /// </summary> public DataNode Parent { get; private set; } /// <summary> /// 子结点 /// </summary> private List<DataNode> m_Childs; /// <summary> /// 子结点数量 /// </summary> public int ChildCount { get { return m_Childs != null ? m_Childs.Count : 0; } } }
在添加构造方法进行数据初始化之前,我们先添加一个检测数据结点名称是否合法的方法
/// <summary> /// 检测数据结点名称是否合法。 /// </summary> /// <param name="name">要检测的数据节点名称。</param> /// <returns>是否是合法的数据结点名称。</returns> private static bool IsValidName(string name) { if (string.IsNullOrEmpty(name)) { return false; } foreach (string pathSplit in s_PathSplit) { if (name.Contains(pathSplit)) { return false; } } return true; }
然后就可以添加构造方法了
public DataNode(string name, DataNode parent) { if (!IsValidName(name)) { Debug.LogError("数据结点名字不合法:" + name); } Name = name; m_Data = null; Parent = parent; m_Childs = null; }
接下来添加结点数据的相关方法
/// <summary> /// 获取结点数据 /// </summary> public T GetData<T>() { return (T)m_Data; } /// <summary> /// 设置结点数据 /// </summary> public void SetData(object data) { m_Data = data; }
添加子结点的相关方法
子结点的获取与增加
/// <summary> /// 根据索引获取子数据结点 /// </summary> /// <param name="index">子数据结点的索引</param> /// <returns>指定索引的子数据结点,如果索引越界,则返回空</returns> public DataNode GetChild(int index) { return index >= ChildCount ? null : m_Childs[index]; } /// <summary> /// 根据名称获取子数据结点 /// </summary> /// <param name="name">子数据结点名称</param> /// <returns>指定名称的子数据结点,如果没有找到,则返回空</returns> public DataNode GetChild(string name) { if (!IsValidName(name)) { Debug.LogError("子结点名称不合法,无法获取"); return null; } if (m_Childs == null) { return null; } foreach (DataNode child in m_Childs) { if (child.Name == name) { return child; } } return null; } /// <summary> /// 根据名称获取或增加子数据结点 /// </summary> /// <param name="name">子数据结点名称</param> /// <returns>指定名称的子数据结点,如果对应名称的子数据结点已存在,则返回已存在的子数据结点,否则增加子数据结点</returns> public DataNode GetOrAddChild(string name) { DataNode node = GetChild(name); if (node != null) { return node; } node = new DataNode(name, this); if (m_Childs == null) { m_Childs = new List<DataNode>(); } m_Childs.Add(node); return node; }
子结点的移除
/// <summary> /// 根据索引移除子数据结点 /// </summary> /// <param name="index">子数据结点的索引位置</param> public void RemoveChild(int index) { DataNode node = GetChild(index); if (node == null) { return; } node.Clear(); m_Childs.Remove(node); } /// <summary> /// 根据名称移除子数据结点 /// </summary> /// <param name="name">子数据结点名称</param> public void RemoveChild(string name) { DataNode node = GetChild(name); if (node == null) { return; } node.Clear(); m_Childs.Remove(node); } /// <summary> /// 移除当前数据结点的数据和所有子数据结点 /// </summary> public void Clear() { m_Data = null; if (m_Childs != null) { foreach (DataNode child in m_Childs) { child.Clear(); } m_Childs.Clear(); } }
到这里DataNode就编写完成了,整个模块也算完成一半了
接下来打开DataNodeManager类,使其继承ManagerBase,添加对应字段与属性,并在构造方法中初始化数据
/// <summary> /// 数据结点管理器 /// </summary> public class DataNodeManager : ManagerBase { /// <summary> /// 根结点 /// </summary> public DataNode Root { get; private set; } /// <summary> /// 根结点名称 /// </summary> private const string RootName = "<Root>"; public DataNodeManager() { Root = new DataNode(RootName, null); } public override void Init() { } public override void Shutdown() { Root.Clear(); Root = null; } public override void Update(float elapseSeconds, float realElapseSeconds) { }
在添加结点相关的方法之前,我们需要先添加一个用来切分结点路径的方法
/// <summary> /// 数据结点路径切分 /// </summary> /// <param name="path">要切分的数据结点路径</param> /// <returns>切分后的字符串数组</returns> private static string[] GetSplitPath(string path) { if (string.IsNullOrEmpty(path)) { return s_EmptyStringArray; } return path.Split(DataNode.s_PathSplit, StringSplitOptions.RemoveEmptyEntries); }这个方法能够将形如aaa.bbb.ccc的路径切分成{aaa,bbb,ccc}的字符串数组,以便我们通过遍历该数组,逐步寻找到最末尾的子结点
接下来添加结点相关的方法
结点的获取与增加
/// <summary> /// 获取数据结点。 /// </summary> /// <param name="path">相对于 node 的查找路径</param> /// <param name="node">查找起始结点</param> /// <returns>指定位置的数据结点,如果没有找到,则返回空</returns> public DataNode GetNode(string path, DataNode node = null) { DataNode current = (node ?? Root); //获取子结点路径的数组 string[] splitPath = GetSplitPath(path); foreach (string ChildName in splitPath) { //根据数组里的路径名获取子结点 current = current.GetChild(ChildName); if (current == null) { return null; } } return current; } /// <summary> /// 获取或增加数据结点 /// </summary> /// <param name="path">相对于 node 的查找路径</param> /// <param name="node">查找起始结点</param> /// <returns>指定位置的数据结点,如果没有找到,则增加相应的数据结点</returns> public DataNode GetOrAddNode(string path, DataNode node = null) { DataNode current = (node ?? Root); string[] splitPath = GetSplitPath(path); foreach (string childName in splitPath) { current = current.GetOrAddChild(childName); } return current; }
移除结点
/// <summary> /// 移除数据结点 /// </summary> /// <param name="path">相对于 node 的查找路径</param> /// <param name="node">查找起始结点</param> public void RemoveNode(string path, DataNode node = null) { DataNode current = (node ?? Root); DataNode parent = current.Parent; string[] splitPath = GetSplitPath(path); foreach (string childName in splitPath) { parent = current; current = current.GetChild(childName); if (current == null) { return; } } if (parent != null) { parent.RemoveChild(current.Name); } }
最后添加结点数据的相关方法
/// <summary> /// 根据类型获取数据结点的数据 /// </summary> /// <typeparam name="T">要获取的数据类型</typeparam> /// <param name="path">相对于 node 的查找路径</param> /// <param name="node">查找起始结点</param> public T GetData<T>(string path, DataNode node = null) { DataNode current = GetNode(path, node); if (current == null) { Debug.Log("要获取数据的结点不存在:" + path); return default(T); } return current.GetData<T>(); } /// <summary> /// 设置数据结点的数据。 /// </summary> /// <param name="path">相对于 node 的查找路径。</param> /// <param name="data">要设置的数据</param> /// <param name="node">查找起始结点</param> public void SetData(string path, object data, DataNode node = null) { DataNode current = GetOrAddNode(path, node); current.SetData(data); }
现在DataNode模块已经完全编写完成,该测试一下了
先仿照我们在上一章的步骤建立起测试需要的文件夹,脚本和场景
打开脚本,在Start方法里编写测试代码(这里我就直接仿照官网的文档例子来写测试代码了)
public class DataNodeTestMain : MonoBehaviour { private void Start() { //根据绝对路径设置与获取数据 DataNodeManager dataNodeManager = FrameworkEntry.Instance.GetManager<DataNodeManager>(); dataNodeManager.SetData("Player.Name", "Ellan"); string playerName = dataNodeManager.GetData<string>("Player.Name"); Debug.Log(playerName); //根据相对路径设置与获取数据 DataNode playerNode = dataNodeManager.GetNode("Player"); dataNodeManager.SetData("Level", 99, playerNode); int playerLevel = dataNodeManager.GetData<int>("Level", playerNode); Debug.Log(playerLevel); //直接通过数据结点来操作 DataNode playerExpNode = playerNode.GetOrAddChild("Exp"); playerExpNode.SetData(1000); int playerExp = playerExpNode.GetData<int>(); Debug.Log(playerExp); } }
启动游戏,看看能不能顺利跑起来吧