From 9bffcce30e7cecedd0ca051071a98784e41a86af Mon Sep 17 00:00:00 2001 From: Alex-Rachel <574809918@qq.com> Date: Sat, 15 Mar 2025 00:42:23 +0800 Subject: [PATCH] UIScriptGenerator --- .../Editor/ScriptGeneratorSetting.asset | 79 +++++ .../Editor/ScriptGeneratorSetting.asset.meta | 8 + .../GameLogic/UI/BattleMainUI/BattleMainUI.cs | 26 +- UnityProject/Assets/TEngine/Editor/UI.meta | 3 + .../TEngine/Editor/UI/ScriptGenerator.cs | 303 ++++++++++++++++++ .../TEngine/Editor/UI/ScriptGenerator.cs.meta | 11 + .../Editor/UI/ScriptGeneratorSetting.cs | 185 +++++++++++ .../Editor/UI/ScriptGeneratorSetting.cs.meta | 3 + 8 files changed, 614 insertions(+), 4 deletions(-) create mode 100644 UnityProject/Assets/Editor/ScriptGeneratorSetting.asset create mode 100644 UnityProject/Assets/Editor/ScriptGeneratorSetting.asset.meta create mode 100644 UnityProject/Assets/TEngine/Editor/UI.meta create mode 100644 UnityProject/Assets/TEngine/Editor/UI/ScriptGenerator.cs create mode 100644 UnityProject/Assets/TEngine/Editor/UI/ScriptGenerator.cs.meta create mode 100644 UnityProject/Assets/TEngine/Editor/UI/ScriptGeneratorSetting.cs create mode 100644 UnityProject/Assets/TEngine/Editor/UI/ScriptGeneratorSetting.cs.meta diff --git a/UnityProject/Assets/Editor/ScriptGeneratorSetting.asset b/UnityProject/Assets/Editor/ScriptGeneratorSetting.asset new file mode 100644 index 00000000..7d67d5b6 --- /dev/null +++ b/UnityProject/Assets/Editor/ScriptGeneratorSetting.asset @@ -0,0 +1,79 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f8a0dfbcd6854965bb6ae78627009a53, type: 3} + m_Name: ScriptGeneratorSetting + m_EditorClassIdentifier: + _codePath: + _namespace: GameLogic + _widgetName: item + CodeStyle: 0 + scriptGenerateRule: + - uiElementRegex: m_go + componentName: GameObject + isUIWidget: 0 + - uiElementRegex: m_item + componentName: GameObject + isUIWidget: 0 + - uiElementRegex: m_tf + componentName: Transform + isUIWidget: 0 + - uiElementRegex: m_rect + componentName: RectTransform + isUIWidget: 0 + - uiElementRegex: m_text + componentName: Text + isUIWidget: 0 + - uiElementRegex: m_richText + componentName: RichTextItem + isUIWidget: 0 + - uiElementRegex: m_btn + componentName: Button + isUIWidget: 0 + - uiElementRegex: m_img + componentName: Image + isUIWidget: 0 + - uiElementRegex: m_rimg + componentName: RawImage + isUIWidget: 0 + - uiElementRegex: m_scrollBar + componentName: Scrollbar + isUIWidget: 0 + - uiElementRegex: m_scroll + componentName: ScrollRect + isUIWidget: 0 + - uiElementRegex: m_input + componentName: InputField + isUIWidget: 0 + - uiElementRegex: m_grid + componentName: GridLayoutGroup + isUIWidget: 0 + - uiElementRegex: m_hlay + componentName: HorizontalLayoutGroup + isUIWidget: 0 + - uiElementRegex: m_vlay + componentName: VerticalLayoutGroup + isUIWidget: 0 + - uiElementRegex: m_slider + componentName: Slider + isUIWidget: 0 + - uiElementRegex: m_group + componentName: ToggleGroup + isUIWidget: 0 + - uiElementRegex: m_curve + componentName: AnimationCurve + isUIWidget: 0 + - uiElementRegex: m_canvasGroup + componentName: CanvasGroup + isUIWidget: 0 + - uiElementRegex: m_tmp + componentName: TextMeshProUGUI + isUIWidget: 0 diff --git a/UnityProject/Assets/Editor/ScriptGeneratorSetting.asset.meta b/UnityProject/Assets/Editor/ScriptGeneratorSetting.asset.meta new file mode 100644 index 00000000..3924a6d2 --- /dev/null +++ b/UnityProject/Assets/Editor/ScriptGeneratorSetting.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fe8fecac349dd7a4cafb77822cdba64a +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/GameScripts/HotFix/GameLogic/UI/BattleMainUI/BattleMainUI.cs b/UnityProject/Assets/GameScripts/HotFix/GameLogic/UI/BattleMainUI/BattleMainUI.cs index 9c389fd3..12557c87 100644 --- a/UnityProject/Assets/GameScripts/HotFix/GameLogic/UI/BattleMainUI/BattleMainUI.cs +++ b/UnityProject/Assets/GameScripts/HotFix/GameLogic/UI/BattleMainUI/BattleMainUI.cs @@ -1,12 +1,30 @@ -using System.Collections; -using UnityEngine; -using TMPro; +using UnityEngine; using UnityEngine.UI; +using TEngine; namespace GameLogic { - [Window(UILayer.Bottom, fullScreen: true)] + [Window(UILayer.UI)] class BattleMainUI : UIWindow { + #region 脚本工具生成的代码 + private RectTransform _rectContainer; + private GameObject _itemTouch; + private GameObject _goTopInfo; + private GameObject _itemRoleInfo; + private GameObject _itemMonsterInfo; + protected override void ScriptGenerator() + { + _rectContainer = FindChildComponent("m_rectContainer"); + _itemTouch = FindChild("m_rectContainer/m_itemTouch").gameObject; + _goTopInfo = FindChild("m_goTopInfo").gameObject; + _itemRoleInfo = FindChild("m_goTopInfo/m_itemRoleInfo").gameObject; + _itemMonsterInfo = FindChild("m_goTopInfo/m_itemMonsterInfo").gameObject; + } + #endregion + + #region 事件 + #endregion + } } \ No newline at end of file diff --git a/UnityProject/Assets/TEngine/Editor/UI.meta b/UnityProject/Assets/TEngine/Editor/UI.meta new file mode 100644 index 00000000..7540c63a --- /dev/null +++ b/UnityProject/Assets/TEngine/Editor/UI.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 12cde5e16fa64eab883975bd63fc5077 +timeCreated: 1741968067 \ No newline at end of file diff --git a/UnityProject/Assets/TEngine/Editor/UI/ScriptGenerator.cs b/UnityProject/Assets/TEngine/Editor/UI/ScriptGenerator.cs new file mode 100644 index 00000000..e28a4991 --- /dev/null +++ b/UnityProject/Assets/TEngine/Editor/UI/ScriptGenerator.cs @@ -0,0 +1,303 @@ +using System.Collections.Generic; +using System.Text; +using UnityEditor; +using UnityEngine; + +namespace TEngine.Editor.UI +{ + public class ScriptGenerator + { + private const string Gap = "/"; + + [MenuItem("GameObject/ScriptGenerator/UIProperty", priority = 41)] + public static void MemberProperty() + { + Generate(false); + } + + [MenuItem("GameObject/ScriptGenerator/UIProperty - UniTask", priority = 43)] + public static void MemberPropertyUniTask() + { + Generate(false, true); + } + + [MenuItem("GameObject/ScriptGenerator/UIPropertyAndListener", priority = 42)] + public static void MemberPropertyAndListener() + { + Generate(true); + } + + [MenuItem("GameObject/ScriptGenerator/UIPropertyAndListener - UniTask", priority = 44)] + public static void MemberPropertyAndListenerUniTask() + { + Generate(true, true); + } + + private static void Generate(bool includeListener, bool isUniTask = false) + { + var root = Selection.activeTransform; + if (root != null) + { + StringBuilder strVar = new StringBuilder(); + StringBuilder strBind = new StringBuilder(); + StringBuilder strOnCreate = new StringBuilder(); + StringBuilder strCallback = new StringBuilder(); + Ergodic(root, root, ref strVar, ref strBind, ref strOnCreate, ref strCallback, isUniTask); + StringBuilder strFile = new StringBuilder(); + + if (includeListener) + { +#if ENABLE_TEXTMESHPRO + strFile.Append("using TMPro;\n"); +#endif + if (isUniTask) + { + strFile.Append("using Cysharp.Threading.Tasks;\n"); + } + + strFile.Append("using UnityEngine;\n"); + strFile.Append("using UnityEngine.UI;\n"); + strFile.Append("using TEngine;\n\n"); + strFile.Append($"namespace {ScriptGeneratorSetting.GetUINameSpace()}\n"); + strFile.Append("{\n"); + strFile.Append("\t[Window(UILayer.UI)]\n"); + strFile.Append("\tclass " + root.name + " : UIWindow\n"); + strFile.Append("\t{\n"); + } + + // 脚本工具生成的代码 + strFile.Append("\t\t#region 脚本工具生成的代码\n"); + strFile.Append(strVar); + strFile.Append("\t\tprotected override void ScriptGenerator()\n"); + strFile.Append("\t\t{\n"); + strFile.Append(strBind); + strFile.Append(strOnCreate); + strFile.Append("\t\t}\n"); + strFile.Append("\t\t#endregion"); + + if (includeListener) + { + strFile.Append("\n\n"); + // #region 事件 + strFile.Append("\t\t#region 事件\n"); + strFile.Append(strCallback); + strFile.Append("\t\t#endregion\n\n"); + + strFile.Append("\t}\n"); + strFile.Append("}\n"); + } + + TextEditor te = new TextEditor(); + te.text = strFile.ToString(); + te.SelectAll(); + te.Copy(); + } + + Debug.Log($"脚本已生成到剪贴板,请自行Ctl+V粘贴"); + } + + public static void Ergodic(Transform root, Transform transform, ref StringBuilder strVar, ref StringBuilder strBind, ref StringBuilder strOnCreate, + ref StringBuilder strCallback, bool isUniTask) + { + for (int i = 0; i < transform.childCount; ++i) + { + Transform child = transform.GetChild(i); + WriteScript(root, child, ref strVar, ref strBind, ref strOnCreate, ref strCallback, isUniTask); + if (child.name.StartsWith("m_item")) + { + continue; + } + + Ergodic(root, child, ref strVar, ref strBind, ref strOnCreate, ref strCallback, isUniTask); + } + } + + private static string GetRelativePath(Transform child, Transform root) + { + StringBuilder path = new StringBuilder(); + path.Append(child.name); + while (child.parent != null && child.parent != root) + { + child = child.parent; + path.Insert(0, Gap); + path.Insert(0, child.name); + } + + return path.ToString(); + } + + public static string GetBtnFuncName(string varName) + { + var codeStyle = ScriptGeneratorSetting.Instance.CodeStyle; + if (codeStyle == UIFieldCodeStyle.MPrefix) + { + return "OnClick" + varName.Replace("m_btn", string.Empty) + "Btn"; + } + else + { + return "OnClick" + varName.Replace("_btn", string.Empty) + "Btn"; + } + } + + public static string GetToggleFuncName(string varName) + { + var codeStyle = ScriptGeneratorSetting.Instance.CodeStyle; + if (codeStyle == UIFieldCodeStyle.MPrefix) + { + return "OnToggle" + varName.Replace("m_toggle", string.Empty) + "Change"; + } + else + { + return "OnToggle" + varName.Replace("_toggle", string.Empty) + "Change"; + } + } + + public static string GetSliderFuncName(string varName) + { + var codeStyle = ScriptGeneratorSetting.Instance.CodeStyle; + if (codeStyle == UIFieldCodeStyle.MPrefix) + { + return "OnSlider" + varName.Replace("m_slider", string.Empty) + "Change"; + } + else + { + return "OnSlider" + varName.Replace("_slider", string.Empty) + "Change"; + } + } + + private static void WriteScript(Transform root, Transform child, ref StringBuilder strVar, ref StringBuilder strBind, ref StringBuilder strOnCreate, + ref StringBuilder strCallback, bool isUniTask) + { + string varName = child.name; + + string componentName = string.Empty; + + var rule = ScriptGeneratorSetting.GetScriptGenerateRule().Find(t => varName.StartsWith(t.uiElementRegex)); + + if (rule != null) + { + componentName = rule.componentName; + } + + bool isUIWidget = rule is { isUIWidget: true }; + + if (componentName == string.Empty) + { + return; + } + + var codeStyle = ScriptGeneratorSetting.Instance.CodeStyle; + if (codeStyle == UIFieldCodeStyle.UnderscorePrefix) + { + if (varName.StartsWith("_")) + { + + } + else if(varName.StartsWith("m_")) + { + varName = varName.Substring(1); + } + else + { + varName = $"_{varName}"; + } + } + else if (codeStyle == UIFieldCodeStyle.MPrefix) + { + if (varName.StartsWith("m_")) + { + + } + else if (varName.StartsWith("_")) + { + varName = $"m{varName}"; + } + else + { + varName = $"m_{varName}"; + } + } + + string varPath = GetRelativePath(child, root); + if (!string.IsNullOrEmpty(varName)) + { + strVar.Append("\t\tprivate " + componentName + " " + varName + ";\n"); + switch (componentName) + { + case "Transform": + strBind.Append($"\t\t\t{varName} = FindChild(\"{varPath}\");\n"); + break; + case "GameObject": + strBind.Append($"\t\t\t{varName} = FindChild(\"{varPath}\").gameObject;\n"); + break; + case "AnimationCurve": + strBind.Append($"\t\t\t{varName} = FindChildComponent(\"{varPath}\").m_animCurve;\n"); + break; + default: + if (isUIWidget) + { + strBind.Append($"\t\t\t{varName} = CreateWidgetByType<{componentName}>(\"{varPath}\");\n"); + } + strBind.Append($"\t\t\t{varName} = FindChildComponent<{componentName}>(\"{varPath}\");\n"); + break; + } + + if (componentName == "Button") + { + string varFuncName = GetBtnFuncName(varName); + if (isUniTask) + { + strOnCreate.Append($"\t\t\t{varName}.onClick.AddListener(UniTask.UnityAction({varFuncName}));\n"); + strCallback.Append($"\t\tprivate async UniTaskVoid {varFuncName}()\n"); + strCallback.Append("\t\t{\n await UniTask.Yield();\n\t\t}\n"); + } + else + { + strOnCreate.Append($"\t\t\t{varName}.onClick.AddListener({varFuncName});\n"); + strCallback.Append($"\t\tprivate void {varFuncName}()\n"); + strCallback.Append("\t\t{\n\t\t}\n"); + } + } + else if (componentName == "Toggle") + { + string varFuncName = GetToggleFuncName(varName); + strOnCreate.Append($"\t\t\t{varName}.onValueChanged.AddListener({varFuncName});\n"); + strCallback.Append($"\t\tprivate void {varFuncName}(bool isOn)\n"); + strCallback.Append("\t\t{\n\t\t}\n"); + } + else if (componentName == "Slider") + { + string varFuncName = GetSliderFuncName(varName); + strOnCreate.Append($"\t\t\t{varName}.onValueChanged.AddListener({varFuncName});\n"); + strCallback.Append($"\t\tprivate void {varFuncName}(float value)\n"); + strCallback.Append("\t\t{\n\t\t}\n"); + } + } + } + + public class GeneratorHelper : EditorWindow + { + [MenuItem("GameObject/ScriptGenerator/About", priority = 49)] + public static void About() + { + GeneratorHelper welcomeWindow = (GeneratorHelper)EditorWindow.GetWindow(typeof(GeneratorHelper), false, "About"); + } + + public void Awake() + { + minSize = new Vector2(400, 600); + } + + protected void OnGUI() + { + GUILayout.BeginVertical(); + foreach (var item in ScriptGeneratorSetting.GetScriptGenerateRule()) + { + GUILayout.Label(item.uiElementRegex + ":\t" + item.componentName); + } + + GUILayout.EndVertical(); + } + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/TEngine/Editor/UI/ScriptGenerator.cs.meta b/UnityProject/Assets/TEngine/Editor/UI/ScriptGenerator.cs.meta new file mode 100644 index 00000000..fcca71e2 --- /dev/null +++ b/UnityProject/Assets/TEngine/Editor/UI/ScriptGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9e5b80682b2036f42b69fcb09c53863a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/TEngine/Editor/UI/ScriptGeneratorSetting.cs b/UnityProject/Assets/TEngine/Editor/UI/ScriptGeneratorSetting.cs new file mode 100644 index 00000000..aab9c6dc --- /dev/null +++ b/UnityProject/Assets/TEngine/Editor/UI/ScriptGeneratorSetting.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace TEngine.Editor.UI +{ + public enum UIFieldCodeStyle + { + /// + /// Field names start with underscore (e.g., _variable) + /// + [InspectorName("Field names start with underscore (e.g., _variable)")] + UnderscorePrefix, + + /// + /// Field names start with m_ prefix (e.g., m_variable) + /// + [InspectorName("Field names start with m_ prefix (e.g., m_variable)")] + MPrefix, + } + + [Serializable] + public class ScriptGenerateRuler + { + public string uiElementRegex; + public string componentName; + public bool isUIWidget = false; + + public ScriptGenerateRuler(string uiElementRegex, string componentName, bool isUIWidget = false) + { + this.uiElementRegex = uiElementRegex; + this.componentName = componentName; + this.isUIWidget = isUIWidget; + } + } + + [CustomPropertyDrawer(typeof(ScriptGenerateRuler))] + public class ScriptGenerateRulerDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + EditorGUI.BeginProperty(position, label, property); + position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); + var indent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + var uiElementRegexRect = new Rect(position.x, position.y, 120, position.height); + var componentNameRect = new Rect(position.x + 125, position.y, 150, position.height); + var isUIWidgetRect = new Rect(position.x + 325, position.y, 150, position.height); + EditorGUI.PropertyField(uiElementRegexRect, property.FindPropertyRelative("uiElementRegex"), GUIContent.none); + EditorGUI.PropertyField(componentNameRect, property.FindPropertyRelative("componentName"), GUIContent.none); + EditorGUI.PropertyField(isUIWidgetRect, property.FindPropertyRelative("isUIWidget"), GUIContent.none); + EditorGUI.indentLevel = indent; + EditorGUI.EndProperty(); + } + } + + [CreateAssetMenu(menuName = "TEngine/ScriptGeneratorSetting", fileName = "ScriptGeneratorSetting")] + public class ScriptGeneratorSetting : ScriptableObject + { + private static ScriptGeneratorSetting _instance; + + public static ScriptGeneratorSetting Instance + { + get + { + if (_instance == null) + { + string[] guids = AssetDatabase.FindAssets("t:ScriptGeneratorSetting"); + if (guids.Length >= 1) + { + string path = AssetDatabase.GUIDToAssetPath(guids[0]); + _instance = AssetDatabase.LoadAssetAtPath(path); + } + } + return _instance; + } + } + + // [FolderPath] + // [LabelText("默认组件代码保存路径")] + [SerializeField] + private string _codePath; + + // [LabelText("绑定代码命名空间")] + [SerializeField] + private string _namespace = "GameLogic"; + + // [LabelText("子组件名称(不会往下继续遍历)")] + [SerializeField] + private string _widgetName = "item"; + + public string CodePath => _codePath; + + public string Namespace => _namespace; + + public string WidgetName => _widgetName; + + public UIFieldCodeStyle CodeStyle = UIFieldCodeStyle.UnderscorePrefix; + + [SerializeField] + private List scriptGenerateRule = new List() + { + new ScriptGenerateRuler("m_go", "GameObject"), + new ScriptGenerateRuler("m_item", "GameObject"), + new ScriptGenerateRuler("m_tf", "Transform"), + new ScriptGenerateRuler("m_rect", "RectTransform"), + new ScriptGenerateRuler("m_text", "Text"), + new ScriptGenerateRuler("m_richText", "RichTextItem"), + new ScriptGenerateRuler("m_btn", "Button"), + new ScriptGenerateRuler("m_img", "Image"), + new ScriptGenerateRuler("m_rimg", "RawImage"), + new ScriptGenerateRuler("m_scrollBar", "Scrollbar"), + new ScriptGenerateRuler("m_scroll", "ScrollRect"), + new ScriptGenerateRuler("m_input", "InputField"), + new ScriptGenerateRuler("m_grid", "GridLayoutGroup"), + new ScriptGenerateRuler("m_hlay", "HorizontalLayoutGroup"), + new ScriptGenerateRuler("m_vlay", "VerticalLayoutGroup"), + new ScriptGenerateRuler("m_slider", "Slider"), + new ScriptGenerateRuler("m_group", "ToggleGroup"), + new ScriptGenerateRuler("m_curve", "AnimationCurve"), + new ScriptGenerateRuler("m_canvasGroup", "CanvasGroup"), + new ScriptGenerateRuler("m_tmp","TextMeshProUGUI"), + }; + + public List ScriptGenerateRule => scriptGenerateRule; + + + [MenuItem("TEngine/Create ScriptGeneratorSetting")] + private static void CreateAutoBindGlobalSetting() + { + string[] paths = AssetDatabase.FindAssets("t:ScriptGeneratorSetting"); + if (paths.Length >= 1) + { + string path = AssetDatabase.GUIDToAssetPath(paths[0]); + EditorUtility.DisplayDialog("警告", $"已存在ScriptGeneratorSetting,路径:{path}", "确认"); + return; + } + + ScriptGeneratorSetting setting = CreateInstance(); + AssetDatabase.CreateAsset(setting, "Assets/Editor/ScriptGeneratorSetting.asset"); + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + } + + public static List GetScriptGenerateRule() + { + if (Instance == null) + { + return null; + } + return Instance.ScriptGenerateRule; + } + + public static string GetUINameSpace() + { + if (Instance == null) + { + return string.Empty; + } + + return Instance.Namespace; + } + + public static UIFieldCodeStyle GetCodeStyle() + { + if (Instance == null) + { + return UIFieldCodeStyle.UnderscorePrefix; + } + + return Instance.CodeStyle; + } + + public static string GetCodePath() + { + if (Instance == null) + { + return string.Empty; + } + + return Instance.CodePath; + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/TEngine/Editor/UI/ScriptGeneratorSetting.cs.meta b/UnityProject/Assets/TEngine/Editor/UI/ScriptGeneratorSetting.cs.meta new file mode 100644 index 00000000..7474eca3 --- /dev/null +++ b/UnityProject/Assets/TEngine/Editor/UI/ScriptGeneratorSetting.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f8a0dfbcd6854965bb6ae78627009a53 +timeCreated: 1741968155 \ No newline at end of file