888 lines
33 KiB
C#
888 lines
33 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using AlicizaX.UI.Runtime;
|
||
using UnityEditor;
|
||
using UnityEditor.Callbacks;
|
||
using UnityEditor.SceneManagement;
|
||
using UnityEngine;
|
||
|
||
namespace AlicizaX.UI.Editor
|
||
{
|
||
public enum EBindType
|
||
{
|
||
None,
|
||
Widget,
|
||
ListCom
|
||
}
|
||
|
||
[Serializable]
|
||
public class UIBindData
|
||
{
|
||
public UIBindData(string name, List<GameObject> objs, Type componentType = null, EBindType bindType = EBindType.None)
|
||
{
|
||
Name = name;
|
||
Objs = objs ?? new List<GameObject>();
|
||
BindType = bindType;
|
||
ComponentType = componentType;
|
||
}
|
||
|
||
public UIBindData(string name, GameObject obj, Type componentType = null, EBindType bindType = EBindType.None)
|
||
: this(name, new List<GameObject> { obj }, componentType, bindType)
|
||
{
|
||
}
|
||
|
||
public string Name { get; }
|
||
|
||
public List<GameObject> Objs { get; set; }
|
||
|
||
public EBindType BindType { get; }
|
||
|
||
public Type ComponentType { get; private set; }
|
||
|
||
public bool IsGameObject => ComponentType == typeof(GameObject);
|
||
|
||
public string TypeName => ComponentType?.FullName ?? string.Empty;
|
||
|
||
public Type GetFirstOrDefaultType() => ComponentType;
|
||
|
||
public void SetComponentType(Type componentType)
|
||
{
|
||
ComponentType = componentType;
|
||
}
|
||
}
|
||
|
||
public static class UIScriptGeneratorHelper
|
||
{
|
||
private const string GenerateTypeNameKey = "AlicizaX.UI.Generate.TypeName";
|
||
private const string GenerateInstanceIdKey = "AlicizaX.UI.Generate.InstanceId";
|
||
private const string GenerateAssetPathKey = "AlicizaX.UI.Generate.AssetPath";
|
||
private static UIGenerateConfiguration _uiGenerateConfiguration;
|
||
private static IUIIdentifierFormatter _identifierFormatter;
|
||
private static IUIResourcePathResolver _resourcePathResolver;
|
||
private static IUIScriptCodeEmitter _scriptCodeEmitter;
|
||
private static IUIScriptFileWriter _scriptFileWriter;
|
||
private static readonly List<UIBindData> _uiBindDatas = new List<UIBindData>();
|
||
private static readonly HashSet<string> _arrayComponents = new HashSet<string>(StringComparer.Ordinal);
|
||
private static readonly Dictionary<string, Type> _componentTypeCache = new Dictionary<string, Type>(StringComparer.Ordinal);
|
||
|
||
private static UIGenerateConfiguration UIConfiguration =>
|
||
_uiGenerateConfiguration ??= UIGenerateConfiguration.Instance;
|
||
|
||
private static IUIIdentifierFormatter IdentifierFormatter =>
|
||
ResolveConfiguredService(
|
||
ref _identifierFormatter,
|
||
UIConfiguration.UIIdentifierFormatterTypeName,
|
||
typeof(DefaultUIIdentifierFormatter),
|
||
nameof(IUIIdentifierFormatter));
|
||
|
||
private static IUIResourcePathResolver ResourcePathResolver =>
|
||
ResolveConfiguredService(
|
||
ref _resourcePathResolver,
|
||
UIConfiguration.UIResourcePathResolverTypeName,
|
||
typeof(DefaultUIResourcePathResolver),
|
||
nameof(IUIResourcePathResolver));
|
||
|
||
private static IUIScriptCodeEmitter ScriptCodeEmitter =>
|
||
ResolveConfiguredService(
|
||
ref _scriptCodeEmitter,
|
||
UIConfiguration.UIScriptCodeEmitterTypeName,
|
||
typeof(DefaultUIScriptCodeEmitter),
|
||
nameof(IUIScriptCodeEmitter));
|
||
|
||
private static IUIScriptFileWriter ScriptFileWriter =>
|
||
ResolveConfiguredService(
|
||
ref _scriptFileWriter,
|
||
UIConfiguration.UIScriptFileWriterTypeName,
|
||
typeof(DefaultUIScriptFileWriter),
|
||
nameof(IUIScriptFileWriter));
|
||
|
||
private static T ResolveConfiguredService<T>(ref T cachedService, string configuredTypeName, Type defaultType, string serviceName)
|
||
where T : class
|
||
{
|
||
var resolvedTypeName = string.IsNullOrWhiteSpace(configuredTypeName) ? defaultType.FullName : configuredTypeName;
|
||
if (cachedService != null && cachedService.GetType().FullName == resolvedTypeName)
|
||
{
|
||
return cachedService;
|
||
}
|
||
|
||
var configuredType = AlicizaX.Utility.Assembly.GetType(resolvedTypeName);
|
||
if (configuredType == null || !typeof(T).IsAssignableFrom(configuredType))
|
||
{
|
||
if (!string.Equals(resolvedTypeName, defaultType.FullName, StringComparison.Ordinal))
|
||
{
|
||
Debug.LogError(
|
||
$"UIScriptGeneratorHelper: Could not load {serviceName} type '{resolvedTypeName}'. Falling back to {defaultType.FullName}.");
|
||
}
|
||
|
||
configuredType = defaultType;
|
||
}
|
||
|
||
cachedService = Activator.CreateInstance(configuredType, true) as T;
|
||
if (cachedService != null)
|
||
{
|
||
return cachedService;
|
||
}
|
||
|
||
if (configuredType != defaultType)
|
||
{
|
||
Debug.LogError(
|
||
$"UIScriptGeneratorHelper: Failed to instantiate {resolvedTypeName} as {serviceName}. Falling back to {defaultType.FullName}.");
|
||
cachedService = Activator.CreateInstance(defaultType, true) as T;
|
||
}
|
||
|
||
if (cachedService == null)
|
||
{
|
||
Debug.LogError($"UIScriptGeneratorHelper: Failed to instantiate fallback {defaultType.FullName} as {serviceName}.");
|
||
}
|
||
|
||
return cachedService;
|
||
}
|
||
|
||
private static bool EnsureGenerationStrategyReady()
|
||
{
|
||
return IdentifierFormatter != null &&
|
||
ResourcePathResolver != null &&
|
||
ScriptCodeEmitter != null &&
|
||
ScriptFileWriter != null;
|
||
}
|
||
|
||
private static bool EnsureVariableGenerationStrategyReady()
|
||
{
|
||
return IdentifierFormatter != null &&
|
||
ScriptCodeEmitter != null;
|
||
}
|
||
|
||
private static Type ResolveUIElementComponentType(string uiName)
|
||
{
|
||
if (string.IsNullOrEmpty(uiName))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var componentTypeName = UIConfiguration.UIElementRegexConfigs
|
||
?.Where(pair => !string.IsNullOrEmpty(pair?.uiElementRegex))
|
||
.FirstOrDefault(pair => uiName.StartsWith(pair.uiElementRegex, StringComparison.Ordinal))
|
||
?.componentType;
|
||
|
||
if (string.IsNullOrWhiteSpace(componentTypeName))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
if (_componentTypeCache.TryGetValue(componentTypeName, out var componentType))
|
||
{
|
||
return componentType;
|
||
}
|
||
|
||
componentType = ResolveConfiguredComponentType(componentTypeName);
|
||
|
||
if (componentType == null)
|
||
{
|
||
Debug.LogError($"UIScriptGeneratorHelper: Could not resolve component type '{componentTypeName}' for '{uiName}'.");
|
||
return null;
|
||
}
|
||
|
||
_componentTypeCache[componentTypeName] = componentType;
|
||
return componentType;
|
||
}
|
||
|
||
private static Type ResolveConfiguredComponentType(string componentTypeName)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(componentTypeName))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
if (componentTypeName == nameof(GameObject))
|
||
{
|
||
return typeof(GameObject);
|
||
}
|
||
|
||
Type componentType = AlicizaX.Utility.Assembly.GetType(componentTypeName);
|
||
if (componentType != null && typeof(Component).IsAssignableFrom(componentType))
|
||
{
|
||
return componentType;
|
||
}
|
||
|
||
return AlicizaX.Utility.Assembly.GetTypes()
|
||
.Where(type => type != null && !type.IsAbstract && !type.IsInterface)
|
||
.Where(type => typeof(Component).IsAssignableFrom(type))
|
||
.Where(type => string.Equals(type.FullName, componentTypeName, StringComparison.Ordinal)
|
||
|| string.Equals(type.Name, componentTypeName, StringComparison.Ordinal))
|
||
.OrderBy(type => string.Equals(type.FullName, componentTypeName, StringComparison.Ordinal) ? 0 : 1)
|
||
.ThenBy(type => string.Equals(type.Namespace, "UnityEngine.UI", StringComparison.Ordinal) ? 0 : 1)
|
||
.ThenBy(type => type.FullName, StringComparer.Ordinal)
|
||
.FirstOrDefault();
|
||
}
|
||
|
||
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;
|
||
|
||
var endIndex = name.IndexOf(common.ComCheckEndName, StringComparison.Ordinal);
|
||
if (endIndex <= 0) return null;
|
||
|
||
var comStr = name.Substring(0, endIndex);
|
||
var split = common.ComCheckSplitName ?? "#";
|
||
|
||
return comStr.Split(new[] { split }, StringSplitOptions.RemoveEmptyEntries);
|
||
}
|
||
|
||
private static void CollectBindData(Transform root)
|
||
{
|
||
if (root == null) return;
|
||
|
||
foreach (Transform child in root.Cast<Transform>().Where(child => child != null))
|
||
{
|
||
if (ShouldSkipChild(child)) continue;
|
||
var hasWidget = child.GetComponent<UIHolderObjectBase>() != null;
|
||
var isArrayComponent = IsArrayComponent(child.name);
|
||
|
||
if (hasWidget)
|
||
{
|
||
CollectWidget(child);
|
||
}
|
||
else if (isArrayComponent)
|
||
{
|
||
ProcessArrayComponent(child, root);
|
||
}
|
||
else
|
||
{
|
||
CollectComponent(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;
|
||
if (!TryGetArrayComponentGroupName(child.name, splitCode, out var groupName))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var groupKey = $"{root.GetInstanceID()}::{groupName}";
|
||
if (!_arrayComponents.Add(groupKey))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var arrayComponents = root.Cast<Transform>()
|
||
.Where(sibling => IsArrayGroupMember(sibling.name, splitCode, groupName))
|
||
.ToList();
|
||
|
||
CollectArrayComponent(arrayComponents, groupName);
|
||
}
|
||
|
||
private static void CollectComponent(Transform node)
|
||
{
|
||
if (node == null) return;
|
||
|
||
var componentArray = SplitComponentName(node.name);
|
||
if (componentArray == null || componentArray.Length == 0) return;
|
||
|
||
foreach (var com in componentArray.Where(com => !string.IsNullOrEmpty(com)))
|
||
{
|
||
var componentType = ResolveUIElementComponentType(com);
|
||
if (componentType == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (componentType != typeof(GameObject) && ResolveAssignableComponent(node.gameObject, componentType) == null)
|
||
{
|
||
Debug.LogError($"{node.name} does not have component of type {componentType.FullName}");
|
||
continue;
|
||
}
|
||
|
||
var keyName = GetPrivateComponentName(com, node.name, EBindType.None);
|
||
if (_uiBindDatas.Exists(data => data.Name == keyName))
|
||
{
|
||
Debug.LogError($"Duplicate key found: {keyName}");
|
||
continue;
|
||
}
|
||
|
||
_uiBindDatas.Add(new UIBindData(keyName, node.gameObject, componentType));
|
||
}
|
||
}
|
||
|
||
private static void CollectWidget(Transform node)
|
||
{
|
||
if (node == null) return;
|
||
|
||
var common = UIConfiguration.UIGenerateCommonData;
|
||
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;
|
||
}
|
||
|
||
var component = node.GetComponent<UIHolderObjectBase>();
|
||
if (component == null)
|
||
{
|
||
Debug.LogError($"{node.name} expected to be a widget but does not have UIHolderObjectBase.");
|
||
return;
|
||
}
|
||
|
||
var keyName = GetPrivateComponentName(string.Empty, node.name, EBindType.Widget);
|
||
if (_uiBindDatas.Exists(data => data.Name == keyName))
|
||
{
|
||
Debug.LogError($"Duplicate key found: {keyName}");
|
||
return;
|
||
}
|
||
|
||
_uiBindDatas.Add(new UIBindData(keyName, component.gameObject, component.GetType(), EBindType.Widget));
|
||
}
|
||
|
||
private static void CollectArrayComponent(List<Transform> arrayNode, string nodeName)
|
||
{
|
||
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;
|
||
}
|
||
|
||
var orderedNodes = OrderArrayNodes(arrayNode);
|
||
var tempBindDatas = CreateTempBindDatas(componentArray, nodeName);
|
||
|
||
PopulateArrayComponents(componentArray, orderedNodes, tempBindDatas);
|
||
_uiBindDatas.AddRange(tempBindDatas);
|
||
}
|
||
|
||
private static List<Transform> OrderArrayNodes(List<Transform> arrayNode)
|
||
{
|
||
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 =>
|
||
{
|
||
var keyName = GetPrivateComponentName(com, nodeName, EBindType.ListCom);
|
||
return new UIBindData(keyName, new List<GameObject>(), null, 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;
|
||
|
||
var componentType = ResolveUIElementComponentType(com);
|
||
if (componentType == null) continue;
|
||
tempBindDatas[index].SetComponentType(componentType);
|
||
foreach (var node in orderedNodes)
|
||
{
|
||
var isGameObject = componentType == typeof(GameObject);
|
||
var component = isGameObject ? null : ResolveAssignableComponent(node.gameObject, componentType);
|
||
|
||
if (component != null || isGameObject)
|
||
{
|
||
tempBindDatas[index].Objs.Add(node.gameObject);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"{node.name} does not have component of type {componentType.FullName}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private static bool TryGetArrayComponentGroupName(string nodeName, string splitCode, out string groupName)
|
||
{
|
||
groupName = null;
|
||
if (string.IsNullOrEmpty(nodeName) || string.IsNullOrEmpty(splitCode))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var firstIndex = nodeName.IndexOf(splitCode, StringComparison.Ordinal);
|
||
var lastIndex = nodeName.LastIndexOf(splitCode, StringComparison.Ordinal);
|
||
if (firstIndex < 0 || lastIndex <= firstIndex)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
groupName = nodeName.Substring(firstIndex + splitCode.Length, lastIndex - (firstIndex + splitCode.Length));
|
||
return !string.IsNullOrEmpty(groupName);
|
||
}
|
||
|
||
private static bool IsArrayGroupMember(string nodeName, string splitCode, string groupName)
|
||
{
|
||
return TryGetArrayComponentGroupName(nodeName, splitCode, out var candidateGroupName) &&
|
||
candidateGroupName.Equals(groupName, StringComparison.Ordinal) &&
|
||
ExtractArrayIndex(nodeName, splitCode).HasValue;
|
||
}
|
||
|
||
private static int? ExtractArrayIndex(string nodeName, string splitCode)
|
||
{
|
||
if (string.IsNullOrEmpty(nodeName) || string.IsNullOrEmpty(splitCode)) 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;
|
||
}
|
||
|
||
public static void GenerateUIBindScript(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("Please save the UI as a prefab asset before generating bindings.");
|
||
return;
|
||
}
|
||
|
||
if (!EnsureGenerationStrategyReady())
|
||
{
|
||
return;
|
||
}
|
||
|
||
InitializeGenerationContext(targetObject);
|
||
CollectBindData(targetObject.transform);
|
||
|
||
var generationContext = new UIGenerationContext(targetObject, scriptGenerateData, _uiBindDatas)
|
||
{
|
||
AssetPath = UIGenerateQuick.GetPrefabAssetPath(targetObject),
|
||
ClassName = GetClassGenerateName(targetObject, scriptGenerateData)
|
||
};
|
||
|
||
if (!CheckCanGenerate(generationContext))
|
||
{
|
||
CleanupContext();
|
||
return;
|
||
}
|
||
|
||
var validationResult = ValidateGeneration(generationContext);
|
||
if (!validationResult.IsValid)
|
||
{
|
||
Debug.LogError(validationResult.Message);
|
||
CleanupContext();
|
||
return;
|
||
}
|
||
|
||
GenerateScript(generationContext);
|
||
}
|
||
|
||
public static void CopyVariableContentToClipboard(GameObject targetObject)
|
||
{
|
||
if (targetObject == null) throw new ArgumentNullException(nameof(targetObject));
|
||
|
||
if (!EnsureVariableGenerationStrategyReady())
|
||
{
|
||
return;
|
||
}
|
||
|
||
string variableContent;
|
||
try
|
||
{
|
||
ResetCollectedBindData();
|
||
CollectBindData(targetObject.transform);
|
||
variableContent = ScriptCodeEmitter.GetVariableContent(_uiBindDatas, GetPublicComponentName);
|
||
}
|
||
finally
|
||
{
|
||
ResetCollectedBindData();
|
||
}
|
||
|
||
EditorGUIUtility.systemCopyBuffer = variableContent ?? string.Empty;
|
||
if (string.IsNullOrEmpty(variableContent))
|
||
{
|
||
Debug.LogWarning($"UI生成绑定未找到可复制的属性内容,已清空剪贴板: {targetObject.name}");
|
||
return;
|
||
}
|
||
|
||
Debug.Log($"UI生成绑定属性已复制到剪贴板: {targetObject.name}");
|
||
}
|
||
|
||
private static void InitializeGenerationContext(GameObject targetObject)
|
||
{
|
||
EditorPrefs.SetInt(GenerateInstanceIdKey, targetObject.GetInstanceID());
|
||
var assetPath = UIGenerateQuick.GetPrefabAssetPath(targetObject);
|
||
if (!string.IsNullOrEmpty(assetPath))
|
||
{
|
||
EditorPrefs.SetString(GenerateAssetPathKey, assetPath);
|
||
}
|
||
|
||
ResetCollectedBindData();
|
||
}
|
||
|
||
private static UIGenerationValidationResult ValidateGeneration(UIGenerationContext context)
|
||
{
|
||
if (context.TargetObject == null)
|
||
{
|
||
return UIGenerationValidationResult.Fail("UI generation target is null.");
|
||
}
|
||
|
||
if (context.ScriptGenerateData == null)
|
||
{
|
||
return UIGenerationValidationResult.Fail("UI generation config is null.");
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(context.ClassName))
|
||
{
|
||
return UIGenerationValidationResult.Fail("Generated class name is empty.");
|
||
}
|
||
|
||
if (!File.Exists(UIGlobalPath.TemplatePath))
|
||
{
|
||
return UIGenerationValidationResult.Fail($"UI template file not found: {UIGlobalPath.TemplatePath}");
|
||
}
|
||
|
||
return UIGenerationValidationResult.Success();
|
||
}
|
||
|
||
private static void GenerateScript(UIGenerationContext context)
|
||
{
|
||
var templateText = File.ReadAllText(UIGlobalPath.TemplatePath);
|
||
var processedText = ProcessTemplateText(context, templateText);
|
||
EditorPrefs.SetString(GenerateTypeNameKey, context.FullTypeName);
|
||
WriteScriptContent(context, processedText);
|
||
}
|
||
|
||
private static string ProcessTemplateText(UIGenerationContext context, string templateText)
|
||
{
|
||
return templateText
|
||
.Replace("#ReferenceNameSpace#", GetReferenceNamespace(context))
|
||
.Replace("#ClassNameSpace#", context.ScriptGenerateData.NameSpace)
|
||
.Replace("#ClassName#", context.ClassName)
|
||
.Replace("#TagName#", GetResourceSavePath(context))
|
||
.Replace("#LoadType#", context.ScriptGenerateData.LoadType.ToString())
|
||
.Replace("#Variable#", GetVariableContent(context));
|
||
}
|
||
|
||
private static string GetClassGenerateName(GameObject targetObject, UIScriptGenerateData scriptGenerateData)
|
||
{
|
||
return IdentifierFormatter.GetClassName(targetObject);
|
||
}
|
||
|
||
private static bool CheckCanGenerate(UIGenerationContext context)
|
||
{
|
||
return ResourcePathResolver.CanGenerate(context.TargetObject, context.ScriptGenerateData);
|
||
}
|
||
|
||
private static string GetPrivateComponentName(string regexName, string componentName, EBindType bindType)
|
||
{
|
||
return IdentifierFormatter.GetPrivateComponentName(regexName, componentName, bindType);
|
||
}
|
||
|
||
private static string GetPublicComponentName(string variableName)
|
||
{
|
||
return IdentifierFormatter.GetPublicComponentName(variableName);
|
||
}
|
||
|
||
private static string GetResourceSavePath(UIGenerationContext context)
|
||
{
|
||
return ResourcePathResolver.GetResourcePath(context.TargetObject, context.ScriptGenerateData);
|
||
}
|
||
|
||
private static string GetReferenceNamespace(UIGenerationContext context)
|
||
{
|
||
return ScriptCodeEmitter.GetReferenceNamespaces(_uiBindDatas);
|
||
}
|
||
|
||
private static string GetVariableContent(UIGenerationContext context)
|
||
{
|
||
return ScriptCodeEmitter.GetVariableContent(_uiBindDatas, GetPublicComponentName);
|
||
}
|
||
|
||
private static void WriteScriptContent(UIGenerationContext context, string scriptContent)
|
||
{
|
||
ScriptFileWriter.Write(context.TargetObject, context.ClassName, scriptContent, context.ScriptGenerateData);
|
||
}
|
||
|
||
[DidReloadScripts]
|
||
public static void BindUIScript()
|
||
{
|
||
if (!EditorPrefs.HasKey(GenerateTypeNameKey)) return;
|
||
|
||
var scriptTypeName = EditorPrefs.GetString(GenerateTypeNameKey);
|
||
var targetObject = ResolveGenerationTarget();
|
||
|
||
if (targetObject == null)
|
||
{
|
||
Debug.LogWarning("UI script generation attachment object missing.");
|
||
CleanupContext();
|
||
return;
|
||
}
|
||
|
||
if (!EnsureGenerationStrategyReady())
|
||
{
|
||
CleanupContext();
|
||
return;
|
||
}
|
||
|
||
ResetCollectedBindData();
|
||
|
||
var bindSucceeded = false;
|
||
CollectBindData(targetObject.transform);
|
||
try
|
||
{
|
||
bindSucceeded = BindScriptPropertyField(targetObject, scriptTypeName);
|
||
}
|
||
catch (Exception exception)
|
||
{
|
||
Debug.LogException(exception);
|
||
}
|
||
finally
|
||
{
|
||
CleanupContext();
|
||
}
|
||
|
||
if (!bindSucceeded)
|
||
{
|
||
return;
|
||
}
|
||
|
||
EditorUtility.SetDirty(targetObject);
|
||
Debug.Log($"Generate {scriptTypeName} successfully attached to game object.");
|
||
}
|
||
|
||
private static GameObject ResolveGenerationTarget()
|
||
{
|
||
var instanceId = EditorPrefs.GetInt(GenerateInstanceIdKey, -1);
|
||
var instanceTarget = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
|
||
if (instanceTarget != null)
|
||
{
|
||
return instanceTarget;
|
||
}
|
||
|
||
var assetPath = EditorPrefs.GetString(GenerateAssetPathKey, string.Empty);
|
||
return string.IsNullOrEmpty(assetPath) ? null : AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
|
||
}
|
||
|
||
private static void CleanupContext()
|
||
{
|
||
EditorPrefs.DeleteKey(GenerateTypeNameKey);
|
||
EditorPrefs.DeleteKey(GenerateInstanceIdKey);
|
||
EditorPrefs.DeleteKey(GenerateAssetPathKey);
|
||
ResetCollectedBindData();
|
||
}
|
||
|
||
private static void ResetCollectedBindData()
|
||
{
|
||
_uiBindDatas.Clear();
|
||
_arrayComponents.Clear();
|
||
_componentTypeCache.Clear();
|
||
}
|
||
|
||
private static bool BindScriptPropertyField(GameObject targetObject, string scriptTypeName)
|
||
{
|
||
if (targetObject == null) throw new ArgumentNullException(nameof(targetObject));
|
||
if (string.IsNullOrEmpty(scriptTypeName)) throw new ArgumentNullException(nameof(scriptTypeName));
|
||
|
||
var scriptType = FindScriptType(scriptTypeName);
|
||
if (scriptType == null)
|
||
{
|
||
Debug.LogError($"Could not find the class: {scriptTypeName}");
|
||
return false;
|
||
}
|
||
|
||
var targetHolder = targetObject.GetOrAddComponent(scriptType);
|
||
return BindFieldsToComponents(targetHolder, scriptType);
|
||
}
|
||
|
||
private static Type FindScriptType(string scriptTypeName)
|
||
{
|
||
var resolvedType = AlicizaX.Utility.Assembly.GetType(scriptTypeName);
|
||
if (resolvedType != null)
|
||
{
|
||
return resolvedType;
|
||
}
|
||
|
||
return AppDomain.CurrentDomain.GetAssemblies()
|
||
.Where(assembly => !assembly.GetName().Name.EndsWith(".Editor", StringComparison.Ordinal) &&
|
||
!assembly.GetName().Name.Equals("UnityEditor", StringComparison.Ordinal))
|
||
.SelectMany(assembly => assembly.GetTypes())
|
||
.FirstOrDefault(type => type.IsClass && !type.IsAbstract &&
|
||
type.FullName.Equals(scriptTypeName, StringComparison.Ordinal));
|
||
}
|
||
|
||
private static bool BindFieldsToComponents(Component targetHolder, Type scriptType)
|
||
{
|
||
var fields = scriptType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(field =>
|
||
field.GetCustomAttributes(typeof(SerializeField), false).Length > 0
|
||
);
|
||
|
||
var isSuccessful = true;
|
||
foreach (var field in fields.Where(field => !string.IsNullOrEmpty(field.Name)))
|
||
{
|
||
var bindData = _uiBindDatas.Find(data => data.Name == field.Name);
|
||
if (bindData == null)
|
||
{
|
||
Debug.LogError($"Field {field.Name} did not find matching component binding.");
|
||
isSuccessful = false;
|
||
continue;
|
||
}
|
||
|
||
if (!SetFieldValue(field, bindData, targetHolder))
|
||
{
|
||
isSuccessful = false;
|
||
}
|
||
}
|
||
|
||
return isSuccessful;
|
||
}
|
||
|
||
private static bool SetFieldValue(FieldInfo field, UIBindData bindData, Component targetComponent)
|
||
{
|
||
if (field.FieldType.IsArray)
|
||
{
|
||
return SetArrayFieldValue(field, bindData, targetComponent);
|
||
}
|
||
|
||
return SetSingleFieldValue(field, bindData, targetComponent);
|
||
}
|
||
|
||
private static bool SetArrayFieldValue(FieldInfo field, UIBindData bindData, Component targetComponent)
|
||
{
|
||
var elementType = field.FieldType.GetElementType();
|
||
if (elementType == null)
|
||
{
|
||
Debug.LogError($"Field {field.Name} has unknown element type.");
|
||
return false;
|
||
}
|
||
|
||
var components = bindData.Objs;
|
||
var array = Array.CreateInstance(elementType, components.Count);
|
||
var isSuccessful = true;
|
||
for (var i = 0; i < components.Count; i++)
|
||
{
|
||
if (components[i] == null) continue;
|
||
|
||
var componentObject = ResolveBoundObject(components[i], bindData.ComponentType);
|
||
|
||
if (componentObject != null && elementType.IsInstanceOfType(componentObject))
|
||
{
|
||
array.SetValue(componentObject, i);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"Element {i} type mismatch for field {field.Name}");
|
||
isSuccessful = false;
|
||
}
|
||
}
|
||
|
||
field.SetValue(targetComponent, array);
|
||
return isSuccessful;
|
||
}
|
||
|
||
private static bool SetSingleFieldValue(FieldInfo field, UIBindData bindData, Component targetComponent)
|
||
{
|
||
if (bindData.Objs.Count == 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var firstComponent = ResolveBoundObject(bindData.Objs[0], bindData.ComponentType);
|
||
if (firstComponent == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!field.FieldType.IsInstanceOfType(firstComponent))
|
||
{
|
||
Debug.LogError($"Field {field.Name} type mismatch");
|
||
return false;
|
||
}
|
||
|
||
field.SetValue(targetComponent, firstComponent);
|
||
return true;
|
||
}
|
||
|
||
private static object ResolveBoundObject(GameObject source, Type componentType)
|
||
{
|
||
if (source == null || componentType == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
return componentType == typeof(GameObject) ? source : ResolveAssignableComponent(source, componentType);
|
||
}
|
||
|
||
private static Component ResolveAssignableComponent(GameObject source, Type componentType)
|
||
{
|
||
if (source == null || componentType == null || !typeof(Component).IsAssignableFrom(componentType))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
Component component = source.GetComponent(componentType);
|
||
if (component != null)
|
||
{
|
||
return component;
|
||
}
|
||
|
||
Component[] components = source.GetComponents<Component>();
|
||
for (int i = 0; i < components.Length; i++)
|
||
{
|
||
Component candidate = components[i];
|
||
if (candidate != null && componentType.IsInstanceOfType(candidate))
|
||
{
|
||
return candidate;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
public static class PrefabChecker
|
||
{
|
||
public static bool IsEditingPrefabAsset(GameObject go)
|
||
{
|
||
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
|
||
return prefabStage?.IsPartOfPrefabContents(go) == true;
|
||
}
|
||
|
||
public static bool IsPrefabAsset(GameObject go)
|
||
{
|
||
if (go == null) return false;
|
||
|
||
var assetType = PrefabUtility.GetPrefabAssetType(go);
|
||
var isRegularPrefab = assetType == PrefabAssetType.Regular ||
|
||
assetType == PrefabAssetType.Variant ||
|
||
assetType == PrefabAssetType.Model;
|
||
|
||
return isRegularPrefab || IsEditingPrefabAsset(go);
|
||
}
|
||
}
|
||
}
|
||
}
|