859 lines
31 KiB
C#
Raw Normal View History

2025-06-07 17:43:34 +08:00
#if !BUILDING
using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UI;
namespace EditorCreateUI
{
public partial class Editor_CreateUIPanel : EditorWindowBase
{
private GenerateAttData[] generateAttDatas = new GenerateAttData[]
{
new GenerateAttData("Ga_", "对象", "GameObject"),
new GenerateAttData("Tr_", "对象", "Transform"),
new GenerateAttData("Rect_", "RectTransform", "RectTransform"),
new GenerateAttData("Content_", "容器", "Transform"),
new GenerateAttData("Btn_", "按钮", "Button"),
new GenerateAttData("Tcg_", "复选框组", "TgCheckGroup"),
new GenerateAttData("Av_", "视频ui", "DisplayUGUI", "using RenderHeads.Media.AVProVideo;"),
new GenerateAttData("Tx_", "文本", "TextMeshProUGUI", "using TMPro;"),
new GenerateAttData("Img_", "图片", "Image或RawImage"),
new GenerateAttData("Sr_", "滑动列表", "ScrollRect"),
new GenerateAttData("Rv_", "无限列表", "RecycleView"),
new GenerateAttData("Ev_", "无限列表(带标签)", "ExpandableView"),
new GenerateAttData("Evs_", "无限列表(带标签,单二级)", "ExpandableViewSingle"),
new GenerateAttData("Input_", "输入框", "TMP_InputField", "using TMPro;"),
new GenerateAttData("Slider_", "滑动条", "Slider"),
new GenerateAttData("Toggle_", "复选框", "Toggle"),
new GenerateAttData("TG_", "复选组", "TgGroup"),
new GenerateAttData("Lt_", "多语言文本", "LanguageText"),
new GenerateAttData("Li_", "多语言图片", "LanguageImage"),
new GenerateAttData("Ldt_", "倒计时", "LanguageDownTime"),
new GenerateAttData("Tm_", "TMP文本", "TextMeshProUGUI", "using TMPro;"),
new GenerateAttData("Cam_", "摄像机", "Camera"),
new GenerateAttData("Class_{T}", "自定义类型", "{T}"),
};
private static string[] notClose_raycast = new string[]
{
"Scroll View",
"btn_",
"ray",
"sv_",
"close",
};
private static string[] notFindChild = new string[]
{
"UI_"
};
private string panelName = "";
private int selectedOptionIndex = 0;
private Vector2 scrollPosition = Vector2.zero;
string systemName;
/// <summary>
/// child, panelName, prefabDir
/// </summary>
List<Tuple<bool, string, string>> lastPanelName = new List<Tuple<bool, string, string>>();
int lastPanelIndex = 0;
[MenuItem("Window/Create UI Panel")]
public static void ShowWindow()
{
var window = EditorWindow.GetWindow(typeof(Editor_CreateUIPanel));
window.titleContent = new GUIContent("UI管理工具");
}
private void ShowTips()
{
try
{
if (position.width <= 350) return;
//GUILayout.Label("首字母大写前缀_名称。会自动创建变量。\n不是以下类型前缀的都自动生成为Transform。\n名称中带ray不会关闭RayCast");
scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true, GUILayout.ExpandHeight(true));
// 开始绘制表格布局
GUILayout.BeginVertical(GUI.skin.box);
// 表头
GUILayout.BeginHorizontal();
DrawTableCell("", 20, 20, Color.gray);
DrawTableCell("前缀", 150, 20, Color.gray);
DrawTableCell("类型", 150, 20, Color.gray);
DrawTableCell("描述", 150, 20, Color.gray);
GUILayout.EndHorizontal();
// 分隔线
DrawHorizontalLine(500);
// 数据行
for (int i = 0; i < generateAttDatas.Length; i++)
{
var data = generateAttDatas[i];
GUILayout.BeginHorizontal();
DrawTableCell((i + 1).ToString(), 20, 15, Color.gray);
DrawTableCell(data.prefix, 150);
DrawTableCell(data.type, 150);
DrawTableCell(data.description, 150);
GUILayout.EndHorizontal();
// 添加淡淡的分割线
DrawHorizontalLine(500, Color.gray);
}
// 结束绘制表格布局
GUILayout.EndVertical();
// 结束绘制滚动视图
GUILayout.EndScrollView();
}
catch (Exception)
{
}
}
bool CheckIsThis()
{
var sta = PrefabStageUtility.GetCurrentPrefabStage();
if (sta && sta.prefabContentsRoot)
{
if (sta.prefabContentsRoot.name == panelName)
{
return true;
}
}
return false;
}
bool GetIsSingle()
{
var sta = PrefabStageUtility.GetCurrentPrefabStage();
Debug.Log(sta.prefabContentsRoot);
return true;
}
string[] GetCompletion()
{
List<string> str = new List<string>();
// 指定目录
string directoryPath = "Assets/Scripts/HotUpdate/Game/UI";
DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath);
DirectoryInfo[] di = directoryInfo.GetDirectories();
for (int i = 0; i < di.Length; i++)
{
DirectoryInfo[] di2 = di[i].GetDirectories();
for (int j = 0; j < di2.Length; j++)
{
str.Add(di2[j].Name);
GetCompletion(ref str, di2[j]);
}
}
return str.ToArray();
}
void GetCompletion(ref List<string> str, DirectoryInfo dir)
{
DirectoryInfo[] dirs = dir.GetDirectories();
for (int i = 0; i < dirs.Length; i++)
{
var item = dirs[i];
if (item.Name.ToLower().StartsWith("ui_"))
{
str.Add(item.Name);
GetCompletion(ref str, item);
}
}
}
private void OnGUI()
{
//try
//{
//// 页签名称数组
//string[] tabNames = { "Main", "Child" };
//// 创建一个横向页签栏
//selectedTabIndex = GUILayout.Toolbar(selectedTabIndex, tabNames);
if (IsCreateChild == false)
OnMainGUI();
else
OnCreateChildGUI();
//if (GUI_Button("测试"))
// GetCompletion();
GUILayout.Space(20);
// 开始绘制滚动视图
ShowTool();
ShowTips();
//}
//catch (Exception)
//{
//}
}
private void CreatePanel(string systemName)
{
string panelName = this.panelName;
if (!this.panelName.StartsWith("UI_"))
panelName = "UI_" + this.panelName;
// 复制 UI_Example 目录
string sourceDir = "Assets/Art/UI/Panels/UI_Example";
string targetDir = "Assets/Art/UI/Panels/" + panelName;
if (!AssetDatabase.IsValidFolder(targetDir))
{
AssetDatabase.CopyAsset(sourceDir, targetDir);
AssetDatabase.Refresh();
}
else
{
Debug.LogWarning("已存在同名的面板。");
return;
}
// 将 UI_Example 文件复制到新目录
string sourceFile = Path.Combine(sourceDir, "UI_Example.prefab");
string destFile = Path.Combine(targetDir, panelName + ".prefab");
string deleteFile = Path.Combine(targetDir, "UI_Example.prefab");
File.Copy(sourceFile, destFile);
File.Delete(deleteFile);
string ToSourceFolder = $"Assets/Scripts/HotUpdate/Game/UI/{systemName}/{panelName}";
if (!Directory.Exists(ToSourceFolder))
Directory.CreateDirectory(ToSourceFolder);
AssetDatabase.Refresh();
//复制 UI_Example.cs 脚本并创建新脚本
string scriptSource = $"Assets/Scripts/HotUpdate/Game/UI/Normal/UI_Example/UI_Example.cs";
string scriptTarget = $"Assets/Scripts/HotUpdate/Game/UI/{systemName}/{panelName}/{panelName}.cs";
AssetDatabase.CopyAsset(scriptSource, scriptTarget);
// 复制 UI_Example_Generate.cs 脚本并创建新脚本
string scriptSource2 = $"Assets/Scripts/HotUpdate/Game/UI/Normal/UI_Example/UI_Example_Generate.cs";
string scriptTarget2 = $"Assets/Scripts/HotUpdate/Game/UI/{systemName}/{panelName}/{panelName}_Generate.cs";
AssetDatabase.CopyAsset(scriptSource2, scriptTarget2);
// 读取并替换脚本内容
string scriptPath = Path.Combine(Application.dataPath, $"Scripts/HotUpdate/Game/UI/{systemName}/{panelName}/{panelName}.cs");
string scriptContent = File.ReadAllText(scriptPath);
scriptContent = scriptContent.Replace("UI_Example", panelName);
// 将修改后的内容写回脚本文件
File.WriteAllText(scriptPath, scriptContent);
// 读取并替换脚本内容
string scriptPath2 = Path.Combine(Application.dataPath, $"Scripts/HotUpdate/Game/UI/{systemName}/{panelName}/{panelName}_Generate.cs");
string scriptContent2 = File.ReadAllText(scriptPath2);
scriptContent2 = scriptContent2.Replace("UI_Example", panelName);
// 将修改后的内容写回脚本文件
File.WriteAllText(scriptPath2, scriptContent2);
AssetDatabase.Refresh();
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(destFile);
if (prefab != null)
{
// 修改 tx_name 的文本属性
//Text textComponent = prefab.GetComponentInChildren<Text>();
//if (textComponent != null)
// textComponent.text = panelName.Substring(3);
//else
// Debug.LogError("在预制体中未找到 Text 组件。");
// 保存预制体的更改
PrefabUtility.SaveAsPrefabAsset(prefab, destFile);
var sceneView = SceneView.sceneViews[0] as SceneView;
// 激活主场景视图
if (sceneView != null)
sceneView.Focus();
Selection.activeObject = prefab;
AssetDatabase.OpenAsset(prefab);
if (!SceneView.lastActiveSceneView.in2DMode)
// 切换到 2D 模式
SceneView.lastActiveSceneView.in2DMode = true;
}
else
{
//Debug.LogError("在路径 " + destFile + " 中未找到预制体。");
}
AssetDatabase.Refresh();
}
bool IsSpecialName(string name)
{
return char.IsUpper(name[0]) && name.Contains("_") && !name.Contains(" ")
&& !name.Contains("(")
&& !name.Contains(")");
}
void AutoSetRaycast(Transform tr)
{
bool isNot = false;
for (int i = 0; i < notClose_raycast.Length; i++)
{
if (tr.name.ToLower().Contains(notClose_raycast[i].ToLower()))
{
isNot = true;
break;
}
}
if (!isNot)
{
//如果不是特殊命名
if (!IsSpecialName(tr.name))
{
var gra = tr.GetComponent<MaskableGraphic>();
if (gra != null)
gra.raycastTarget = false;
}
else
{
//Text都关掉 Raycast
var te = tr.GetComponent<Text>();
if (te != null)
te.raycastTarget = false;
}
}
for (int k = 0; k < tr.childCount; k++)
AutoSetRaycast(tr.GetChild(k));
}
void GenerateAttribute(string systemName)
{
string panelName = this.panelName;
if (!this.panelName.StartsWith("UI_"))
panelName = "UI_" + this.panelName;
string targetDir = "Assets/Art/UI/Panels/" + panelName;
string destFile = Path.Combine(targetDir, panelName + ".prefab");
string str =
@"using UnityEngine;
using UnityEngine.UI;
[USING]
//子类[NAME]
[UIClassName(typeof([NAME]))]
public abstract class [NAME]_Generate : UIRootPanelBase
{
[ATTRIBUTE]
protected abstract void Init();
internal override void InitAttribute()
{
[FIND]
Init();
}
}";
str = str.Replace("[NAME]", panelName);
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(destFile);
List<Tuple<string, string>> tuples = new List<Tuple<string, string>>();
string str_using = "";
Action<Transform> action = null;
action = (tr) =>
{
// 检查节点名字首字母是否为大写,并且带_
if (IsSpecialName(tr.name))
{
Transform target = tr.parent;
string namePath = tr.name;
while (target != prefab.transform)
{
namePath = target.name + "/" + namePath;
target = target.parent;
}
//Debug.Log("生成:" + tr.name);
string attStr;
string findStr;
if (tr.name.StartsWith("Img_"))
{
if (tr.GetComponent<RawImage>() != null)
{
attStr = "internal RawImage " + tr.name + ";";
findStr = $"{tr.name} = transform.Find(\"{namePath}\").GetComponent<RawImage>();";
}
else if (tr.GetComponent<Image>() != null)
{
attStr = "internal Image " + tr.name + ";";
findStr = $"{tr.name} = transform.Find(\"{namePath}\").GetComponent<Image>();";
}
else
{
attStr = "internal Transform " + tr.name + ";";
findStr = $"{tr.name} = transform.Find(\"{namePath}\");";
}
}
else if (tr.name.StartsWith("Btn_"))
{
if (tr.GetComponent<Button>() != null)
{
attStr = "internal Button " + tr.name + ";";
findStr = $"{tr.name} = transform.Find(\"{namePath}\").GetComponent<Button>();";
}
else
{
attStr = "internal GameObject " + tr.name + ";";
findStr = $"{tr.name} = transform.Find(\"{namePath}\").gameObject;";
}
}
else if (tr.name.StartsWith("Content_"))
{
attStr = "internal Transform " + tr.name + ";";
findStr = $"{tr.name} = transform.Find(\"{namePath}\");";
}
else if (tr.name.StartsWith("Ga_"))
{
attStr = "internal GameObject " + tr.name + ";";
findStr = $"{tr.name} = transform.Find(\"{namePath}\").gameObject;";
}
else if (tr.name.StartsWith("Class_"))
{
string componName = tr.name.Replace("Class_", "");
attStr = $"internal {componName} {tr.name};";
findStr = $"{tr.name} = transform.Find(\"{namePath}\").GetComponent<{componName}>();";
}
else
{
GenerateAttData gen = null;
foreach (var item in generateAttDatas)
if (tr.name.StartsWith(item.prefix))
{
gen = item;
break;
}
if (gen == null)
{
attStr = $"internal Transform {tr.name};";
findStr = $"{tr.name} = transform.Find(\"{namePath}\");";
}
else
{
attStr = $"internal {gen.type} {tr.name};";
findStr = $"{tr.name} = transform.Find(\"{namePath}\").GetComponent<{gen.type}>();";
foreach (var item_u in gen.usin)
{
if (!str_using.Contains(item_u))
str_using += $"{item_u}\n";
}
}
}
tuples.Add(Tuple.Create(attStr, findStr));
}
bool isFind = true;
foreach (var startW in notFindChild)
if (tr.name.StartsWith(startW))
{
isFind = false;
break;
}
if (isFind)
for (int k = 0; k < tr.childCount; k++)
action.Invoke(tr.GetChild(k));
};
for (int i = 0; i < prefab.transform.childCount; i++)
action(prefab.transform.GetChild(i));
StringBuilder atts = new StringBuilder();
StringBuilder finds = new StringBuilder();
for (int j = 0; j < tuples.Count; j++)
{
var tuple = tuples[j];
if (j == 0)
{
atts.Append(tuple.Item1);
finds.Append(tuple.Item2);
}
else
{
atts.Append("\n " + tuple.Item1);
finds.Append("\n " + tuple.Item2);
}
}
str = str.Replace("[ATTRIBUTE]", atts.ToString());
str = str.Replace("[FIND]", finds.ToString());
str = str.Replace("[USING]", str_using);
string scriptTarget2 = $"Assets/Scripts/HotUpdate/Game/UI/{systemName}/{panelName}/{panelName}_Generate.cs";
File.WriteAllText(scriptTarget2, str);
AssetDatabase.Refresh();
}
DirectoryInfo GetDir(string uiName)
{
string sysFolder = Path.Combine(Application.dataPath, "Scripts/HotUpdate/Game/UI");
var sysF = new DirectoryInfo(sysFolder);
//sys
foreach (var item in sysF.GetDirectories())
{
//主ui
foreach (var item2 in item.GetDirectories())
{
if (item2.Name == uiName)
{
return item2;
}
else
{
//子ui
DirectoryInfo[] allDirectories = item2.GetDirectories("*", SearchOption.AllDirectories);
foreach (var dir in allDirectories)
if (uiName == dir.Name)
return dir;
}
}
}
return null;
}
static string ConvertPath(string path)
{
string fixedPart = @"Assets\Scripts\HotUpdate\Game\UI";
var parts = path.Split('\\');
int startIndex = Array.IndexOf(parts, fixedPart.Split('\\').Last()) + 1;
return startIndex > 0 && startIndex < parts.Length
? string.Join(" → ", parts.Skip(startIndex))
: "Path does not contain the specified structure.";
}
void OpenPanel(bool isChild, string prefabDir)
{
if (!isChild) this.currentPanelScriptDir = null;
string prefabPath = $"{prefabDir}/{panelName}.prefab";
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
Selection.activeObject = prefab;
AssetDatabase.OpenAsset(prefab);
var sceneView = SceneView.sceneViews[0] as SceneView;
// 激活主场景视图
if (sceneView != null)
sceneView.Focus();
if (!SceneView.lastActiveSceneView.in2DMode)
// 切换到 2D 模式
SceneView.lastActiveSceneView.in2DMode = true;
//临时处理解决描边不正常的bug
var sta = PrefabStageUtility.GetCurrentPrefabStage();
}
private void OnMainGUI()
{
if (currentPanelScriptDir != null)
{
GUILayout.Label(ConvertPath(currentPanelScriptDir.FullName));
}
GUILayout.Label("输入面板名称", EditorStyles.boldLabel);
this.panelName = PlayerPrefs.GetString("editor_panelName");
//this.panelName = EditorGUILayout.TextField("面板名称: UI_", this.panelName);
// 绘制自动补全输入框
string[] suggestions = GetCompletion();
bool isClick = false;
bool isReturn = false;
bool isShowReturn = lastPanelName.Count > 1;
this.panelName = AutoCompleteTextField(this.panelName, suggestions, ref isClick, isShowReturn, ref isReturn);
PlayerPrefs.SetString("editor_panelName", this.panelName);
if (string.IsNullOrEmpty(this.panelName))
{
EditorGUILayout.HelpBox("输入名称后创建/打开面板", MessageType.Info);
//ShowTips();
return;
}
string panelName = this.panelName;
if (!this.panelName.StartsWith("UI_"))
panelName = "UI_" + this.panelName;
bool isExa = false;
if (panelName == "UI_Example")
{
GUILayout.Label("示例ui");
isExa = true;
}
string prefabDir = $"Assets/Art/UI/Panels/{panelName}";
if (AssetDatabase.IsValidFolder(prefabDir))
{
bool isChild = false;
string sysFolder = Path.Combine(Application.dataPath, "Scripts/HotUpdate/Game/UI");
var sysF = new DirectoryInfo(sysFolder);
var dirs = sysF.GetDirectories();
string[] dirNames = new string[dirs.Length];
for (int i = 0; i < dirs.Length; i++)
dirNames[i] = dirs[i].Name;
int index = -1;
for (int i = 0; i < dirs.Length; i++)
{
var dirs2 = dirs[i].GetDirectories();
for (int j = 0; j < dirs2.Length; j++)
if (dirs2[j].Name == panelName)
{
index = i;
break;
}
}
if (index == -1)
isChild = true;
this.currentPanelScriptDir = GetDir(panelName);
int newIndex;
if (!isChild)
{
newIndex = EditorGUILayout.Popup("系统目录:", index, dirNames);
systemName = dirNames[index];
if (newIndex != index)
{
Debug.Log("修改目录为" + dirNames[newIndex]);
string oldFolder = $"{Application.dataPath}/Scripts/HotUpdate/Game/UI/{systemName}/{panelName}";
string newFolder = $"{Application.dataPath}/Scripts/HotUpdate/Game/UI/{dirNames[newIndex]}";
MoveDirectory(oldFolder, newFolder);
}
}
if (isReturn)
{
//打开上一个面板
lastPanelName.RemoveAt(lastPanelName.Count - 1);
var last = lastPanelName[lastPanelName.Count - 1];
this.panelName = last.Item2;
OpenPanel(last.Item1, last.Item3);
PlayerPrefs.SetString("editor_panelName", this.panelName);
}
if (CheckIsThis())
{
if (GUI_Button("生成属性"))
{
if (isChild)
{
GenerateAttribute_Child(currentPanelScriptDir);
}
else
{
GenerateAttribute(systemName);
}
var sta = PrefabStageUtility.GetCurrentPrefabStage();
AutoSetRaycast(sta.prefabContentsRoot.transform);
}
//if (GUI_Button("关闭多余 Raycast Target"))
//{
// var sta = PrefabStageUtility.GetCurrentPrefabStage();
// AutoSetRaycast(sta.prefabContentsRoot.transform);
//}
if (isClick)
{
var sceneView = SceneView.sceneViews[0] as SceneView;
// 激活主场景视图
if (sceneView != null)
sceneView.Focus();
if (!SceneView.lastActiveSceneView.in2DMode)
// 切换到 2D 模式
SceneView.lastActiveSceneView.in2DMode = true;
}
}
else
{
if (GUI_Button("打开面板") || isClick)
{
if (panelName != "")
if (lastPanelName.Count == 0 || lastPanelName[lastPanelName.Count - 1].Item2 != panelName)
lastPanelName.Add(Tuple.Create(isChild, this.panelName, prefabDir));
OpenPanel(isChild, prefabDir);
}
}
if (GUI_Button("打开脚本"))
{
if (isChild)
{
string scriptTarget = Path.Combine(currentPanelScriptDir.FullName, $"{panelName}.cs");
Application.OpenURL(scriptTarget);
}
else
{
var openPath = $"Scripts/HotUpdate/Game/UI/{systemName}/{panelName}/{panelName}.cs";
string scriptTarget = Path.Combine(Application.dataPath, openPath).Replace('\\', '/');
Application.OpenURL(scriptTarget);
}
}
if (GUI_Button("创建子面板"))
{
int controlID = GUIUtility.GetControlID(FocusType.Keyboard);
GUIUtility.keyboardControl = controlID; // 退出输入状态
//this.currentPanelScriptDir = new DirectoryInfo(currentPanelScriptDir.FullName);
IsCreateChild = true;
}
if (isExa == false)
if (GUI_Button("删除界面"))
{
bool confirm = EditorUtility.DisplayDialog("是否删除?", "是否删除" + panelName, "确定", "取消");
if (confirm)
{
Directory.Delete(prefabDir, true);
File.Delete(prefabDir + ".meta");
if (isChild)
{
if (currentPanelScriptDir != null)
{
File.Delete(currentPanelScriptDir.FullName + ".meta");
currentPanelScriptDir.Delete(true);
}
}
else
{
string scriptFolder = $"{Application.dataPath}/Scripts/HotUpdate/Game/UI/{systemName}/{panelName}";
Directory.Delete(scriptFolder, true);
File.Delete(scriptFolder + ".meta");
}
AssetDatabase.Refresh();
}
}
GUILayout.Space(10);
}
else
{
string sysFolder = Path.Combine(Application.dataPath, "Scripts/HotUpdate/Game/UI");
var sysF = new DirectoryInfo(sysFolder);
var dirs = sysF.GetDirectories();
string[] dirNames = new string[dirs.Length];
for (int i = 0; i < dirs.Length; i++)
dirNames[i] = dirs[i].Name;
// 绘制下拉框控件并将选定的选项索引保存到selectedOptionIndex变量中
this.selectedOptionIndex = PlayerPrefs.GetInt("editor_system");
selectedOptionIndex = EditorGUILayout.Popup("创建到目录:", selectedOptionIndex, dirNames);
PlayerPrefs.SetInt("editor_system", this.selectedOptionIndex);
if (GUI_Button("创建面板"))
CreatePanel(dirNames[selectedOptionIndex]);
}
}
}
public class GenerateAttData
{
public string prefix;
public string description;
public string type;
public string[] usin;
public GenerateAttData(string prefix, string description, string type, params string[] usin)
{
this.prefix = prefix;
this.description = description;
this.type = type;
this.usin = usin;
}
}
}
#endif