463 lines
19 KiB
C#
463 lines
19 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} |