Tz2/Assets/Zion/Scripts/Editor/TreeMenuGenerator.cs

463 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
namespace Zion.Scripts.Editor
{
/// <summary>
/// 树状菜单生成器编辑器工具
/// </summary>
public class TreeMenuGenerator : EditorWindow
{
private TreeMenuData menuData;
private SerializedObject serializedData;
private SerializedProperty menuItemsProperty;
private Vector2 scrollPosition;
private GameObject menuRoot;
// 预制体路径
private const string ButtonPrefabPath = "UI/Button";
private const string TextPrefabPath = "UI/Text";
// 编辑器窗口尺寸
private const float WindowWidth = 600f;
private const float WindowHeight = 700f;
[MenuItem("Zion/树状菜单生成器")]
public static void ShowWindow()
{
var window = GetWindow<TreeMenuGenerator>("树状菜单生成器");
window.minSize = new Vector2(WindowWidth, WindowHeight);
window.Show();
}
private void OnEnable()
{
// 加载编辑器图标
folderIcon = EditorGUIUtility.FindTexture("Folder Icon");
fileIcon = EditorGUIUtility.FindTexture("TextAsset Icon");
}
private void OnGUI()
{
EditorGUILayout.BeginVertical(GUI.skin.box);
// 标题
EditorGUILayout.LabelField("树状菜单生成器", EditorStyles.boldLabel);
EditorGUILayout.Space();
// 菜单数据对象
EditorGUI.BeginChangeCheck();
menuData = (TreeMenuData)EditorGUILayout.ObjectField("菜单数据", menuData, typeof(TreeMenuData), false);
if (EditorGUI.EndChangeCheck())
{
if (menuData != null)
{
serializedData = new SerializedObject(menuData);
menuItemsProperty = serializedData.FindProperty("menuItems");
}
else
{
serializedData = null;
menuItemsProperty = null;
}
}
EditorGUILayout.Space();
// 如果没有菜单数据,显示创建按钮
if (menuData == null)
{
EditorGUILayout.HelpBox("请先创建或选择一个树状菜单数据对象", MessageType.Info);
if (GUILayout.Button("创建菜单数据", GUILayout.Height(30)))
{
CreateNewMenuData();
}
EditorGUILayout.EndVertical();
return;
}
// 更新序列化对象
serializedData.Update();
// 菜单基本设置
DrawMenuBasicSettings();
EditorGUILayout.Space();
// 显示添加根菜单项按钮
if (GUILayout.Button("添加根菜单项", GUILayout.Height(25)))
{
Undo.RecordObject(menuData, "添加根菜单项");
menuData.menuItems.Add(new TreeMenuItem());
EditorUtility.SetDirty(menuData);
}
EditorGUILayout.Space();
// 使用滚动视图显示菜单结构
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.ExpandHeight(true));
// 绘制所有根菜单项
for (int i = 0; i < menuItemsProperty.arraySize; i++)
{
DrawMenuItem(menuItemsProperty.GetArrayElementAtIndex(i), 0, i);
}
EditorGUILayout.EndScrollView();
// 应用修改
serializedData.ApplyModifiedProperties();
EditorGUILayout.Space();
// 菜单生成区域
EditorGUILayout.BeginHorizontal();
menuRoot = (GameObject)EditorGUILayout.ObjectField("菜单根对象", menuRoot, typeof(GameObject), true);
if (GUILayout.Button("生成菜单", GUILayout.Width(100), GUILayout.Height(25)))
{
if (menuRoot != null)
{
GenerateMenu();
}
else
{
EditorUtility.DisplayDialog("错误", "请先选择一个菜单根对象", "确定");
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
// 绘制菜单基本设置
private void DrawMenuBasicSettings()
{
EditorGUILayout.LabelField("菜单基本设置", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
menuData.menuTitle = EditorGUILayout.TextField("菜单标题", menuData.menuTitle);
menuData.backgroundColor = EditorGUILayout.ColorField("背景颜色", menuData.backgroundColor);
menuData.textColor = EditorGUILayout.ColorField("文本颜色", menuData.textColor);
menuData.highlightColor = EditorGUILayout.ColorField("高亮颜色", menuData.highlightColor);
menuData.menuFont = (Font)EditorGUILayout.ObjectField("菜单字体", menuData.menuFont, typeof(Font), false);
menuData.fontSize = EditorGUILayout.IntField("字体大小", menuData.fontSize);
menuData.itemHeight = EditorGUILayout.FloatField("菜单项高度", menuData.itemHeight);
menuData.indentWidth = EditorGUILayout.FloatField("缩进宽度", menuData.indentWidth);
EditorGUI.indentLevel--;
}
// 用于显示的图标
private Texture folderIcon;
private Texture fileIcon;
// 绘制单个菜单项
private void DrawMenuItem(SerializedProperty menuItemProperty, int level, int index)
{
SerializedProperty nameProperty = menuItemProperty.FindPropertyRelative("name");
SerializedProperty iconProperty = menuItemProperty.FindPropertyRelative("icon");
SerializedProperty actionProperty = menuItemProperty.FindPropertyRelative("action");
SerializedProperty childrenProperty = menuItemProperty.FindPropertyRelative("children");
SerializedProperty expandedProperty = menuItemProperty.FindPropertyRelative("isExpanded");
// 缩进
EditorGUILayout.BeginHorizontal();
GUILayout.Space(level * 20);
// 折叠箭头
expandedProperty.boolValue = EditorGUILayout.Foldout(expandedProperty.boolValue, "", true);
// 菜单项图标
Texture icon = childrenProperty.arraySize > 0 ? folderIcon : fileIcon;
GUILayout.Label(icon, GUILayout.Width(20), GUILayout.Height(20));
// 菜单项名称
nameProperty.stringValue = EditorGUILayout.TextField(nameProperty.stringValue, GUILayout.ExpandWidth(true));
// 操作按钮
if (GUILayout.Button("+", GUILayout.Width(25)))
{
// 添加子菜单
AddChildMenuItem(childrenProperty);
}
if (GUILayout.Button("×", GUILayout.Width(25)))
{
// 删除菜单项
DeleteMenuItem(menuItemProperty, level, index);
EditorGUILayout.EndHorizontal();
return;
}
EditorGUILayout.EndHorizontal();
// 如果展开,显示更多属性和子项
if (expandedProperty.boolValue)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Space((level + 1) * 20);
EditorGUILayout.LabelField("图标:", GUILayout.Width(40));
iconProperty.stringValue = EditorGUILayout.TextField(iconProperty.stringValue);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
GUILayout.Space((level + 1) * 20);
EditorGUILayout.LabelField("动作:", GUILayout.Width(40));
actionProperty.stringValue = EditorGUILayout.TextField(actionProperty.stringValue);
EditorGUILayout.EndHorizontal();
// 绘制子菜单项
for (int i = 0; i < childrenProperty.arraySize; i++)
{
DrawMenuItem(childrenProperty.GetArrayElementAtIndex(i), level + 1, i);
}
}
}
// 添加子菜单项
private void AddChildMenuItem(SerializedProperty childrenProperty)
{
childrenProperty.arraySize++;
SerializedProperty newItem = childrenProperty.GetArrayElementAtIndex(childrenProperty.arraySize - 1);
newItem.FindPropertyRelative("name").stringValue = "新菜单项";
newItem.FindPropertyRelative("children").arraySize = 0;
newItem.FindPropertyRelative("isExpanded").boolValue = true;
}
// 删除菜单项
private void DeleteMenuItem(SerializedProperty menuItemProperty, int level, int index)
{
SerializedProperty parentProperty;
if (level == 0)
{
parentProperty = menuItemsProperty;
}
else
{
// 需要获取父级属性
parentProperty = GetParentProperty(menuItemProperty);
}
if (parentProperty != null)
{
// 删除前记录更改
Undo.RecordObject(menuData, "删除菜单项");
// 删除项
parentProperty.DeleteArrayElementAtIndex(index);
EditorUtility.SetDirty(menuData);
}
}
// 获取父级属性(此方法为简化版,真实项目中可能需要更复杂的实现)
private SerializedProperty GetParentProperty(SerializedProperty property)
{
string path = property.propertyPath;
int lastDotIndex = path.LastIndexOf('.');
if (lastDotIndex == -1) return null;
string parentPath = path.Substring(0, lastDotIndex);
return serializedData.FindProperty(parentPath);
}
// 创建新的菜单数据对象
private void CreateNewMenuData()
{
// 创建保存路径
string path = EditorUtility.SaveFilePanelInProject("创建菜单数据", "TreeMenuData", "asset", "请选择保存位置");
if (string.IsNullOrEmpty(path)) return;
// 创建菜单数据对象
TreeMenuData newMenuData = CreateInstance<TreeMenuData>();
newMenuData.menuItems = new List<TreeMenuItem>();
// 保存资源
AssetDatabase.CreateAsset(newMenuData, path);
AssetDatabase.SaveAssets();
// 选择并显示新建的资源
menuData = newMenuData;
serializedData = new SerializedObject(menuData);
menuItemsProperty = serializedData.FindProperty("menuItems");
EditorGUIUtility.PingObject(newMenuData);
}
// 生成菜单UI
private void GenerateMenu()
{
if (menuData == null || menuRoot == null) return;
// 记录Undo
Undo.RegisterFullObjectHierarchyUndo(menuRoot, "生成菜单");
// 清除现有子对象
while (menuRoot.transform.childCount > 0)
{
DestroyImmediate(menuRoot.transform.GetChild(0).gameObject);
}
// 创建标题
GameObject titleObj = new GameObject("Title");
titleObj.transform.SetParent(menuRoot.transform, false);
Text titleText = titleObj.AddComponent<Text>();
titleText.text = menuData.menuTitle;
titleText.font = menuData.menuFont != null ? menuData.menuFont : Resources.GetBuiltinResource<Font>("Arial.ttf");
titleText.fontSize = menuData.fontSize + 4;
titleText.color = menuData.textColor;
titleText.alignment = TextAnchor.MiddleCenter;
RectTransform titleRect = titleObj.GetComponent<RectTransform>();
titleRect.anchorMin = new Vector2(0, 1);
titleRect.anchorMax = new Vector2(1, 1);
titleRect.pivot = new Vector2(0.5f, 1);
titleRect.sizeDelta = new Vector2(0, 40);
titleRect.anchoredPosition = Vector2.zero;
// 创建滚动视图
GameObject scrollViewObj = new GameObject("ScrollView");
scrollViewObj.transform.SetParent(menuRoot.transform, false);
ScrollRect scrollRect = scrollViewObj.AddComponent<ScrollRect>();
Image scrollImage = scrollViewObj.AddComponent<Image>();
scrollImage.color = new Color(1, 1, 1, 0.1f);
RectTransform scrollRectTransform = scrollViewObj.GetComponent<RectTransform>();
scrollRectTransform.anchorMin = new Vector2(0, 0);
scrollRectTransform.anchorMax = new Vector2(1, 1);
scrollRectTransform.pivot = new Vector2(0.5f, 0.5f);
scrollRectTransform.sizeDelta = new Vector2(0, -40);
scrollRectTransform.anchoredPosition = new Vector2(0, -20);
// 创建视口
GameObject viewportObj = new GameObject("Viewport");
viewportObj.transform.SetParent(scrollViewObj.transform, false);
Image viewportImage = viewportObj.AddComponent<Image>();
viewportImage.color = Color.clear;
Mask viewportMask = viewportObj.AddComponent<Mask>();
viewportMask.showMaskGraphic = false;
RectTransform viewportRect = viewportObj.GetComponent<RectTransform>();
viewportRect.anchorMin = Vector2.zero;
viewportRect.anchorMax = Vector2.one;
viewportRect.pivot = new Vector2(0.5f, 0.5f);
viewportRect.sizeDelta = Vector2.zero;
viewportRect.anchoredPosition = Vector2.zero;
// 创建内容容器
GameObject contentObj = new GameObject("Content");
contentObj.transform.SetParent(viewportObj.transform, false);
VerticalLayoutGroup layoutGroup = contentObj.AddComponent<VerticalLayoutGroup>();
layoutGroup.padding = new RectOffset(5, 5, 5, 5);
layoutGroup.spacing = 2;
layoutGroup.childAlignment = TextAnchor.UpperLeft;
layoutGroup.childControlHeight = false; // 不自动控制高度
layoutGroup.childControlWidth = true;
layoutGroup.childForceExpandHeight = false;
layoutGroup.childForceExpandWidth = true;
ContentSizeFitter sizeFitter = contentObj.AddComponent<ContentSizeFitter>();
sizeFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
sizeFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
RectTransform contentRect = contentObj.GetComponent<RectTransform>();
contentRect.anchorMin = new Vector2(0, 1);
contentRect.anchorMax = new Vector2(1, 1);
contentRect.pivot = new Vector2(0.5f, 1);
contentRect.anchoredPosition = Vector2.zero;
// 设置滚动视图引用
scrollRect.viewport = viewportRect;
scrollRect.content = contentRect;
scrollRect.horizontal = false;
scrollRect.vertical = true;
// 创建菜单项
foreach (var item in menuData.menuItems)
{
GenerateMenuItem(item, contentObj.transform, 0);
}
// 确保UI更新
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate(contentRect);
Debug.Log("菜单生成完成!");
}
// 生成单个菜单项
private void GenerateMenuItem(TreeMenuItem item, Transform parent, int level)
{
// 创建菜单项对象
GameObject menuItemObj = new GameObject(item.name);
menuItemObj.transform.SetParent(parent, false);
// 添加按钮组件
Button button = menuItemObj.AddComponent<Button>();
ColorBlock colors = button.colors;
colors.normalColor = menuData.backgroundColor;
colors.highlightedColor = menuData.highlightColor;
colors.pressedColor = new Color(
menuData.highlightColor.r * 0.8f,
menuData.highlightColor.g * 0.8f,
menuData.highlightColor.b * 0.8f
);
button.colors = colors;
// 添加图像组件作为背景
Image image = menuItemObj.AddComponent<Image>();
image.color = menuData.backgroundColor;
// 设置RectTransform
RectTransform rect = menuItemObj.GetComponent<RectTransform>();
rect.anchorMin = new Vector2(0, 0);
rect.anchorMax = new Vector2(1, 0);
rect.pivot = new Vector2(0.5f, 0.5f);
rect.sizeDelta = new Vector2(0, menuData.itemHeight);
// 确保设置了明确的高度
LayoutElement layoutElement = menuItemObj.AddComponent<LayoutElement>();
layoutElement.minHeight = menuData.itemHeight;
layoutElement.preferredHeight = menuData.itemHeight;
layoutElement.flexibleHeight = 0;
// 创建文本对象
GameObject textObj = new GameObject("Text");
textObj.transform.SetParent(menuItemObj.transform, false);
Text text = textObj.AddComponent<Text>();
text.text = item.name;
text.font = menuData.menuFont != null ? menuData.menuFont : Resources.GetBuiltinResource<Font>("Arial.ttf");
text.fontSize = menuData.fontSize;
text.color = menuData.textColor;
text.alignment = TextAnchor.MiddleLeft;
RectTransform textRect = textObj.GetComponent<RectTransform>();
textRect.anchorMin = new Vector2(0, 0);
textRect.anchorMax = new Vector2(1, 1);
textRect.pivot = new Vector2(0.5f, 0.5f);
float indent = level * menuData.indentWidth;
textRect.offsetMin = new Vector2(10 + indent, 0);
textRect.offsetMax = new Vector2(-10, 0);
// 处理子菜单项
if (item.children != null && item.children.Count > 0)
{
foreach (var child in item.children)
{
GenerateMenuItem(child, parent, level + 1);
}
}
}
}
}