using System; using System.Collections; 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 UnityEngine; using UnityEditor; using UnityEditor.Callbacks; using UnityEditor.SceneManagement; namespace AlicizaX.UI.Editor { public enum EBindType { None, Widget, ListCom, } [Serializable] internal class UIBindData { public string Name; public List BindCom; public EBindType BindType; public UIBindData(string name, List bindCom, EBindType bindType = EBindType.None) { Name = name ?? throw new ArgumentNullException(nameof(name)); BindCom = bindCom ?? new List(); BindType = bindType; } public UIBindData(string name, Component bindCom, EBindType bindType = EBindType.None) : this(name, new List { bindCom }, bindType) { } } internal static class UIScriptGeneratorHelper { private static UIGenerateConfiguration _uiGenerateConfiguration; private static IUIGeneratorRuleHelper _uiGeneratorRuleHelper; private static List _uiBindDatas = new List(); private static HashSet _arrayComponents = new HashSet(StringComparer.Ordinal); public static IUIGeneratorRuleHelper UIGeneratorRuleHelper { get { if (_uiGeneratorRuleHelper == null || (_uiGeneratorRuleHelper != null && !UIConfiguration.UIScriptGeneratorRuleHelper.Equals(_uiGeneratorRuleHelper.GetType().FullName, StringComparison.Ordinal))) { var ruleHelperTypeName = UIConfiguration.UIScriptGeneratorRuleHelper; if (string.IsNullOrWhiteSpace(ruleHelperTypeName)) { Debug.LogError("UIScriptGeneratorHelper: UIScriptGeneratorRuleHelper not configured."); return null; } Type ruleHelperType = Type.GetType(ruleHelperTypeName); if (ruleHelperType == null) { Debug.LogError($"UIScriptGeneratorHelper: Could not load UI ScriptGeneratorHelper {ruleHelperTypeName}"); return null; } _uiGeneratorRuleHelper = Activator.CreateInstance(ruleHelperType) as IUIGeneratorRuleHelper; if (_uiGeneratorRuleHelper == null) { Debug.LogError($"UIScriptGeneratorHelper: Failed to instantiate {ruleHelperTypeName} as IUIGeneratorRuleHelper."); } } return _uiGeneratorRuleHelper; } } private static UIGenerateConfiguration UIConfiguration { get { if (_uiGenerateConfiguration == null) { _uiGenerateConfiguration = UIGenerateConfiguration.Instance; } return _uiGenerateConfiguration; } } private static string GetUIElementComponentType(string uiName) { if (string.IsNullOrEmpty(uiName)) return string.Empty; foreach (var pair in UIConfiguration.UIElementRegexConfigs ?? Enumerable.Empty()) { if (string.IsNullOrEmpty(pair?.uiElementRegex)) continue; if (uiName.StartsWith(pair.uiElementRegex, StringComparison.Ordinal)) { return pair.componentType ?? string.Empty; } } return string.Empty; } private static string[] SplitComponentName(string name) { if (string.IsNullOrEmpty(name)) return null; var common = UIConfiguration.UIGenerateCommonData; if (string.IsNullOrEmpty(common?.ComCheckEndName) || !name.Contains(common.ComCheckEndName)) return null; int endIndex = name.IndexOf(common.ComCheckEndName, StringComparison.Ordinal); if (endIndex <= 0) return null; string comStr = name.Substring(0, endIndex); string split = common.ComCheckSplitName ?? "#"; // 使用 string[] 重载并移除空项,防止错误 overload return comStr.Split(new[] { split }, StringSplitOptions.RemoveEmptyEntries); } private static string GetVariableName(string key, string componentName, EBindType bindType) { var helper = UIGeneratorRuleHelper; if (helper == null) throw new InvalidOperationException("UIGeneratorRuleHelper is not configured."); return helper.GetPrivateComponentByNameRule(key, componentName, bindType); } private static void GetBindData(Transform root) { if (root == null) return; for (int i = 0; i < root.childCount; ++i) { Transform child = root.GetChild(i); if (child == null) continue; // 排除关键字 if (UIConfiguration.UIGenerateCommonData.ExcludeKeywords != null && UIConfiguration.UIGenerateCommonData.ExcludeKeywords.Any(k => !string.IsNullOrEmpty(k) && child.name.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0)) { continue; } bool hasWidget = child.GetComponent() != null; bool isArrayComponent = !string.IsNullOrEmpty(UIConfiguration.UIGenerateCommonData.ArrayComSplitName) && child.name.StartsWith(UIConfiguration.UIGenerateCommonData.ArrayComSplitName, StringComparison.Ordinal); if (hasWidget) { CollectWidget(child); } else if (isArrayComponent) { // 提取 array 标识文本(例如 "*Item*0" -> "Item") string splitCode = UIConfiguration.UIGenerateCommonData.ArrayComSplitName; int firstIndex = child.name.IndexOf(splitCode, StringComparison.Ordinal); int lastIndex = child.name.LastIndexOf(splitCode, StringComparison.Ordinal); if (firstIndex < 0 || lastIndex <= firstIndex) continue; // 中间文本 string text = child.name.Substring(firstIndex + splitCode.Length, lastIndex - (firstIndex + splitCode.Length)); if (string.IsNullOrEmpty(text)) continue; if (_arrayComponents.Contains(text)) continue; _arrayComponents.Add(text); // 在同一个父节点下收集包含该 text 的同级节点 List arrayComponents = new List(); for (int j = 0; j < root.childCount; j++) { Transform sibling = root.GetChild(j); if (sibling != null && sibling.name.Contains(text, StringComparison.Ordinal)) { arrayComponents.Add(sibling); } } CollectArrayComponent(arrayComponents, text); } else // 普通组件/进一步递归 { CollectComponent(child); GetBindData(child); } } } private static void CollectComponent(Transform node) { if (node == null) return; string[] componentArray = SplitComponentName(node.name); if (componentArray == null || componentArray.Length == 0) return; foreach (var com in componentArray) { if (string.IsNullOrEmpty(com)) continue; string typeName = GetUIElementComponentType(com); if (string.IsNullOrEmpty(typeName)) continue; Component component = node.GetComponent(typeName); if (component != null) { string keyName = GetVariableName(com, node.name, EBindType.None); 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 == null) return; var common = UIConfiguration.UIGenerateCommonData; if (node.name.IndexOf(common.ComCheckEndName, StringComparison.Ordinal) != -1 && node.name.IndexOf(common.ComCheckSplitName, StringComparison.Ordinal) != -1) { Debug.LogWarning($"{node.name} child component cannot contain rule definition symbols!"); return; } UIHolderObjectBase component = node.GetComponent(); if (component == null) { Debug.LogError($"{node.name} expected to be a widget but does not have UIHolderObjectBase."); return; } string keyName = GetVariableName(string.Empty, node.name, EBindType.Widget); 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) { if (arrayNode == null || arrayNode.Count == 0) return; // 从 nodeName(例如 "*Item*0")取出组件描述部分(即名字中 @End 之前那部分) string[] componentArray = SplitComponentName(nodeName); // 对 arrayNode 做基于后缀的安全排序:提取 last segment 作为索引(int.TryParse) string splitCode = UIConfiguration.UIGenerateCommonData.ArrayComSplitName; var orderedNodes = arrayNode .Select(n => new { Node = n, RawIndex = ExtractArrayIndex(n.name, splitCode) }) .OrderBy(x => x.RawIndex.HasValue ? x.RawIndex.Value : int.MaxValue) .Select(x => x.Node) .ToList(); if (componentArray == null || componentArray.Length == 0) { Debug.LogWarning($"CollectArrayComponent: {nodeName} has no component definitions."); return; } // 准备临时 bind 列表,每个 componentArray 项对应一个 UIBindData List tempBindDatas = new List(componentArray.Length); for (int i = 0; i < componentArray.Length; i++) { string keyNamePreview = GetVariableName(componentArray[i], nodeName, EBindType.ListCom); tempBindDatas.Add(new UIBindData(keyNamePreview, new List(), EBindType.ListCom)); } // 遍历元素并填充 for (int index = 0; index < componentArray.Length; index++) { string com = componentArray[index]; if (string.IsNullOrEmpty(com)) continue; string typeName = GetUIElementComponentType(com); if (string.IsNullOrEmpty(typeName)) continue; foreach (var node in orderedNodes) { Component component = node.GetComponent(typeName); if (component != null) { tempBindDatas[index].BindCom.Add(component); } else { Debug.LogError($"{node.name} does not have component of type {typeName}"); } } } // 将结果合并到全局绑定数据 _uiBindDatas.AddRange(tempBindDatas); } private static int? ExtractArrayIndex(string nodeName, string splitCode) { if (string.IsNullOrEmpty(nodeName) || string.IsNullOrEmpty(splitCode)) return null; int last = nodeName.LastIndexOf(splitCode, StringComparison.Ordinal); if (last < 0) return null; string suffix = nodeName.Substring(last + splitCode.Length); if (int.TryParse(suffix, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out int idx)) return idx; return null; } private static string GetReferenceNamespace() { HashSet namespaceSet = new HashSet(StringComparer.Ordinal); namespaceSet.Add("UnityEngine"); if (_uiBindDatas.Any(d => d.BindType == EBindType.ListCom)) namespaceSet.Add("System.Collections.Generic"); foreach (var bindData in _uiBindDatas) { var ns = bindData.BindCom?.FirstOrDefault()?.GetType().Namespace; if (!string.IsNullOrEmpty(ns) && !namespaceSet.Contains(ns)) { namespaceSet.Add(ns); } } return new StringBuilder().Append(string.Join(Environment.NewLine, namespaceSet.Select(n => $"using {n};"))).ToString(); } private static string GetVariableText(List uiBindDatas) { if (uiBindDatas == null || uiBindDatas.Count == 0) return string.Empty; StringBuilder variableTextBuilder = new StringBuilder(); var helper = UIGeneratorRuleHelper; if (helper == null) throw new InvalidOperationException("UIGeneratorRuleHelper is not configured."); var result = string.Join("\n\n", uiBindDatas .Where(b => b != null && !string.IsNullOrEmpty(b.Name)) .Select(b => { string variableName = b.Name; string publicName = helper.GetPublicComponentByNameRule(variableName); var firstType = b.BindCom?.FirstOrDefault()?.GetType(); string typeName = firstType?.Name ?? "Component"; var sb = new StringBuilder(); sb.AppendLine("\t\t[SerializeField]"); if (b.BindType == EBindType.None || b.BindType == EBindType.Widget) { sb.AppendLine($"\t\tprivate {typeName} {variableName};"); sb.Append($"\t\tpublic {typeName} {publicName} => {variableName};"); } else if (b.BindType == EBindType.ListCom) { int count = Math.Max(0, b.BindCom?.Count ?? 0); sb.AppendLine($"\t\tprivate {typeName}[] {variableName} = new {typeName}[{count}];"); sb.Append($"\t\tpublic {typeName}[] {publicName} => {variableName};"); } return sb.ToString(); })); return variableTextBuilder.Append(result).ToString(); } public static void GenerateAndAttachScript(GameObject targetObject, UIScriptGenerateData scriptGenerateData) { if (targetObject == null) throw new ArgumentNullException(nameof(targetObject)); if (scriptGenerateData == null) throw new ArgumentNullException(nameof(scriptGenerateData)); if (!PrefabChecker.IsPrefabAsset(targetObject)) { Debug.LogWarning("请将UI界面保存为对应的目录Prefab 在进行代码生成"); return; } var ruleHelper = UIGeneratorRuleHelper; if (ruleHelper == null) { Debug.LogError("UIGeneratorRuleHelper not available, abort."); return; } if (!ruleHelper.CheckCanGenerate(targetObject, scriptGenerateData)) { return; } EditorPrefs.SetInt("InstanceId", targetObject.GetInstanceID()); _uiBindDatas.Clear(); _arrayComponents.Clear(); string className = ruleHelper.GetClassGenerateName(targetObject, scriptGenerateData); if (string.IsNullOrEmpty(className)) { Debug.LogError("Generated className is empty."); return; } GetBindData(targetObject.transform); string uiTemplateText = File.ReadAllText(UIGlobalPath.TemplatePath); uiTemplateText = uiTemplateText.Replace("#ReferenceNameSpace#", GetReferenceNamespace()); uiTemplateText = uiTemplateText.Replace("#ClassNameSpace#", scriptGenerateData.NameSpace); uiTemplateText = uiTemplateText.Replace("#ClassName#", className); uiTemplateText = uiTemplateText.Replace("#TagName#", ruleHelper.GetUIResourceSavePath(targetObject, scriptGenerateData)); uiTemplateText = uiTemplateText.Replace("#LoadType#", scriptGenerateData.LoadType.ToString()); uiTemplateText = uiTemplateText.Replace("#Variable#", GetVariableText(_uiBindDatas)); ruleHelper.WriteUIScriptContent(className, uiTemplateText, scriptGenerateData); } [DidReloadScripts] public static void CheckHasAttach() { if (!EditorPrefs.HasKey("Generate")) return; EditorPrefs.DeleteKey("Generate"); _uiBindDatas.Clear(); _arrayComponents.Clear(); string className = EditorPrefs.GetString("Generate"); int instanceId = EditorPrefs.GetInt("InstanceId", -1); if (instanceId == -1) { Debug.LogWarning("CheckHasAttach: InstanceId missing."); return; } GameObject targetObject = EditorUtility.InstanceIDToObject(instanceId) as GameObject; if (targetObject == null) { Debug.LogWarning("UI script generation attachment object missing!"); return; } GetBindData(targetObject.transform); BindScriptPropertyField(targetObject, className); Debug.Log($"Generate {className} Successfully attached to game object"); } private static void BindScriptPropertyField(GameObject targetObject, string scriptClassName) { if (targetObject == null) throw new ArgumentNullException(nameof(targetObject)); if (string.IsNullOrEmpty(scriptClassName)) throw new ArgumentNullException(nameof(scriptClassName)); Type scriptType = null; foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { // 跳过典型的编辑器专用程序集(但只在程序集名字结束或包含 ".Editor" 时跳过) var asmName = assembly.GetName().Name ?? string.Empty; if (asmName.EndsWith(".Editor", StringComparison.OrdinalIgnoreCase) || asmName.Equals("UnityEditor", StringComparison.OrdinalIgnoreCase)) { continue; } Type[] types= assembly.GetTypes(); foreach (var type in types) { if (!type.IsClass || type.IsAbstract) continue; if (type.Name.Equals(scriptClassName, StringComparison.Ordinal) || type.Name.Contains(scriptClassName, StringComparison.Ordinal)) { scriptType = type; break; } } if (scriptType != null) break; } if (scriptType == null) { Debug.LogError($"Could not find the class: {scriptClassName}"); return; } Component component = targetObject.GetOrAddComponent(scriptType); FieldInfo[] fields = scriptType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); foreach (FieldInfo field in fields) { if (string.IsNullOrEmpty(field.Name)) continue; var componentInObjects = _uiBindDatas.Find(data => data.Name == field.Name)?.BindCom; if (componentInObjects == null) { Debug.LogError($"Field {field.Name} did not find matching component binding"); continue; } if (field.FieldType.IsArray) { Type elementType = field.FieldType.GetElementType(); if (elementType == null) { Debug.LogError($"Field {field.Name} has unknown element type."); continue; } Array array = Array.CreateInstance(elementType, componentInObjects.Count); for (int i = 0; i < componentInObjects.Count; i++) { Component comp = componentInObjects[i]; if (comp == null) continue; if (elementType.IsInstanceOfType(comp)) { array.SetValue(comp, i); } else { Debug.LogError($"Element {i} type mismatch for field {field.Name}, expected {elementType.Name}, actual {comp.GetType().Name}"); } } field.SetValue(component, array); } else { if (componentInObjects.Count > 0) { var first = componentInObjects[0]; if (first == null) continue; if (field.FieldType.IsInstanceOfType(first)) { field.SetValue(component, first); } else { Debug.LogError($"Field {field.Name} type mismatch, cannot assign value. Field expects {field.FieldType.Name}, actual {first.GetType().Name}"); } } } } } public static class PrefabChecker { public static bool IsEditingPrefabAsset(GameObject go) { var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); if (prefabStage == null) return false; return prefabStage.IsPartOfPrefabContents(go); } public static bool IsPrefabAsset(GameObject go) { if (go == null) return false; var assetType = PrefabUtility.GetPrefabAssetType(go); if (assetType == PrefabAssetType.Regular || assetType == PrefabAssetType.Variant || assetType == PrefabAssetType.Model) return true; return IsEditingPrefabAsset(go); } } } }