optimiza code

This commit is contained in:
陈思海 2025-11-13 11:16:31 +08:00
parent 4de5156174
commit 110c3451ea
5 changed files with 494 additions and 433 deletions

View File

@ -29,7 +29,7 @@ namespace AlicizaX.UI.Editor
{
if (CheckCanGenerate(selectedObject, config))
{
UIScriptGeneratorHelper.GenerateAndAttachScript(selectedObject, config);
UIScriptGeneratorHelper.GenerateUIBindScript(selectedObject, config);
return;
}
}

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using AlicizaX.UI.Runtime;
using UnityEditor;
@ -21,54 +23,61 @@ namespace AlicizaX.UI.Editor
void WriteUIScriptContent(string className, string scriptContent, UIScriptGenerateData scriptGenerateData);
bool CheckCanGenerate(GameObject targetObject, UIScriptGenerateData scriptGenerateData);
string GetReferenceNamespace(List<UIBindData> uiBindDatas);
string GetVariableContent(List<UIBindData> uiBindDatas);
}
public class DefaultIuiGeneratorRuleHelper : IUIGeneratorRuleHelper
public class DefaultUIGeneratorRuleHelper : IUIGeneratorRuleHelper
{
public string GetPrivateComponentByNameRule(string regexName, string componentName, EBindType bindType)
{
string endPrefix = bindType == EBindType.ListCom ? "List" : string.Empty;
var endPrefix = bindType == EBindType.ListCom ? "List" : string.Empty;
var common = UIGenerateConfiguration.Instance.UIGenerateCommonData;
int endNameIndex = componentName.IndexOf(common.ComCheckEndName, StringComparison.Ordinal);
string componentSuffix = endNameIndex >= 0 ? componentName.Substring(endNameIndex + 1) : componentName;
var endNameIndex = componentName.IndexOf(common.ComCheckEndName, StringComparison.Ordinal);
var componentSuffix = endNameIndex >= 0 ? componentName.Substring(endNameIndex + 1) : componentName;
return $"m{regexName}{componentSuffix}{endPrefix}";
}
public string GetPublicComponentByNameRule(string variableName)
{
if (string.IsNullOrEmpty(variableName)) return variableName;
if (variableName.Length > 1)
return variableName.Substring(1);
return variableName;
return variableName.Length > 1 ? variableName.Substring(1) : variableName;
}
public string GetClassGenerateName(GameObject targetObject, UIScriptGenerateData scriptGenerateData)
{
var config = UIGenerateConfiguration.Instance.UIGenerateCommonData;
string prefix = config.GeneratePrefix ?? "ui";
var prefix = config.GeneratePrefix ?? "ui";
return $"{prefix}_{targetObject.name}";
}
public string GetUIResourceSavePath(GameObject targetObject, UIScriptGenerateData scriptGenerateData)
{
if (targetObject == null)
return $"\"{nameof(targetObject)}\"";
if (targetObject == null) return $"\"{nameof(targetObject)}\"";
var defaultPath = targetObject.name;
var assetPath = UIGenerateQuick.GetPrefabAssetPath(targetObject);
string defaultPath = targetObject.name;
string assetPath = UIGenerateQuick.GetPrefabAssetPath(targetObject);
if (string.IsNullOrEmpty(assetPath) || !assetPath.StartsWith("Assets/", StringComparison.Ordinal))
return defaultPath;
assetPath = assetPath.Replace('\\', '/');
return scriptGenerateData.LoadType switch
{
EUIResLoadType.Resources => GetResourcesPath(assetPath, scriptGenerateData, defaultPath),
EUIResLoadType.AssetBundle => GetAssetBundlePath(assetPath, scriptGenerateData, defaultPath),
_ => defaultPath
};
}
switch (scriptGenerateData.LoadType)
private static string GetResourcesPath(string assetPath, UIScriptGenerateData scriptGenerateData, string defaultPath)
{
case EUIResLoadType.Resources:
{
string resourcesRoot = scriptGenerateData.UIPrefabRootPath; // 例如 "Assets/Resources/UI"
string relPath = GetResourcesRelativePath(assetPath, resourcesRoot);
var resourcesRoot = scriptGenerateData.UIPrefabRootPath;
var relPath = GetResourcesRelativePath(assetPath, resourcesRoot);
if (relPath == null)
{
@ -79,20 +88,20 @@ namespace AlicizaX.UI.Editor
return relPath;
}
case EUIResLoadType.AssetBundle:
private static string GetAssetBundlePath(string assetPath, UIScriptGenerateData scriptGenerateData, string defaultPath)
{
string bundleRoot = scriptGenerateData.UIPrefabRootPath; // 例如 "Assets/Bundles/UI"
try
{
var defaultPackage = YooAsset.Editor.AssetBundleCollectorSettingData.Setting.GetPackage("DefaultPackage");
if (defaultPackage != null && defaultPackage.EnableAddressable)
if (defaultPackage?.EnableAddressable == true)
return defaultPath;
}
catch
{
// 忽略异常,继续处理
}
var bundleRoot = scriptGenerateData.UIPrefabRootPath;
if (!assetPath.StartsWith(bundleRoot, StringComparison.OrdinalIgnoreCase))
{
Debug.LogWarning($"[UI生成] 资源 {assetPath} 不在配置的 AssetBundle 根目录下: {bundleRoot}");
@ -102,21 +111,17 @@ namespace AlicizaX.UI.Editor
return Path.ChangeExtension(assetPath, null);
}
default:
return defaultPath;
}
}
private string GetResourcesRelativePath(string assetPath, string resourcesRoot)
private static string GetResourcesRelativePath(string assetPath, string resourcesRoot)
{
if (string.IsNullOrEmpty(assetPath) || string.IsNullOrEmpty(resourcesRoot)) return null;
assetPath = assetPath.Replace('\\', '/');
resourcesRoot = resourcesRoot.Replace('\\', '/');
if (!assetPath.StartsWith(resourcesRoot, StringComparison.OrdinalIgnoreCase))
return null;
string relPath = assetPath.Substring(resourcesRoot.Length).TrimStart('/');
var relPath = assetPath.Substring(resourcesRoot.Length).TrimStart('/');
return Path.ChangeExtension(relPath, null);
}
@ -126,47 +131,104 @@ namespace AlicizaX.UI.Editor
if (scriptContent == null) throw new ArgumentNullException(nameof(scriptContent));
if (scriptGenerateData == null) throw new ArgumentNullException(nameof(scriptGenerateData));
string scriptFolderPath = scriptGenerateData.GenerateHolderCodePath;
string scriptFilePath = Path.Combine(scriptFolderPath, className + ".cs");
var scriptFolderPath = scriptGenerateData.GenerateHolderCodePath;
var scriptFilePath = Path.Combine(scriptFolderPath, $"{className}.cs");
if (!Directory.Exists(scriptFolderPath))
{
Directory.CreateDirectory(scriptFolderPath);
}
if (File.Exists(scriptFilePath))
if (File.Exists(scriptFilePath) && IsContentUnchanged(scriptFilePath, scriptContent))
{
string oldText = File.ReadAllText(scriptFilePath, Encoding.UTF8);
if (oldText.Equals(scriptContent, StringComparison.Ordinal))
{
// 文件未变更:标记并等待脚本 reload 去做附加
EditorPrefs.SetString("Generate", className);
UIScriptGeneratorHelper.CheckHasAttach();
UIScriptGeneratorHelper.BindUIScript();
return;
}
}
File.WriteAllText(scriptFilePath, scriptContent, Encoding.UTF8);
EditorPrefs.SetString("Generate", className);
AssetDatabase.Refresh();
}
private static bool IsContentUnchanged(string filePath, string newContent)
{
var oldText = File.ReadAllText(filePath, Encoding.UTF8);
return oldText.Equals(newContent, StringComparison.Ordinal);
}
public bool CheckCanGenerate(GameObject targetObject, UIScriptGenerateData scriptGenerateData)
{
if (targetObject == null || scriptGenerateData == null) return false;
string assetPath = UIGenerateQuick.GetPrefabAssetPath(targetObject);
var assetPath = UIGenerateQuick.GetPrefabAssetPath(targetObject);
if (string.IsNullOrEmpty(assetPath) || !assetPath.StartsWith("Assets/", StringComparison.Ordinal))
return false; // 不在 Assets 下
return false;
assetPath = assetPath.Replace('\\', '/');
bool result = assetPath.StartsWith(scriptGenerateData.UIPrefabRootPath, StringComparison.OrdinalIgnoreCase);
if (!result)
var isValidPath = assetPath.StartsWith(scriptGenerateData.UIPrefabRootPath, StringComparison.OrdinalIgnoreCase);
if (!isValidPath)
{
Debug.LogWarning($"UI存储位置与配置生成规则不符合 请检查对应配置的UIPrefabRootPath\n[AssetPath]{assetPath}\n[ConfigPath]{scriptGenerateData.UIPrefabRootPath}");
}
return result;
return isValidPath;
}
public string GetReferenceNamespace(List<UIBindData> uiBindDatas)
{
var namespaceSet = new HashSet<string>(StringComparer.Ordinal) { "UnityEngine" };
if (uiBindDatas?.Any(d => d.BindType == EBindType.ListCom) == true)
{
namespaceSet.Add("System.Collections.Generic");
}
uiBindDatas?
.Where(bindData => bindData?.BindCom?.FirstOrDefault() != null)
.Select(bindData => bindData.BindCom[0].GetType().Namespace)
.Where(ns => !string.IsNullOrEmpty(ns))
.ToList()
.ForEach(ns => namespaceSet.Add(ns));
return string.Join(Environment.NewLine, namespaceSet.Select(ns => $"using {ns};"));
}
public string GetVariableContent(List<UIBindData> uiBindDatas)
{
if (uiBindDatas == null || uiBindDatas.Count == 0) return string.Empty;
var variableBuilder = new StringBuilder();
var variables = uiBindDatas
.Where(b => b != null && !string.IsNullOrEmpty(b.Name))
.Select(b => GenerateVariableDeclaration(b))
.Where(declaration => !string.IsNullOrEmpty(declaration));
return string.Join("\n\n", variables);
}
private string GenerateVariableDeclaration(UIBindData bindData)
{
var variableName = bindData.Name;
var publicName = GetPublicComponentByNameRule(variableName);
var firstType = bindData.BindCom?.FirstOrDefault()?.GetType();
var typeName = firstType?.Name ?? "Component";
var declaration = new StringBuilder();
declaration.AppendLine("\t\t[SerializeField]");
switch (bindData.BindType)
{
case EBindType.None:
case EBindType.Widget:
declaration.AppendLine($"\t\tprivate {typeName} {variableName};");
declaration.Append($"\t\tpublic {typeName} {publicName} => {variableName};");
break;
case EBindType.ListCom:
var count = Math.Max(0, bindData.BindCom?.Count ?? 0);
declaration.AppendLine($"\t\tprivate {typeName}[] {variableName} = new {typeName}[{count}];");
declaration.Append($"\t\tpublic {typeName}[] {publicName} => {variableName};");
break;
}
return declaration.ToString();
}
}
}

View File

@ -1,16 +1,13 @@
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;
using UnityEngine;
namespace AlicizaX.UI.Editor
{
@ -18,16 +15,15 @@ namespace AlicizaX.UI.Editor
{
None,
Widget,
ListCom,
ListCom
}
[Serializable]
internal class UIBindData
public class UIBindData
{
public string Name;
public List<Component> BindCom;
public EBindType BindType;
public string Name { get; }
public List<Component> BindCom { get; }
public EBindType BindType { get; }
public UIBindData(string name, List<Component> bindCom, EBindType bindType = EBindType.None)
{
@ -46,16 +42,16 @@ namespace AlicizaX.UI.Editor
{
private static UIGenerateConfiguration _uiGenerateConfiguration;
private static IUIGeneratorRuleHelper _uiGeneratorRuleHelper;
private static readonly List<UIBindData> _uiBindDatas = new List<UIBindData>();
private static readonly HashSet<string> _arrayComponents = new HashSet<string>(StringComparer.Ordinal);
private static List<UIBindData> _uiBindDatas = new List<UIBindData>();
private static HashSet<string> _arrayComponents = new HashSet<string>(StringComparer.Ordinal);
private static IUIGeneratorRuleHelper UIGeneratorRuleHelper =>
_uiGeneratorRuleHelper ?? InitializeRuleHelper();
public static IUIGeneratorRuleHelper UIGeneratorRuleHelper
{
get
{
if (_uiGeneratorRuleHelper == null ||
(_uiGeneratorRuleHelper != null && !UIConfiguration.UIScriptGeneratorRuleHelper.Equals(_uiGeneratorRuleHelper.GetType().FullName, StringComparison.Ordinal)))
private static UIGenerateConfiguration UIConfiguration =>
_uiGenerateConfiguration ??= UIGenerateConfiguration.Instance;
private static IUIGeneratorRuleHelper InitializeRuleHelper()
{
var ruleHelperTypeName = UIConfiguration.UIScriptGeneratorRuleHelper;
if (string.IsNullOrWhiteSpace(ruleHelperTypeName))
@ -64,7 +60,7 @@ namespace AlicizaX.UI.Editor
return null;
}
Type ruleHelperType = Type.GetType(ruleHelperTypeName);
var ruleHelperType = Type.GetType(ruleHelperTypeName);
if (ruleHelperType == null)
{
Debug.LogError($"UIScriptGeneratorHelper: Could not load UI ScriptGeneratorHelper {ruleHelperTypeName}");
@ -76,38 +72,18 @@ namespace AlicizaX.UI.Editor
{
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<UIEelementRegexData>())
{
if (string.IsNullOrEmpty(pair?.uiElementRegex)) continue;
if (uiName.StartsWith(pair.uiElementRegex, StringComparison.Ordinal))
{
return pair.componentType ?? string.Empty;
}
}
return string.Empty;
return UIConfiguration.UIElementRegexConfigs
?.Where(pair => !string.IsNullOrEmpty(pair?.uiElementRegex))
.FirstOrDefault(pair => uiName.StartsWith(pair.uiElementRegex, StringComparison.Ordinal))
?.componentType ?? string.Empty;
}
private static string[] SplitComponentName(string name)
@ -118,43 +94,24 @@ namespace AlicizaX.UI.Editor
if (string.IsNullOrEmpty(common?.ComCheckEndName) || !name.Contains(common.ComCheckEndName))
return null;
int endIndex = name.IndexOf(common.ComCheckEndName, StringComparison.Ordinal);
var endIndex = name.IndexOf(common.ComCheckEndName, StringComparison.Ordinal);
if (endIndex <= 0) return null;
string comStr = name.Substring(0, endIndex);
string split = common.ComCheckSplitName ?? "#";
// 使用 string[] 重载并移除空项,防止错误 overload
var comStr = name.Substring(0, endIndex);
var split = common.ComCheckSplitName ?? "#";
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)
private static void CollectBindData(Transform root)
{
if (root == null) return;
for (int i = 0; i < root.childCount; ++i)
foreach (Transform child in root.Cast<Transform>().Where(child => child != null))
{
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<UIHolderObjectBase>() != null;
bool isArrayComponent = !string.IsNullOrEmpty(UIConfiguration.UIGenerateCommonData.ArrayComSplitName) &&
child.name.StartsWith(UIConfiguration.UIGenerateCommonData.ArrayComSplitName, StringComparison.Ordinal);
if (ShouldSkipChild(child)) continue;
var hasWidget = child.GetComponent<UIHolderObjectBase>() != null;
var isArrayComponent = IsArrayComponent(child.name);
if (hasWidget)
{
@ -162,58 +119,72 @@ namespace AlicizaX.UI.Editor
}
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<Transform> arrayComponents = new List<Transform>();
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);
ProcessArrayComponent(child, root);
}
}
CollectArrayComponent(arrayComponents, text);
}
else // 普通组件/进一步递归
else
{
CollectComponent(child);
GetBindData(child);
CollectBindData(child);
}
}
}
private static bool ShouldSkipChild(Transform child)
{
var keywords = UIConfiguration.UIGenerateCommonData.ExcludeKeywords;
return keywords?.Any(k =>
!string.IsNullOrEmpty(k) &&
child.name.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0) == true;
}
private static bool IsArrayComponent(string componentName)
{
var splitName = UIConfiguration.UIGenerateCommonData.ArrayComSplitName;
return !string.IsNullOrEmpty(splitName) &&
componentName.StartsWith(splitName, StringComparison.Ordinal);
}
private static void ProcessArrayComponent(Transform child, Transform root)
{
var splitCode = UIConfiguration.UIGenerateCommonData.ArrayComSplitName;
var firstIndex = child.name.IndexOf(splitCode, StringComparison.Ordinal);
var lastIndex = child.name.LastIndexOf(splitCode, StringComparison.Ordinal);
if (firstIndex < 0 || lastIndex <= firstIndex) return;
var text = child.name.Substring(firstIndex + splitCode.Length, lastIndex - (firstIndex + splitCode.Length));
if (string.IsNullOrEmpty(text) || _arrayComponents.Contains(text)) return;
_arrayComponents.Add(text);
var arrayComponents = root.Cast<Transform>()
.Where(sibling => sibling.name.Contains(text, StringComparison.Ordinal))
.ToList();
CollectArrayComponent(arrayComponents, text);
}
private static void CollectComponent(Transform node)
{
if (node == null) return;
string[] componentArray = SplitComponentName(node.name);
var componentArray = SplitComponentName(node.name);
if (componentArray == null || componentArray.Length == 0) return;
foreach (var com in componentArray)
foreach (var com in componentArray.Where(com => !string.IsNullOrEmpty(com)))
{
if (string.IsNullOrEmpty(com)) continue;
string typeName = GetUIElementComponentType(com);
var typeName = GetUIElementComponentType(com);
if (string.IsNullOrEmpty(typeName)) continue;
Component component = node.GetComponent(typeName);
if (component != null)
var component = node.GetComponent(typeName);
if (component == null)
{
string keyName = GetVariableName(com, node.name, EBindType.None);
if (_uiBindDatas.Exists(a => a.Name == keyName))
Debug.LogError($"{node.name} does not have component of type {typeName}");
continue;
}
var keyName = UIGeneratorRuleHelper.GetPrivateComponentByNameRule(com, node.name, EBindType.None);
if (_uiBindDatas.Exists(data => data.Name == keyName))
{
Debug.LogError($"Duplicate key found: {keyName}");
continue;
@ -221,11 +192,6 @@ namespace AlicizaX.UI.Editor
_uiBindDatas.Add(new UIBindData(keyName, component));
}
else
{
Debug.LogError($"{node.name} does not have component of type {typeName}");
}
}
}
private static void CollectWidget(Transform node)
@ -233,22 +199,22 @@ namespace AlicizaX.UI.Editor
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)
if (node.name.Contains(common.ComCheckEndName, StringComparison.Ordinal) &&
node.name.Contains(common.ComCheckSplitName, StringComparison.Ordinal))
{
Debug.LogWarning($"{node.name} child component cannot contain rule definition symbols!");
return;
}
UIHolderObjectBase component = node.GetComponent<UIHolderObjectBase>();
var component = node.GetComponent<UIHolderObjectBase>();
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))
var keyName = UIGeneratorRuleHelper.GetPrivateComponentByNameRule(string.Empty, node.name, EBindType.Widget);
if (_uiBindDatas.Exists(data => data.Name == keyName))
{
Debug.LogError($"Duplicate key found: {keyName}");
return;
@ -259,45 +225,54 @@ namespace AlicizaX.UI.Editor
private static void CollectArrayComponent(List<Transform> 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 (arrayNode == null || !arrayNode.Any()) return;
var componentArray = SplitComponentName(nodeName);
if (componentArray == null || componentArray.Length == 0)
{
Debug.LogWarning($"CollectArrayComponent: {nodeName} has no component definitions.");
return;
}
// 准备临时 bind 列表,每个 componentArray 项对应一个 UIBindData
List<UIBindData> tempBindDatas = new List<UIBindData>(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<Component>(), EBindType.ListCom));
var orderedNodes = OrderArrayNodes(arrayNode);
var tempBindDatas = CreateTempBindDatas(componentArray, nodeName);
PopulateArrayComponents(componentArray, orderedNodes, tempBindDatas);
_uiBindDatas.AddRange(tempBindDatas);
}
// 遍历元素并填充
for (int index = 0; index < componentArray.Length; index++)
private static List<Transform> OrderArrayNodes(List<Transform> arrayNode)
{
string com = componentArray[index];
var splitCode = UIConfiguration.UIGenerateCommonData.ArrayComSplitName;
return arrayNode
.Select(node => new { Node = node, Index = ExtractArrayIndex(node.name, splitCode) })
.OrderBy(x => x.Index ?? int.MaxValue)
.Select(x => x.Node)
.ToList();
}
private static List<UIBindData> CreateTempBindDatas(string[] componentArray, string nodeName)
{
return componentArray.Select((com, index) =>
{
var keyName = UIGeneratorRuleHelper.GetPrivateComponentByNameRule(com, nodeName, EBindType.ListCom);
return new UIBindData(keyName, new List<Component>(), EBindType.ListCom);
}).ToList();
}
private static void PopulateArrayComponents(string[] componentArray, List<Transform> orderedNodes, List<UIBindData> tempBindDatas)
{
for (var index = 0; index < componentArray.Length; index++)
{
var com = componentArray[index];
if (string.IsNullOrEmpty(com)) continue;
string typeName = GetUIElementComponentType(com);
var typeName = GetUIElementComponentType(com);
if (string.IsNullOrEmpty(typeName)) continue;
foreach (var node in orderedNodes)
{
Component component = node.GetComponent(typeName);
var component = node.GetComponent(typeName);
if (component != null)
{
tempBindDatas[index].BindCom.Add(component);
@ -308,82 +283,20 @@ namespace AlicizaX.UI.Editor
}
}
}
// 将结果合并到全局绑定数据
_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;
var lastIndex = nodeName.LastIndexOf(splitCode, StringComparison.Ordinal);
if (lastIndex < 0) return null;
var suffix = nodeName.Substring(lastIndex + splitCode.Length);
return int.TryParse(suffix, out var idx) ? idx : (int?)null;
}
private static string GetReferenceNamespace()
{
HashSet<string> namespaceSet = new HashSet<string>(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<UIBindData> 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)
public static void GenerateUIBindScript(GameObject targetObject, UIScriptGenerateData scriptGenerateData)
{
if (targetObject == null) throw new ArgumentNullException(nameof(targetObject));
if (scriptGenerateData == null) throw new ArgumentNullException(nameof(scriptGenerateData));
@ -395,61 +308,59 @@ namespace AlicizaX.UI.Editor
}
var ruleHelper = UIGeneratorRuleHelper;
if (ruleHelper == null)
{
Debug.LogError("UIGeneratorRuleHelper not available, abort.");
return;
}
if (!ruleHelper.CheckCanGenerate(targetObject, scriptGenerateData))
if (ruleHelper == null || !ruleHelper.CheckCanGenerate(targetObject, scriptGenerateData))
{
return;
}
EditorPrefs.SetInt("InstanceId", targetObject.GetInstanceID());
_uiBindDatas.Clear();
_arrayComponents.Clear();
InitializeGenerationContext(targetObject);
string className = ruleHelper.GetClassGenerateName(targetObject, scriptGenerateData);
var className = ruleHelper.GetClassGenerateName(targetObject, scriptGenerateData);
if (string.IsNullOrEmpty(className))
{
Debug.LogError("Generated className is empty.");
return;
}
GetBindData(targetObject.transform);
CollectBindData(targetObject.transform);
GenerateScript(targetObject, className, scriptGenerateData, ruleHelper);
}
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));
private static void InitializeGenerationContext(GameObject targetObject)
{
EditorPrefs.SetInt("InstanceId", targetObject.GetInstanceID());
_uiBindDatas.Clear();
_arrayComponents.Clear();
}
ruleHelper.WriteUIScriptContent(className, uiTemplateText, scriptGenerateData);
private static void GenerateScript(GameObject targetObject, string className, UIScriptGenerateData scriptGenerateData, IUIGeneratorRuleHelper ruleHelper)
{
var templateText = File.ReadAllText(UIGlobalPath.TemplatePath);
var processedText = ProcessTemplateText(targetObject, templateText, className, scriptGenerateData, ruleHelper);
ruleHelper.WriteUIScriptContent(className, processedText, scriptGenerateData);
EditorPrefs.SetString("Generate", className);
}
private static string ProcessTemplateText(GameObject targetObject, string templateText, string className, UIScriptGenerateData scriptGenerateData, IUIGeneratorRuleHelper ruleHelper)
{
return templateText
.Replace("#ReferenceNameSpace#", ruleHelper.GetReferenceNamespace(_uiBindDatas))
.Replace("#ClassNameSpace#", scriptGenerateData.NameSpace)
.Replace("#ClassName#", className)
.Replace("#TagName#", ruleHelper.GetUIResourceSavePath(targetObject, scriptGenerateData))
.Replace("#LoadType#", scriptGenerateData.LoadType.ToString())
.Replace("#Variable#", ruleHelper.GetVariableContent(_uiBindDatas));
}
[DidReloadScripts]
public static void CheckHasAttach()
public static void BindUIScript()
{
if (!EditorPrefs.HasKey("Generate"))
return;
EditorPrefs.DeleteKey("Generate");
_uiBindDatas.Clear();
_arrayComponents.Clear();
if (!EditorPrefs.HasKey("Generate")) return;
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;
var className = EditorPrefs.GetString("Generate");
var instanceId = EditorPrefs.GetInt("InstanceId", -1);
var targetObject = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
if (targetObject == null)
{
@ -457,12 +368,20 @@ namespace AlicizaX.UI.Editor
return;
}
_uiBindDatas.Clear();
_arrayComponents.Clear();
GetBindData(targetObject.transform);
CollectBindData(targetObject.transform);
BindScriptPropertyField(targetObject, className);
CleanupContext();
Debug.Log($"Generate {className} Successfully attached to game object");
}
private static void CleanupContext()
{
EditorPrefs.DeleteKey("Generate");
_uiBindDatas.Clear();
_arrayComponents.Clear();
}
private static void BindScriptPropertyField(GameObject targetObject, string scriptClassName)
@ -470,97 +389,97 @@ namespace AlicizaX.UI.Editor
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;
}
var scriptType = FindScriptType(scriptClassName);
if (scriptType == null)
{
Debug.LogError($"Could not find the class: {scriptClassName}");
return;
}
Component component = targetObject.GetOrAddComponent(scriptType);
var component = targetObject.GetOrAddComponent(scriptType);
BindFieldsToComponents(component, scriptType);
}
FieldInfo[] fields = scriptType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
private static Type FindScriptType(string scriptClassName)
{
if (string.IsNullOrEmpty(field.Name)) continue;
var componentInObjects = _uiBindDatas.Find(data => data.Name == field.Name)?.BindCom;
if (componentInObjects == null)
return AppDomain.CurrentDomain.GetAssemblies()
.Where(asm => !asm.GetName().Name.EndsWith(".Editor") &&
!asm.GetName().Name.Equals("UnityEditor"))
.SelectMany(asm => asm.GetTypes())
.FirstOrDefault(type => type.IsClass && !type.IsAbstract &&
type.Name.Equals(scriptClassName, StringComparison.Ordinal));
}
private static void BindFieldsToComponents(Component component, Type scriptType)
{
var fields = scriptType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields.Where(field => !string.IsNullOrEmpty(field.Name)))
{
var components = _uiBindDatas.Find(data => data.Name == field.Name)?.BindCom;
if (components == null)
{
Debug.LogError($"Field {field.Name} did not find matching component binding");
continue;
}
SetFieldValue(field, components, component);
}
}
private static void SetFieldValue(FieldInfo field, IReadOnlyList<Component> components, Component targetComponent)
{
if (field.FieldType.IsArray)
{
Type elementType = field.FieldType.GetElementType();
SetArrayFieldValue(field, components, targetComponent);
}
else
{
SetSingleFieldValue(field, components, targetComponent);
}
}
private static void SetArrayFieldValue(FieldInfo field, IReadOnlyList<Component> components, Component targetComponent)
{
var elementType = field.FieldType.GetElementType();
if (elementType == null)
{
Debug.LogError($"Field {field.Name} has unknown element type.");
continue;
return;
}
Array array = Array.CreateInstance(elementType, componentInObjects.Count);
for (int i = 0; i < componentInObjects.Count; i++)
var array = Array.CreateInstance(elementType, components.Count);
for (var i = 0; i < components.Count; i++)
{
Component comp = componentInObjects[i];
if (comp == null) continue;
if (components[i] == null) continue;
if (elementType.IsInstanceOfType(comp))
if (elementType.IsInstanceOfType(components[i]))
{
array.SetValue(comp, i);
array.SetValue(components[i], i);
}
else
{
Debug.LogError($"Element {i} type mismatch for field {field.Name}, expected {elementType.Name}, actual {comp.GetType().Name}");
Debug.LogError($"Element {i} type mismatch for field {field.Name}");
}
}
field.SetValue(component, array);
field.SetValue(targetComponent, array);
}
else
{
if (componentInObjects.Count > 0)
{
var first = componentInObjects[0];
if (first == null) continue;
if (field.FieldType.IsInstanceOfType(first))
private static void SetSingleFieldValue(FieldInfo field, IReadOnlyList<Component> components, Component targetComponent)
{
field.SetValue(component, first);
if (components.Count == 0) return;
var firstComponent = components[0];
if (firstComponent == null) return;
if (field.FieldType.IsInstanceOfType(firstComponent))
{
field.SetValue(targetComponent, firstComponent);
}
else
{
Debug.LogError($"Field {field.Name} type mismatch, cannot assign value. Field expects {field.FieldType.Name}, actual {first.GetType().Name}");
}
}
}
Debug.LogError($"Field {field.Name} type mismatch");
}
}
@ -569,9 +488,7 @@ namespace AlicizaX.UI.Editor
public static bool IsEditingPrefabAsset(GameObject go)
{
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage == null)
return false;
return prefabStage.IsPartOfPrefabContents(go);
return prefabStage?.IsPartOfPrefabContents(go) == true;
}
public static bool IsPrefabAsset(GameObject go)
@ -579,12 +496,11 @@ namespace AlicizaX.UI.Editor
if (go == null) return false;
var assetType = PrefabUtility.GetPrefabAssetType(go);
if (assetType == PrefabAssetType.Regular ||
var isRegularPrefab = assetType == PrefabAssetType.Regular ||
assetType == PrefabAssetType.Variant ||
assetType == PrefabAssetType.Model)
return true;
assetType == PrefabAssetType.Model;
return IsEditingPrefabAsset(go);
return isRegularPrefab || IsEditingPrefabAsset(go);
}
}
}

View File

@ -1,6 +1,8 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace AlicizaX.UI.Runtime
@ -38,19 +40,67 @@ namespace AlicizaX.UI.Runtime
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGet(RuntimeTypeHandle handle, out UIMetaInfo info)
{
return _typeHandleMap.TryGetValue(handle, out info);
if (_typeHandleMap.TryGetValue(handle, out info))
return true;
var t = Type.GetTypeFromHandle(handle);
if (TryReflectAndRegister(t, out info))
return true;
return false;
}
public static bool TryGet(string type, out UIMetaInfo info)
{
RuntimeTypeHandle typeHandle;
if (_stringHandleMap.TryGetValue(type, out typeHandle))
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGet(string typeName, out UIMetaInfo info)
{
if (_stringHandleMap.TryGetValue(typeName, out var handle))
return TryGet(handle, out info);
var type = AlicizaX.Utility.Assembly.GetType(typeName);
if (type != null && TryReflectAndRegister(type, out info))
return true;
info = default;
return false;
}
return _typeHandleMap.TryGetValue(typeHandle, out info);
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool TryReflectAndRegister(Type uiType, out UIMetaInfo info)
{
Log.Warning($"[UI] UI未注册[{uiType.FullName}] 反射进行缓存");
Type baseType = uiType;
Type? holderType = baseType.GetGenericArguments()[0];
UILayer layer = UILayer.UI;
bool fullScreen = false;
int cacheTime = 0;
var cad = CustomAttributeData.GetCustomAttributes(uiType)
.FirstOrDefault(a => a.AttributeType.Name == nameof(WindowAttribute));
if (cad != null)
{
var args = cad.ConstructorArguments;
if (args.Count > 0) layer = (UILayer)(args[0].Value ?? UILayer.UI);
if (args.Count > 1) fullScreen = (bool)(args[1].Value ?? false);
if (args.Count > 2) cacheTime = (int)(args[2].Value ?? 0);
}
if (holderType != null)
{
Register(uiType, holderType, layer, fullScreen, cacheTime);
info = _typeHandleMap[uiType.TypeHandle];
return true;
}
info = default;
return false;
}
}
}

View File

@ -1,6 +1,8 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using UnityEditor;
using UnityEngine;
@ -32,6 +34,37 @@ namespace AlicizaX.UI.Runtime
}
public static bool TryGet(RuntimeTypeHandle handle, out UIResInfo info)
=> _typeHandleMap.TryGetValue(handle, out info);
{
if (_typeHandleMap.TryGetValue(handle, out info))
return true;
var t = Type.GetTypeFromHandle(handle);
if (TryReflectAndRegister(t, out info))
return true;
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool TryReflectAndRegister(Type holderType, out UIResInfo info)
{
var cad = CustomAttributeData.GetCustomAttributes(holderType)
.FirstOrDefault(a => a.AttributeType.Name == nameof(UIResAttribute));
string resLocation = string.Empty;
EUIResLoadType resLoadType = EUIResLoadType.AssetBundle;
if (cad != null)
{
var args = cad.ConstructorArguments;
if (args.Count > 0) resLocation = (string)(args[0].Value ?? string.Empty);
if (args.Count > 1) resLoadType = (EUIResLoadType)(args[1].Value ?? EUIResLoadType.AssetBundle);
Register(holderType, resLocation, resLoadType);
info = _typeHandleMap[holderType.TypeHandle];
return true;
}
info = default;
return false;
}
}
}