using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using AlicizaX.UI.Editor; using AlicizaX.UI.Runtime; using Sirenix.Utilities.Editor; using UnityEngine; using UnityEditor; using UnityEditor.Callbacks; using UnityEngine.UI; namespace AlicizaX.UI.Editor { enum EBindType { None, Widget, ListCom, } [Serializable] class UIBindData { public string Name; public List BindCom; public EBindType BindType; public UIBindData(string name, List bindCom, EBindType bindType = EBindType.None) { Name = name; BindCom = bindCom; BindType = bindType; } public UIBindData(string name, Component bindCom, EBindType bindType = EBindType.None) { Name = name; BindCom = new List() { bindCom }; BindType = bindType; } } internal static class UIScriptGeneratorHelper { private static UIGenerateConfiguration _uiGenerateConfiguration; static UIGenerateConfiguration UIGenerateConfiguration { get { if (_uiGenerateConfiguration == null) { _uiGenerateConfiguration = UIGenerateConfiguration.Instance; } return _uiGenerateConfiguration; } } private static string GetVerType(string uiName) { foreach (var pair in UIGenerateConfiguration.UIElementRegexConfigs) { if (uiName.StartsWith(pair.uiElementRegex)) { return pair.componentType; } } return string.Empty; } private static string[] SplitComName(string name) { bool hasCom = name.Contains(UIGenerateConfiguration.UIGenerateCommonData.ComCheckEndName); if (!hasCom) return null; string comStr = name.Substring(0, name.IndexOf(UIGenerateConfiguration.UIGenerateCommonData.ComCheckEndName)); return comStr.Split(UIGenerateConfiguration.UIGenerateCommonData.ComCheckSplitName); } private static string GetKeyName(string key, string componentName) { return $"{key}{componentName.Substring(componentName.IndexOf(UIGenerateConfiguration.UIGenerateCommonData.ComCheckEndName) + 1)}"; } private static List UIBindDatas = new List(); private static string GenerateNameSpace = string.Empty; private static List ArrayComs = new List(); private static void GetBindData(Transform root) { for (int i = 0; i < root.childCount; ++i) { Transform child = root.GetChild(i); bool hasWdiget = child.GetComponent() != null; if (UIGenerateConfiguration.UIGenerateCommonData.ExcludeKeywords.Any(k => child.name.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0)) continue; bool isArrayComs = child.name.StartsWith(UIGenerateConfiguration.UIGenerateCommonData.ArrayComSplitName); if (hasWdiget) { CollectWidget(child); } else if (isArrayComs) { string splitCode = UIGenerateConfiguration.UIGenerateCommonData.ArrayComSplitName; int lastIndex = child.name.LastIndexOf(splitCode); string text = child.name.Substring(child.name.IndexOf(splitCode) + 1, lastIndex - 1); if (ArrayComs.Contains(text)) continue; ArrayComs.Add(text); List arrayComs = new List(); for (int j = 0; j < root.childCount; j++) { if (root.GetChild(j).name.Contains(text)) { arrayComs.Add(root.GetChild(j)); } } CollectArrayComponent(arrayComs, text); } else if (!isArrayComs && !hasWdiget) { CollectComponent(child); GetBindData(child); } } } private static void CollectComponent(Transform node) { string[] comArray = SplitComName(node.name); if (comArray != null) { foreach (var com in comArray) { string typeName = GetVerType(com); if (string.IsNullOrEmpty(typeName)) continue; Component component = node.GetComponent(typeName); if (component != null) { string keyName = GetKeyName(com, node.name); if (UIBindDatas.Exists(a => a.Name == keyName)) { Debug.LogError($"Duplicate key found: {keyName}"); continue; } UIBindDatas.Add(new UIBindData(keyName, component)); } else { Debug.LogError($"{node.name} does not have component of type {typeName}"); } } } } private static void CollectWidget(Transform node) { if (node.name.IndexOf(UIGenerateConfiguration.UIGenerateCommonData.ComCheckEndName) != -1 && node.name.IndexOf(UIGenerateConfiguration.UIGenerateCommonData.ComCheckSplitName) != -1) { Debug.LogWarning($"{node.name} 子组件不能包含规则定义符号!"); return; } UIHolderObjectBase component = node.GetComponent(); string keyName = node.name; if (UIBindDatas.Exists(a => a.Name == keyName)) { Debug.LogError($"Duplicate key found: {keyName}"); return; } UIBindDatas.Add(new UIBindData(keyName, component, EBindType.Widget)); } private static void CollectArrayComponent(List arrayNode, string nodeName) { string[] comArray = SplitComName(nodeName); arrayNode = arrayNode.OrderBy(s => int.Parse(s.name.Split('*').Last())).ToList(); List tempBindDatas = new List(comArray.Length); if (comArray != null) { int index = 0; foreach (var com in comArray) { foreach (var node in arrayNode) { string typeName = GetVerType(com); if (string.IsNullOrEmpty(typeName)) continue; Component component = node.GetComponent(typeName); if (component != null) { string keyName = GetKeyName(com, nodeName) + "List"; if (tempBindDatas.Count - 1 < index) tempBindDatas.Add(new UIBindData(keyName, new List(), EBindType.ListCom)); tempBindDatas[index].BindCom.Add(component); } else { Debug.LogError($"{node.name} does not have component of type {typeName}"); } } index++; } } UIBindDatas.AddRange(tempBindDatas.ToArray()); } private static string GetRefrenceNameSpace() { StringBuilder refrenceNameSpaceBuilder = new StringBuilder(); HashSet nameSpaces = new HashSet(); nameSpaces.Add("UnityEngine"); refrenceNameSpaceBuilder.Append($"using UnityEngine;\n"); foreach (var bindData in UIBindDatas) { string nameSpace = bindData.BindCom.FirstOrDefault().GetType().Namespace; if (bindData.BindType == EBindType.ListCom) { if (!nameSpaces.Contains("using System.Collections.Generic;")) { refrenceNameSpaceBuilder.Append("using System.Collections.Generic;\n"); } } if (!nameSpaces.Contains(nameSpace) && !string.IsNullOrEmpty(nameSpace)) { nameSpaces.Add(nameSpace); refrenceNameSpaceBuilder.Append($"using {nameSpace};\n"); } } return refrenceNameSpaceBuilder.ToString(); } private static string GetVarText(List uiBindDatas) { StringBuilder varTextBuilder = new StringBuilder(); foreach (var bindData in uiBindDatas) { var varName = bindData.Name; varTextBuilder.Append("\t\t[SerializeField]\n"); varTextBuilder.Append("\t\t[ReadOnly]\n"); varTextBuilder.Append("\t\t[HideLabel]\n"); if (bindData.BindType == EBindType.None) { varTextBuilder.Append($"\t\tprivate {bindData.BindCom.FirstOrDefault().GetType().Name} m{varName};\n"); varTextBuilder.Append($"\t\tpublic {bindData.BindCom.FirstOrDefault().GetType().Name} {varName} => m{varName};\n\n"); } else if (bindData.BindType == EBindType.ListCom) { varTextBuilder.Append($"\t\tprivate {bindData.BindCom.FirstOrDefault().GetType().Name} [] m{varName} = new {bindData.BindCom.FirstOrDefault().GetType().Name}[{bindData.BindCom.Count}];\n"); varTextBuilder.Append($"\t\tpublic {bindData.BindCom.FirstOrDefault().GetType().Name} [] {varName} => m{varName};\n\n"); } else if (bindData.BindType == EBindType.Widget) { varTextBuilder.Append($"\t\tprivate {bindData.BindCom.FirstOrDefault().GetType().Name} m{varName};\n"); varTextBuilder.Append($"\t\tpublic {bindData.BindCom.FirstOrDefault().GetType().Name} {varName} => m{varName};\n\n"); } } return varTextBuilder.ToString(); } private static string GenerateScript(string className) { StringBuilder scriptBuilder = new StringBuilder(); scriptBuilder.Append(GetRefrenceNameSpace()); scriptBuilder.Append("using Sirenix.OdinInspector;\n"); scriptBuilder.Append("using AlicizaX.UI.Runtime;\n"); scriptBuilder.Append($"namespace {GenerateNameSpace}\n"); scriptBuilder.Append("{\n"); scriptBuilder.Append($"\t#Attribute#\n"); scriptBuilder.Append($"\tpublic class {className} : UIHolderObjectBase\n"); scriptBuilder.Append("\t{\n"); scriptBuilder.Append($"\t\tpublic const string ResTag = #Tag#;\n"); scriptBuilder.Append("\t\t#region Generated by Script Tool\n\n"); scriptBuilder.Append(GetVarText(UIBindDatas)); scriptBuilder.Append("\n\t\t#endregion\n"); scriptBuilder.Append("\t}\n"); scriptBuilder.Append("}\n"); return scriptBuilder.ToString(); } public static void GenerateAndAttachScript(GameObject targetObject, UIScriptGenerateData scriptGenerateData) { EditorPrefs.SetInt("InstanceId", targetObject.GetInstanceID()); UIBindDatas.Clear(); GenerateNameSpace = scriptGenerateData.NameSpace; ArrayComs.Clear(); string className = $"{UIGenerateConfiguration.UIGenerateCommonData.GeneratePrefix}_{targetObject.name}"; string scriptSavePath = Path.Combine(scriptGenerateData.GenerateHolderCodePath, className + ".cs"); GetBindData(targetObject.transform); string scriptContent = GenerateScript(className); string TagName = $"\"{targetObject.name}\""; if (scriptGenerateData.LoadType == EUIResLoadType.Resources) { string matchWords = string.Empty; UIGenerateConfiguration.UIGenerateCommonData.CombineWords.Any(t => { if (targetObject.name.IndexOf(t.Key) >= 0) { matchWords = t.Value; } return targetObject.name.IndexOf(t.Key) >= 0; }); string finalPath = Path.Combine(scriptGenerateData.UIPrefabRootPath.Replace("Assets/", "").Replace("Resources/", ""), matchWords); string didc = Path.Combine(scriptGenerateData.UIPrefabRootPath, matchWords); if (!Directory.Exists(didc)) { Directory.CreateDirectory(didc); } finalPath = Utility.Path.GetRegularPath(finalPath); TagName = $"\"{finalPath}/{targetObject.name}\""; } scriptContent = scriptContent.Replace("#Tag#", TagName); //#Attribute# string uiAttribute = $"[UIRes({className}.ResTag, EUIResLoadType.{scriptGenerateData.LoadType.ToString()})]"; scriptContent = scriptContent.Replace("#Attribute#", uiAttribute); if (File.Exists(scriptSavePath)) { string oldText = File.ReadAllText(scriptSavePath); if (oldText.Equals(scriptContent)) { EditorPrefs.SetString("Generate", className); CheckHasAttach(); return; } } File.WriteAllText(scriptSavePath, scriptContent); EditorPrefs.SetString("Generate", className); AssetDatabase.Refresh(); } [DidReloadScripts] private static void CheckHasAttach() { bool has = EditorPrefs.HasKey("Generate"); if (has) { UIBindDatas.Clear(); ArrayComs.Clear(); var className = EditorPrefs.GetString("Generate"); // var targetObject = UIGenerateWindow.GetTargetObject(); int instanceId = EditorPrefs.GetInt("InstanceId", -1); if (instanceId == -1) { return; } var targetObject = (GameObject)EditorUtility.InstanceIDToObject(instanceId); if (!targetObject) { Debug.Log("UI脚本生成附加物体丢失!"); } EditorPrefs.DeleteKey("Generate"); GetBindData(targetObject.transform); AttachScriptToGameObject(targetObject, className); Debug.Log($"Generate {className} Successfully attached to game object"); } } private static void AttachScriptToGameObject(GameObject targetObject, string scriptClassName) { Type scriptType = null; foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { if (assembly.FullName.Contains("Editor")) continue; var types = assembly.GetTypes(); foreach (var type in types) { if (type.IsClass && !type.IsAbstract && type.Name.Contains(scriptClassName)) { scriptType = type; } } } if (scriptType != null) { Component component = targetObject.GetOrAddComponent(scriptType); FieldInfo[] fields = scriptType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); foreach (FieldInfo field in fields) { List componentInObjects = UIBindDatas.Find(data => "m" + data.Name == field.Name)?.BindCom; if (componentInObjects != null) { if (field.FieldType.IsArray) { // 获取数组元素类型 Type elementType = field.FieldType.GetElementType(); // 创建对应类型的数组 Array array = Array.CreateInstance(elementType, componentInObjects.Count); for (int i = 0; i < componentInObjects.Count; i++) { Component comp = componentInObjects[i]; // 检查元素类型是否匹配 if (elementType.IsInstanceOfType(comp)) { array.SetValue(comp, i); } else { Debug.LogError($"元素 {i} 类型不匹配,期望 {elementType.Name},实际为 {comp.GetType().Name}"); // 处理错误,如跳过或终止赋值 } } field.SetValue(component, array); } else { // 非数组字段取第一个元素 if (componentInObjects.Count > 0) { // 同样检查类型兼容性 if (field.FieldType.IsInstanceOfType(componentInObjects[0])) { field.SetValue(component, componentInObjects[0]); } else { Debug.LogError($"字段 {field.Name} 类型不匹配,无法赋值"); } } } } else { Debug.LogError($"字段 {field.Name} 未找到匹配的组件绑定"); } } } else { Debug.LogError($"Could not find the class: {scriptClassName}"); } } } }