unity 目录树

    xiaoxiao2022-07-13  161

    最近项目上需要对一些编组信息进行树状展示,为了通用,将目录树写成一个组件,完整的代码包括测试代码已经上传到了github上目录树代码,代码需要配合RectTransform的锚点使用,所以写了一个编辑器扩展,可以像创建其他UI组件一样创建一个目录树编辑器代码

    使用方式

    1.在编辑器中创建一个Tree

    2.在代码中获取并创建

      首先要在对应面板中获取到Tree组件,用于构造一棵树的函数为

    Tree.GenerateTree(TreeNode rootNode)

      这个函数需要传入一个节点,这个节点可以是一个空节点,也可以是一个已经构造好父子关系的根节点。传入之后,会根据这个节点的子节点信息创建一棵树。   TreeNode支持链式添加父子关系,可以像下面一样构造一棵树。

    TreeNode root = new TreeNode("1"); root.AddChild(new TreeNode("11") .AddChild(new TreeNode("111") .AddChild(new TreeNode("1111") .AddChild(new TreeNode("11111")) .AddChild(new TreeNode("11112"))) .AddChild(new TreeNode("1112"))) .AddChild(new TreeNode("112"))) .AddChild(new TreeNode("12") .AddChild(new TreeNode("121")) .AddChild(new TreeNode("122") .AddChild(new TreeNode("1221")) .AddChild(new TreeNode("1222"))) .AddChild(new TreeNode("123"))) .AddChild(new TreeNode("13") .AddChild(new TreeNode("131"))); tree.GenerateTree(root);

      但大多数情况下,树的信息应该是由另外一个信息类或者数据表之类的东西转来的,所以父子关系的创建需要自己写。

    代码

    1.树的节点

      在大多数的时候外界并不需要对TreeNode进行直接控制,但是在初始化树的时候,要传入一个已经设置好父子关系的根节点,这个类里面的几个核心方法都利用了递归,所以在更新一个节点的信息的时候,这个节点的所有子节点也会改变。 树的节点的初始化是根据对应的模板来,目前还不是很灵活,模板结构变化比较大的话需要修改InitEnity方法。

    using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace XFramework.UI { /// <summary> /// 树节点 /// </summary> public class TreeNode { private RectTransform m_Rect; /// <summary> /// 当前结点所归属的树 /// </summary> private Tree m_Tree; /// <summary> /// 子结点 /// </summary> private List<TreeNode> m_Childs; /// <summary> /// 父结点 /// </summary> private TreeNode m_Parent; /// <summary> /// 开启状态 /// </summary> private bool m_IsOn; /// <summary> /// 层级 /// </summary> private int m_level; /// <summary> /// 显示文字 /// </summary> public string Text { get; private set; } public TreeNode() { m_IsOn = true; m_Childs = new List<TreeNode>(); } public TreeNode(string text) : this() { this.Text = text; } /// <summary> /// 初始化场景中对应的实体 /// </summary> /// <param name="tree"></param> private void InitEnity(Tree tree) { m_Tree = tree; // 创建自己 if (m_Parent == null) { m_Rect = Object.Instantiate(m_Tree.NodeTemplate, m_Tree.NodeTemplate.transform.parent.Find("Root")).GetComponent<RectTransform>(); m_level = 0; } else { m_Rect = Object.Instantiate(m_Tree.NodeTemplate, m_Parent.m_Rect.Find("Child")).GetComponent<RectTransform>(); m_level = m_Parent.m_level + 1; } // UI组件设置 m_Rect.Find("Toggle").GetComponent<Toggle>().onValueChanged.AddListener((value) => { m_IsOn = value; Refresh(value); Root.RefreshPos(); tree.onOn_Off.Invoke(value, this); }); m_Rect.Find("Button").GetComponent<Button>().onClick.AddListener(() => { tree.onSelectNode.Invoke(this); }); m_Rect.Find("Button").Find("Text").GetComponent<Text>().text = this.Text; } /// <summary> /// 刷新位置及显示隐藏 /// </summary> private void Refresh(bool isOn) { isOn &= m_IsOn; if (isOn) { foreach (var item in m_Childs) { item.Refresh(isOn); item.m_Rect.localScale = new Vector3(1, 1, 1); } } else { foreach (var item in m_Childs) { item.Refresh(isOn); item.m_Rect.localScale = new Vector3(1, 0, 1); } } } /// <summary> /// 刷新位置 /// </summary> public void RefreshPos() { int index = 0; if (m_Parent != null) { foreach (var item in m_Parent.m_Childs) { if (item == this) break; index += item.GetItemCount(); } } m_Rect.anchoredPosition = new Vector2(0, -index * 30); foreach (var item in m_Childs) { item.RefreshPos(); } } /// <summary> /// 添加一个父子关系 /// </summary> /// <param name="item"></param> /// <returns></returns> public TreeNode AddChild(TreeNode item) { m_Childs.Add(item); item.m_Parent = this; return this; } public TreeNode AddChild(string text) { return AddChild(new TreeNode(text)); } /// <summary> /// 根据已有的父子关系创建一颗(子)树 /// </summary> /// <param name="m_Parent"></param> /// <param name="gameObject"></param> public void CreateTree(Tree tree) { InitEnity(tree); // 继续创建 foreach (var child in m_Childs) { child.CreateTree(m_Tree); } if (m_Parent == null) { RefreshPos(); } } /// <summary> /// 添加一个父子关系并创建实体 /// </summary> /// <param name="item"></param> /// <returns></returns> public TreeNode CreateChild(TreeNode item) { AddChild(item); item.InitEnity(m_Tree); Refresh(m_IsOn); Root.RefreshPos(); return this; } public TreeNode CreateChild(string text) { TreeNode item = new TreeNode(text); return CreateChild(item); } /// <summary> /// 删除自身 /// </summary> public void Delete() { if(m_Parent == null) { Debug.Log("根结点不能删除"); return; } Object.Destroy(m_Rect.gameObject); m_Parent.m_Childs.Remove(this); Root.RefreshPos(); } /// <summary> /// 子物体的数量 +1 /// </summary> public int GetItemCount() { if (m_Childs.Count == 0 || !m_IsOn) { return 1; } else { int count = 0; foreach (var item in m_Childs) { count += item.GetItemCount(); } return count + 1; } } /// <summary> /// 获取自己在父物体种的索引 /// </summary> public int GetSiblingIndex() { if(m_Parent != null) { int index = 0; foreach (var item in m_Parent.m_Childs) { if (item == this) return index; } } return 0; } /// <summary> /// 根结点 /// </summary> public TreeNode Root { get { TreeNode item = this; while(item.m_Parent != null) { item = item.m_Parent; } return item; } } /// <summary> /// 重置父物体 /// </summary> /// <param name="parent"></param> public void SetParent(TreeNode parent) { m_Parent.m_Childs.Remove(this); parent.AddChild(this); } /// <summary> /// 通过字符串找寻子节点 /// </summary> /// <param name="path"></param> /// <returns></returns> public TreeNode Find(string path) { var temp = path.Split(new char[] { '/' }, 2); if (temp.Length == 1) return this; TreeNode node = null; foreach (var item in m_Childs) { if (item.Text == temp[0]) { node = item; break; } } if (node == null) return node; else return node.Find(temp[1]); } /// <summary> /// 根据索引获取子节点 /// </summary> public TreeNode GetChild(int index) { return m_Childs[index]; } } }

    2.树

      这个类的使用方式就像Button一样,可以先注册事件,然后调用GenerateTree在场景中构造一棵树,注意事件的实际调用者不是Tree而是TreeNode

    using UnityEngine; namespace XFramework.UI { /// <summary> /// 目录树 /// </summary> public class Tree : MonoBehaviour { /// <summary> /// 模板 /// </summary> public GameObject NodeTemplate { get; private set; } /// <summary> /// 树的根节点 /// </summary> private TreeNode m_RootTreeNode; public string rootText = "Root"; /// <summary> /// 节点被选中的事件 /// </summary> public TreeEvent onSelectNode = new TreeEvent(); /// <summary> /// 节点展开关闭事件 /// </summary> public SwitchEvent onOn_Off = new SwitchEvent(); private void Awake() { NodeTemplate = transform.Find("NodeTemplate").gameObject; NodeTemplate.GetComponent<RectTransform>().anchoredPosition = new Vector2(10000, 10000); } /// <summary> /// 构造一棵树 /// </summary> /// <param name="rootNode">父子关系已经设置好的根节点</param> public void GenerateTree(TreeNode rootNode) { if (m_RootTreeNode != null) m_RootTreeNode.Delete(); m_RootTreeNode = rootNode; m_RootTreeNode.CreateTree(this); } /// <summary> /// 删除某个节点 /// </summary> /// <param name="path">路径</param> public bool Delete(string path) { TreeNode node = m_RootTreeNode.Find(path); if (node != null) { node.Delete(); return true; } return false; } public class TreeEvent : UnityEngine.Events.UnityEvent<TreeNode> { } public class SwitchEvent : UnityEngine.Events.UnityEvent<bool,TreeNode> { } } }

    3.Editor

      最后放上编辑器代码,可以直接在场景中创建出一棵树

    using UnityEngine; using UnityEditor; using UnityEngine.UI; /// <summary> /// 用于创建一些自定义UI组件 /// </summary> public class CreateComponent { private static DefaultControls.Resources s_StandardResources; private const string kUILayerName = "UI"; private const string kStandardSpritePath = "UI/Skin/UISprite.psd"; private const string kBackgroundSpritePath = "UI/Skin/Background.psd"; private const string kInputFieldBackgroundPath = "UI/Skin/InputFieldBackground.psd"; private const string kKnobPath = "UI/Skin/Knob.psd"; private const string kCheckmarkPath = "UI/Skin/Checkmark.psd"; private const string kDropdownArrowPath = "UI/Skin/DropdownArrow.psd"; private const string kMaskPath = "UI/Skin/UIMask.psd"; [MenuItem("GameObject/UI/Tree")] public static void CreateTree() { GameObject parent = Selection.activeGameObject; RectTransform tree = new GameObject("Tree").AddComponent<RectTransform>(); tree.SetParent(parent.transform); tree.localPosition = Vector3.zero; tree.gameObject.AddComponent<XFramework.UI.Tree>(); tree.sizeDelta = new Vector2(180, 30); // 设置模板 RectTransform itemTemplate = new GameObject("NodeTemplate").AddComponent<RectTransform>(); itemTemplate.SetParent(tree); itemTemplate.pivot = new Vector2(0, 1); itemTemplate.anchorMin = new Vector2(0.5f, 1); itemTemplate.anchorMax = new Vector2(0.5f, 1); itemTemplate.anchoredPosition = new Vector2(-90, 0); itemTemplate.sizeDelta = new Vector2(180, 30); RectTransform button = DefaultControls.CreateButton(GetStandardResources()).GetComponent<RectTransform>(); button.SetParent(itemTemplate); button.anchoredPosition = new Vector2(10, 0); button.sizeDelta = new Vector2(160, 30); RectTransform toggle = DefaultControls.CreateToggle(GetStandardResources()).GetComponent<RectTransform>(); toggle.SetParent(itemTemplate); Object.DestroyImmediate(toggle.Find("Label").gameObject); toggle.anchoredPosition = new Vector2(-80, 0); toggle.sizeDelta = new Vector2(20, 20); RectTransform child = new GameObject("Child").AddComponent<RectTransform>(); child.SetParent(itemTemplate); child.pivot = new Vector2(0, 1); child.anchorMin = new Vector2(0, 1); child.anchorMax = new Vector2(0, 1); child.sizeDelta = Vector2.zero; child.anchoredPosition = new Vector2(20, -30); // 设置树的跟结点位置 RectTransform treeRoot = new GameObject("Root").AddComponent<RectTransform>(); treeRoot.SetParent(tree); treeRoot.anchoredPosition = new Vector2(-90, 0); treeRoot.sizeDelta = new Vector2(100, 30); } private static DefaultControls.Resources GetStandardResources() { if (s_StandardResources.standard == null) { s_StandardResources.standard = AssetDatabase.GetBuiltinExtraResource<Sprite>(kStandardSpritePath); s_StandardResources.background = AssetDatabase.GetBuiltinExtraResource<Sprite>(kBackgroundSpritePath); s_StandardResources.inputField = AssetDatabase.GetBuiltinExtraResource<Sprite>(kInputFieldBackgroundPath); s_StandardResources.knob = AssetDatabase.GetBuiltinExtraResource<Sprite>(kKnobPath); s_StandardResources.checkmark = AssetDatabase.GetBuiltinExtraResource<Sprite>(kCheckmarkPath); s_StandardResources.dropdown = AssetDatabase.GetBuiltinExtraResource<Sprite>(kDropdownArrowPath); s_StandardResources.mask = AssetDatabase.GetBuiltinExtraResource<Sprite>(kMaskPath); } return s_StandardResources; } }
    最新回复(0)