优化UI生成工作流
This commit is contained in:
parent
0197adc5c6
commit
2bf7d146bf
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,26 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using AlicizaX.UI.Runtime;
|
using AlicizaX.UI.Runtime;
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.SceneManagement;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace AlicizaX.UI.Editor
|
namespace AlicizaX.UI.Editor
|
||||||
{
|
{
|
||||||
|
internal interface IContextualUIGeneratorRuleHelper
|
||||||
|
{
|
||||||
|
string GetClassGenerateName(UIGenerationContext context);
|
||||||
|
|
||||||
|
string GetUIResourceSavePath(UIGenerationContext context);
|
||||||
|
|
||||||
|
bool CheckCanGenerate(UIGenerationContext context);
|
||||||
|
|
||||||
|
string GetReferenceNamespace(UIGenerationContext context);
|
||||||
|
|
||||||
|
string GetVariableContent(UIGenerationContext context);
|
||||||
|
|
||||||
|
void WriteUIScriptContent(UIGenerationContext context, string scriptContent);
|
||||||
|
}
|
||||||
|
|
||||||
public interface IUIGeneratorRuleHelper
|
public interface IUIGeneratorRuleHelper
|
||||||
{
|
{
|
||||||
string GetPrivateComponentByNameRule(string regexName, string componetName, EBindType bindType);
|
string GetPrivateComponentByNameRule(string regexName, string componetName, EBindType bindType);
|
||||||
@ -30,207 +41,74 @@ namespace AlicizaX.UI.Editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class DefaultUIGeneratorRuleHelper : IUIGeneratorRuleHelper
|
public class DefaultUIGeneratorRuleHelper : IUIGeneratorRuleHelper, IContextualUIGeneratorRuleHelper
|
||||||
{
|
{
|
||||||
public string GetPrivateComponentByNameRule(string regexName, string componentName, EBindType bindType)
|
private readonly IUIIdentifierFormatter _identifierFormatter;
|
||||||
{
|
private readonly IUIResourcePathResolver _resourcePathResolver;
|
||||||
var endPrefix = bindType == EBindType.ListCom ? "List" : string.Empty;
|
private readonly IUIScriptCodeEmitter _scriptCodeEmitter;
|
||||||
var common = UIGenerateConfiguration.Instance.UIGenerateCommonData;
|
private readonly IUIScriptFileWriter _scriptFileWriter;
|
||||||
var endNameIndex = componentName.IndexOf(common.ComCheckEndName, StringComparison.Ordinal);
|
|
||||||
|
|
||||||
var componentSuffix = endNameIndex >= 0 ? componentName.Substring(endNameIndex + 1) : componentName;
|
public DefaultUIGeneratorRuleHelper()
|
||||||
return $"m{regexName}{componentSuffix}{endPrefix}";
|
: this(
|
||||||
|
new DefaultUIIdentifierFormatter(),
|
||||||
|
new DefaultUIResourcePathResolver(),
|
||||||
|
new DefaultUIScriptCodeEmitter(),
|
||||||
|
new DefaultUIScriptFileWriter())
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal DefaultUIGeneratorRuleHelper(
|
||||||
|
IUIIdentifierFormatter identifierFormatter,
|
||||||
|
IUIResourcePathResolver resourcePathResolver,
|
||||||
|
IUIScriptCodeEmitter scriptCodeEmitter,
|
||||||
|
IUIScriptFileWriter scriptFileWriter)
|
||||||
|
{
|
||||||
|
_identifierFormatter = identifierFormatter ?? throw new ArgumentNullException(nameof(identifierFormatter));
|
||||||
|
_resourcePathResolver = resourcePathResolver ?? throw new ArgumentNullException(nameof(resourcePathResolver));
|
||||||
|
_scriptCodeEmitter = scriptCodeEmitter ?? throw new ArgumentNullException(nameof(scriptCodeEmitter));
|
||||||
|
_scriptFileWriter = scriptFileWriter ?? throw new ArgumentNullException(nameof(scriptFileWriter));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPrivateComponentByNameRule(string regexName, string componentName, EBindType bindType)
|
||||||
|
=> _identifierFormatter.GetPrivateComponentName(regexName, componentName, bindType);
|
||||||
|
|
||||||
public string GetPublicComponentByNameRule(string variableName)
|
public string GetPublicComponentByNameRule(string variableName)
|
||||||
{
|
=> _identifierFormatter.GetPublicComponentName(variableName);
|
||||||
if (string.IsNullOrEmpty(variableName)) return variableName;
|
|
||||||
return variableName.Length > 1 ? variableName.Substring(1) : variableName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetClassGenerateName(GameObject targetObject, UIScriptGenerateData scriptGenerateData)
|
public string GetClassGenerateName(GameObject targetObject, UIScriptGenerateData scriptGenerateData)
|
||||||
{
|
=> _identifierFormatter.GetClassName(targetObject);
|
||||||
var config = UIGenerateConfiguration.Instance.UIGenerateCommonData;
|
|
||||||
var prefix = config.GeneratePrefix ?? "ui";
|
string IContextualUIGeneratorRuleHelper.GetClassGenerateName(UIGenerationContext context)
|
||||||
return $"{prefix}_{targetObject.name}";
|
=> _identifierFormatter.GetClassName(context.TargetObject);
|
||||||
}
|
|
||||||
|
|
||||||
public string GetUIResourceSavePath(GameObject targetObject, UIScriptGenerateData scriptGenerateData)
|
public string GetUIResourceSavePath(GameObject targetObject, UIScriptGenerateData scriptGenerateData)
|
||||||
{
|
=> _resourcePathResolver.GetResourcePath(targetObject, scriptGenerateData);
|
||||||
if (targetObject == null) return $"\"{nameof(targetObject)}\"";
|
|
||||||
|
|
||||||
var defaultPath = targetObject.name;
|
string IContextualUIGeneratorRuleHelper.GetUIResourceSavePath(UIGenerationContext context)
|
||||||
var assetPath = UIGenerateQuick.GetPrefabAssetPath(targetObject);
|
=> _resourcePathResolver.GetResourcePath(context.TargetObject, context.ScriptGenerateData);
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetResourcesPath(string assetPath, UIScriptGenerateData scriptGenerateData, string defaultPath)
|
|
||||||
{
|
|
||||||
var resourcesRoot = scriptGenerateData.UIPrefabRootPath;
|
|
||||||
var relPath = GetResourcesRelativePath(assetPath, resourcesRoot);
|
|
||||||
|
|
||||||
if (relPath == null)
|
|
||||||
{
|
|
||||||
Debug.LogWarning($"[UI生成] 资源 {assetPath} 不在配置的 Resources 根目录下: {resourcesRoot}");
|
|
||||||
return defaultPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return relPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetAssetBundlePath(string assetPath, UIScriptGenerateData scriptGenerateData, string defaultPath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var defaultPackage = YooAsset.Editor.AssetBundleCollectorSettingData.Setting.GetPackage("DefaultPackage");
|
|
||||||
if (defaultPackage?.EnableAddressable == true)
|
|
||||||
return defaultPath;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// 忽略异常,继续处理
|
|
||||||
}
|
|
||||||
|
|
||||||
var bundleRoot = scriptGenerateData.UIPrefabRootPath;
|
|
||||||
if (!assetPath.StartsWith(bundleRoot, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
Debug.LogWarning($"[UI生成] 资源 {assetPath} 不在配置的 AssetBundle 根目录下: {bundleRoot}");
|
|
||||||
return defaultPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Path.ChangeExtension(assetPath, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
var relPath = assetPath.Substring(resourcesRoot.Length).TrimStart('/');
|
|
||||||
return Path.ChangeExtension(relPath, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteUIScriptContent(GameObject targetObject, string className, string scriptContent, UIScriptGenerateData scriptGenerateData)
|
public void WriteUIScriptContent(GameObject targetObject, string className, string scriptContent, UIScriptGenerateData scriptGenerateData)
|
||||||
{
|
=> _scriptFileWriter.Write(targetObject, className, scriptContent, scriptGenerateData);
|
||||||
if (string.IsNullOrEmpty(className)) throw new ArgumentNullException(nameof(className));
|
|
||||||
if (scriptContent == null) throw new ArgumentNullException(nameof(scriptContent));
|
|
||||||
if (scriptGenerateData == null) throw new ArgumentNullException(nameof(scriptGenerateData));
|
|
||||||
|
|
||||||
var scriptFolderPath = scriptGenerateData.GenerateHolderCodePath;
|
void IContextualUIGeneratorRuleHelper.WriteUIScriptContent(UIGenerationContext context, string scriptContent)
|
||||||
var scriptFilePath = Path.Combine(scriptFolderPath, $"{className}.cs");
|
=> _scriptFileWriter.Write(context.TargetObject, context.ClassName, scriptContent, context.ScriptGenerateData);
|
||||||
|
|
||||||
Directory.CreateDirectory(scriptFolderPath);
|
|
||||||
|
|
||||||
scriptContent = scriptContent.Replace("#Controller#", string.Empty);
|
|
||||||
|
|
||||||
if (File.Exists(scriptFilePath) && IsContentUnchanged(scriptFilePath, scriptContent))
|
|
||||||
{
|
|
||||||
UIScriptGeneratorHelper.BindUIScript();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File.WriteAllText(scriptFilePath, scriptContent, Encoding.UTF8);
|
|
||||||
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)
|
public bool CheckCanGenerate(GameObject targetObject, UIScriptGenerateData scriptGenerateData)
|
||||||
{
|
=> _resourcePathResolver.CanGenerate(targetObject, scriptGenerateData);
|
||||||
if (targetObject == null || scriptGenerateData == null) return false;
|
|
||||||
|
|
||||||
var assetPath = UIGenerateQuick.GetPrefabAssetPath(targetObject);
|
bool IContextualUIGeneratorRuleHelper.CheckCanGenerate(UIGenerationContext context)
|
||||||
if (string.IsNullOrEmpty(assetPath) || !assetPath.StartsWith("Assets/", StringComparison.Ordinal))
|
=> _resourcePathResolver.CanGenerate(context.TargetObject, context.ScriptGenerateData);
|
||||||
return false;
|
|
||||||
|
|
||||||
assetPath = assetPath.Replace('\\', '/');
|
|
||||||
var isValidPath = assetPath.StartsWith(scriptGenerateData.UIPrefabRootPath, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
if (!isValidPath)
|
|
||||||
{
|
|
||||||
Debug.LogWarning($"UI存储位置与配置生成规则不符合 请检查对应配置的UIPrefabRootPath\n[AssetPath]{assetPath}\n[ConfigPath]{scriptGenerateData.UIPrefabRootPath}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return isValidPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetReferenceNamespace(List<UIBindData> uiBindDatas)
|
public string GetReferenceNamespace(List<UIBindData> uiBindDatas)
|
||||||
{
|
=> _scriptCodeEmitter.GetReferenceNamespaces(uiBindDatas);
|
||||||
var namespaceSet = new HashSet<string>(StringComparer.Ordinal) { "UnityEngine" };
|
|
||||||
|
|
||||||
if (uiBindDatas?.Any(d => d.BindType == EBindType.ListCom) == true)
|
string IContextualUIGeneratorRuleHelper.GetReferenceNamespace(UIGenerationContext context)
|
||||||
{
|
=> _scriptCodeEmitter.GetReferenceNamespaces(context.BindDatas?.ToList());
|
||||||
namespaceSet.Add("System.Collections.Generic");
|
|
||||||
}
|
|
||||||
|
|
||||||
uiBindDatas?
|
|
||||||
.Where(bindData => bindData?.Objs?.FirstOrDefault() != null)
|
|
||||||
.Select(bindData => bindData.GetFirstOrDefaultType().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)
|
public string GetVariableContent(List<UIBindData> uiBindDatas)
|
||||||
{
|
=> _scriptCodeEmitter.GetVariableContent(uiBindDatas, GetPublicComponentByNameRule);
|
||||||
if (uiBindDatas == null || uiBindDatas.Count == 0) return string.Empty;
|
|
||||||
|
|
||||||
var variableBuilder = new StringBuilder();
|
string IContextualUIGeneratorRuleHelper.GetVariableContent(UIGenerationContext context)
|
||||||
var variables = uiBindDatas
|
=> _scriptCodeEmitter.GetVariableContent(context.BindDatas?.ToList(), GetPublicComponentByNameRule);
|
||||||
.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.GetFirstOrDefaultType();
|
|
||||||
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.Objs?.Count ?? 0);
|
|
||||||
declaration.AppendLine($"\t\tprivate {typeName}[] {variableName} = new {typeName}[{count}];");
|
|
||||||
declaration.Append($"\t\tpublic {typeName}[] {publicName} => {variableName};");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return declaration.ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
Editor/UI/Helper/UIGenerationContext.cs
Normal file
45
Editor/UI/Helper/UIGenerationContext.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.Editor
|
||||||
|
{
|
||||||
|
internal sealed class UIGenerationContext
|
||||||
|
{
|
||||||
|
public UIGenerationContext(GameObject targetObject, UIScriptGenerateData scriptGenerateData, IReadOnlyList<UIBindData> bindDatas)
|
||||||
|
{
|
||||||
|
TargetObject = targetObject;
|
||||||
|
ScriptGenerateData = scriptGenerateData;
|
||||||
|
BindDatas = bindDatas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameObject TargetObject { get; }
|
||||||
|
|
||||||
|
public UIScriptGenerateData ScriptGenerateData { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<UIBindData> BindDatas { get; }
|
||||||
|
|
||||||
|
public string AssetPath { get; set; }
|
||||||
|
|
||||||
|
public string ClassName { get; set; }
|
||||||
|
|
||||||
|
public string FullTypeName =>
|
||||||
|
string.IsNullOrWhiteSpace(ScriptGenerateData?.NameSpace) ? ClassName : $"{ScriptGenerateData.NameSpace}.{ClassName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly struct UIGenerationValidationResult
|
||||||
|
{
|
||||||
|
private UIGenerationValidationResult(bool isValid, string message)
|
||||||
|
{
|
||||||
|
IsValid = isValid;
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsValid { get; }
|
||||||
|
|
||||||
|
public string Message { get; }
|
||||||
|
|
||||||
|
public static UIGenerationValidationResult Success() => new(true, string.Empty);
|
||||||
|
|
||||||
|
public static UIGenerationValidationResult Fail(string message) => new(false, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Editor/UI/Helper/UIGenerationContext.cs.meta
Normal file
11
Editor/UI/Helper/UIGenerationContext.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f8b0f70017de25a4b950d96af16e81ca
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
338
Editor/UI/Helper/UIGeneratorRuleServices.cs
Normal file
338
Editor/UI/Helper/UIGeneratorRuleServices.cs
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using AlicizaX.UI.Runtime;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.Editor
|
||||||
|
{
|
||||||
|
internal interface IUIIdentifierFormatter
|
||||||
|
{
|
||||||
|
string GetPrivateComponentName(string regexName, string componentName, EBindType bindType);
|
||||||
|
|
||||||
|
string GetPublicComponentName(string variableName);
|
||||||
|
|
||||||
|
string GetClassName(GameObject targetObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface IUIResourcePathResolver
|
||||||
|
{
|
||||||
|
string GetResourcePath(GameObject targetObject, UIScriptGenerateData scriptGenerateData);
|
||||||
|
|
||||||
|
bool CanGenerate(GameObject targetObject, UIScriptGenerateData scriptGenerateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface IUIScriptCodeEmitter
|
||||||
|
{
|
||||||
|
string GetReferenceNamespaces(List<UIBindData> uiBindDatas);
|
||||||
|
|
||||||
|
string GetVariableContent(List<UIBindData> uiBindDatas, Func<string, string> publicNameFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface IUIScriptFileWriter
|
||||||
|
{
|
||||||
|
void Write(GameObject targetObject, string className, string scriptContent, UIScriptGenerateData scriptGenerateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DefaultUIIdentifierFormatter : IUIIdentifierFormatter
|
||||||
|
{
|
||||||
|
private static readonly HashSet<string> CSharpKeywords = new HashSet<string>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
"abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked",
|
||||||
|
"class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else",
|
||||||
|
"enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for",
|
||||||
|
"foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock",
|
||||||
|
"long", "namespace", "new", "null", "object", "operator", "out", "override", "params",
|
||||||
|
"private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short",
|
||||||
|
"sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true",
|
||||||
|
"try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual",
|
||||||
|
"void", "volatile", "while"
|
||||||
|
};
|
||||||
|
|
||||||
|
public string GetPrivateComponentName(string regexName, string componentName, EBindType bindType)
|
||||||
|
{
|
||||||
|
var endPrefix = bindType == EBindType.ListCom ? "List" : string.Empty;
|
||||||
|
var common = UIGenerateConfiguration.Instance.UIGenerateCommonData;
|
||||||
|
var endNameIndex = componentName.IndexOf(common.ComCheckEndName, StringComparison.Ordinal);
|
||||||
|
var componentSuffix = endNameIndex >= 0 ? componentName.Substring(endNameIndex + 1) : componentName;
|
||||||
|
var fieldName = $"m{NormalizeIdentifier(regexName)}{NormalizeIdentifier(componentSuffix)}{endPrefix}";
|
||||||
|
return MakeSafeIdentifier(string.IsNullOrWhiteSpace(fieldName) ? "mComponent" : fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPublicComponentName(string variableName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(variableName))
|
||||||
|
{
|
||||||
|
return variableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
var publicName = variableName.StartsWith("m", StringComparison.Ordinal) && variableName.Length > 1
|
||||||
|
? variableName.Substring(1)
|
||||||
|
: variableName;
|
||||||
|
|
||||||
|
publicName = NormalizeIdentifier(publicName);
|
||||||
|
return MakeSafeIdentifier(string.IsNullOrEmpty(publicName) ? "Component" : publicName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetClassName(GameObject targetObject)
|
||||||
|
{
|
||||||
|
var config = UIGenerateConfiguration.Instance.UIGenerateCommonData;
|
||||||
|
var prefix = NormalizeIdentifier(config.GeneratePrefix ?? "ui");
|
||||||
|
var objectName = NormalizeIdentifier(targetObject.name);
|
||||||
|
var className = $"{prefix}_{objectName}".Trim('_');
|
||||||
|
return MakeSafeIdentifier(string.IsNullOrEmpty(className) ? "ui_View" : className);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeIdentifier(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = Regex.Split(value, "[^A-Za-z0-9_]+")
|
||||||
|
.Where(part => !string.IsNullOrWhiteSpace(part))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (parts.Length == 0)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
foreach (var part in parts)
|
||||||
|
{
|
||||||
|
builder.Append(part[0]);
|
||||||
|
if (part.Length > 1)
|
||||||
|
{
|
||||||
|
builder.Append(part.Substring(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string MakeSafeIdentifier(string identifier)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(identifier))
|
||||||
|
{
|
||||||
|
return "_";
|
||||||
|
}
|
||||||
|
|
||||||
|
var safeIdentifier = identifier;
|
||||||
|
if (char.IsDigit(safeIdentifier[0]))
|
||||||
|
{
|
||||||
|
safeIdentifier = "_" + safeIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CSharpKeywords.Contains(safeIdentifier))
|
||||||
|
{
|
||||||
|
safeIdentifier += "_";
|
||||||
|
}
|
||||||
|
|
||||||
|
return safeIdentifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DefaultUIResourcePathResolver : IUIResourcePathResolver
|
||||||
|
{
|
||||||
|
public string GetResourcePath(GameObject targetObject, UIScriptGenerateData scriptGenerateData)
|
||||||
|
{
|
||||||
|
if (targetObject == null)
|
||||||
|
{
|
||||||
|
return $"\"{nameof(targetObject)}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultPath = targetObject.name;
|
||||||
|
var 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanGenerate(GameObject targetObject, UIScriptGenerateData scriptGenerateData)
|
||||||
|
{
|
||||||
|
if (targetObject == null || scriptGenerateData == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var assetPath = UIGenerateQuick.GetPrefabAssetPath(targetObject);
|
||||||
|
if (string.IsNullOrEmpty(assetPath) || !assetPath.StartsWith("Assets/", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
assetPath = assetPath.Replace('\\', '/');
|
||||||
|
var isValidPath = assetPath.StartsWith(scriptGenerateData.UIPrefabRootPath, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (!isValidPath)
|
||||||
|
{
|
||||||
|
Debug.LogWarning(
|
||||||
|
$"UI asset path does not match UIGenerateConfiguration.UIPrefabRootPath.\n[AssetPath]{assetPath}\n[ConfigPath]{scriptGenerateData.UIPrefabRootPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValidPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetResourcesPath(string assetPath, UIScriptGenerateData scriptGenerateData, string defaultPath)
|
||||||
|
{
|
||||||
|
var resourcesRoot = scriptGenerateData.UIPrefabRootPath;
|
||||||
|
var relPath = GetResourcesRelativePath(assetPath, resourcesRoot);
|
||||||
|
if (relPath == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[UI Generate] Resource {assetPath} is not under configured Resources root: {resourcesRoot}");
|
||||||
|
return defaultPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return relPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetAssetBundlePath(string assetPath, UIScriptGenerateData scriptGenerateData, string defaultPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var defaultPackage = YooAsset.Editor.AssetBundleCollectorSettingData.Setting.GetPackage("DefaultPackage");
|
||||||
|
if (defaultPackage?.EnableAddressable == true)
|
||||||
|
{
|
||||||
|
return defaultPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
var bundleRoot = scriptGenerateData.UIPrefabRootPath;
|
||||||
|
if (!assetPath.StartsWith(bundleRoot, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[UI Generate] Resource {assetPath} is not under configured AssetBundle root: {bundleRoot}");
|
||||||
|
return defaultPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Path.ChangeExtension(assetPath, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relPath = assetPath.Substring(resourcesRoot.Length).TrimStart('/');
|
||||||
|
return Path.ChangeExtension(relPath, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DefaultUIScriptCodeEmitter : IUIScriptCodeEmitter
|
||||||
|
{
|
||||||
|
public string GetReferenceNamespaces(List<UIBindData> uiBindDatas)
|
||||||
|
{
|
||||||
|
var namespaceSet = new HashSet<string>(StringComparer.Ordinal) { "UnityEngine" };
|
||||||
|
|
||||||
|
uiBindDatas?
|
||||||
|
.Where(bindData => bindData?.GetFirstOrDefaultType() != null)
|
||||||
|
.Select(bindData => bindData.GetFirstOrDefaultType()?.Namespace)
|
||||||
|
.Where(ns => !string.IsNullOrEmpty(ns))
|
||||||
|
.ToList()
|
||||||
|
.ForEach(ns => namespaceSet.Add(ns));
|
||||||
|
|
||||||
|
return string.Join(Environment.NewLine, namespaceSet.OrderBy(ns => ns, StringComparer.Ordinal).Select(ns => $"using {ns};"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetVariableContent(List<UIBindData> uiBindDatas, Func<string, string> publicNameFactory)
|
||||||
|
{
|
||||||
|
if (uiBindDatas == null || uiBindDatas.Count == 0)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var declarations = uiBindDatas
|
||||||
|
.Where(bindData => bindData != null && !string.IsNullOrEmpty(bindData.Name))
|
||||||
|
.OrderBy(bindData => bindData.Name, StringComparer.Ordinal)
|
||||||
|
.Select(bindData => GenerateVariableDeclaration(bindData, publicNameFactory))
|
||||||
|
.Where(content => !string.IsNullOrEmpty(content));
|
||||||
|
|
||||||
|
return string.Join(Environment.NewLine + Environment.NewLine, declarations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateVariableDeclaration(UIBindData bindData, Func<string, string> publicNameFactory)
|
||||||
|
{
|
||||||
|
var variableName = bindData.Name;
|
||||||
|
var publicName = publicNameFactory(variableName);
|
||||||
|
var firstType = bindData.GetFirstOrDefaultType();
|
||||||
|
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.Objs?.Count ?? 0);
|
||||||
|
declaration.AppendLine($"\t\tprivate {typeName}[] {variableName} = new {typeName}[{count}];");
|
||||||
|
declaration.Append($"\t\tpublic {typeName}[] {publicName} => {variableName};");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return declaration.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DefaultUIScriptFileWriter : IUIScriptFileWriter
|
||||||
|
{
|
||||||
|
public void Write(GameObject targetObject, string className, string scriptContent, UIScriptGenerateData scriptGenerateData)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(className)) throw new ArgumentNullException(nameof(className));
|
||||||
|
if (scriptContent == null) throw new ArgumentNullException(nameof(scriptContent));
|
||||||
|
if (scriptGenerateData == null) throw new ArgumentNullException(nameof(scriptGenerateData));
|
||||||
|
|
||||||
|
var scriptFolderPath = scriptGenerateData.GenerateHolderCodePath;
|
||||||
|
var scriptFilePath = Path.Combine(scriptFolderPath, $"{className}.cs");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(scriptFolderPath);
|
||||||
|
scriptContent = scriptContent.Replace("#Controller#", string.Empty);
|
||||||
|
|
||||||
|
if (File.Exists(scriptFilePath) && IsContentUnchanged(scriptFilePath, scriptContent))
|
||||||
|
{
|
||||||
|
UIScriptGeneratorHelper.BindUIScript();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(scriptFilePath, scriptContent, Encoding.UTF8);
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsContentUnchanged(string filePath, string newContent)
|
||||||
|
{
|
||||||
|
var oldText = File.ReadAllText(filePath, Encoding.UTF8);
|
||||||
|
return oldText.Equals(newContent, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Editor/UI/Helper/UIGeneratorRuleServices.cs.meta
Normal file
11
Editor/UI/Helper/UIGeneratorRuleServices.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c37ea53228fd9854a9e7a04e26b4b873
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@ -21,59 +21,59 @@ namespace AlicizaX.UI.Editor
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class UIBindData
|
public class UIBindData
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
public UIBindData(string name, List<GameObject> objs, Type componentType = null, EBindType bindType = EBindType.None)
|
||||||
|
|
||||||
public List<GameObject> Objs { get; set; }
|
|
||||||
public EBindType BindType { get; }
|
|
||||||
public bool IsGameObject => nameof(GameObject).Equals(TypeName);
|
|
||||||
|
|
||||||
public string TypeName = string.Empty;
|
|
||||||
|
|
||||||
public Type GetFirstOrDefaultType()
|
|
||||||
{
|
|
||||||
if (IsGameObject)
|
|
||||||
{
|
|
||||||
return typeof(GameObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return Objs.FirstOrDefault()?.GetComponent(TypeName).GetType();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.Log(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UIBindData(string name, List<GameObject> objs, string typeName = "", EBindType bindType = EBindType.None)
|
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Objs = objs ?? new List<GameObject>();
|
Objs = objs ?? new List<GameObject>();
|
||||||
BindType = bindType;
|
BindType = bindType;
|
||||||
TypeName = typeName;
|
ComponentType = componentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UIBindData(string name, GameObject obj, string typeName = "", EBindType bindType = EBindType.None)
|
public UIBindData(string name, GameObject obj, Type componentType = null, EBindType bindType = EBindType.None)
|
||||||
: this(name, new List<GameObject> { obj }, typeName, bindType)
|
: 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
|
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 UIGenerateConfiguration _uiGenerateConfiguration;
|
||||||
private static IUIGeneratorRuleHelper _uiGeneratorRuleHelper;
|
private static IUIGeneratorRuleHelper _uiGeneratorRuleHelper;
|
||||||
private static readonly List<UIBindData> _uiBindDatas = new List<UIBindData>();
|
private static readonly List<UIBindData> _uiBindDatas = new List<UIBindData>();
|
||||||
private static readonly HashSet<string> _arrayComponents = new HashSet<string>(StringComparer.Ordinal);
|
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 IUIGeneratorRuleHelper UIGeneratorRuleHelper
|
private static IUIGeneratorRuleHelper UIGeneratorRuleHelper
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_uiGeneratorRuleHelper == null || (_uiGeneratorRuleHelper != null && _uiGeneratorRuleHelper.GetType().FullName != UIConfiguration.UIScriptGeneratorRuleHelper))
|
var configuredHelperTypeName = string.IsNullOrWhiteSpace(UIConfiguration.UIScriptGeneratorRuleHelper)
|
||||||
|
? typeof(DefaultUIGeneratorRuleHelper).FullName
|
||||||
|
: UIConfiguration.UIScriptGeneratorRuleHelper;
|
||||||
|
|
||||||
|
if (_uiGeneratorRuleHelper == null || _uiGeneratorRuleHelper.GetType().FullName != configuredHelperTypeName)
|
||||||
{
|
{
|
||||||
InitializeRuleHelper();
|
InitializeRuleHelper();
|
||||||
}
|
}
|
||||||
@ -82,6 +82,9 @@ namespace AlicizaX.UI.Editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IContextualUIGeneratorRuleHelper ContextualUIGeneratorRuleHelper =>
|
||||||
|
UIGeneratorRuleHelper as IContextualUIGeneratorRuleHelper;
|
||||||
|
|
||||||
private static UIGenerateConfiguration UIConfiguration =>
|
private static UIGenerateConfiguration UIConfiguration =>
|
||||||
_uiGenerateConfiguration ??= UIGenerateConfiguration.Instance;
|
_uiGenerateConfiguration ??= UIGenerateConfiguration.Instance;
|
||||||
|
|
||||||
@ -90,8 +93,7 @@ namespace AlicizaX.UI.Editor
|
|||||||
var ruleHelperTypeName = UIConfiguration.UIScriptGeneratorRuleHelper;
|
var ruleHelperTypeName = UIConfiguration.UIScriptGeneratorRuleHelper;
|
||||||
if (string.IsNullOrWhiteSpace(ruleHelperTypeName))
|
if (string.IsNullOrWhiteSpace(ruleHelperTypeName))
|
||||||
{
|
{
|
||||||
Debug.LogError("UIScriptGeneratorHelper: UIScriptGeneratorRuleHelper not configured.");
|
ruleHelperTypeName = typeof(DefaultUIGeneratorRuleHelper).FullName;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ruleHelperType = AlicizaX.Utility.Assembly.GetType(ruleHelperTypeName);
|
var ruleHelperType = AlicizaX.Utility.Assembly.GetType(ruleHelperTypeName);
|
||||||
@ -110,14 +112,40 @@ namespace AlicizaX.UI.Editor
|
|||||||
return _uiGeneratorRuleHelper;
|
return _uiGeneratorRuleHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetUIElementComponentType(string uiName)
|
private static Type ResolveUIElementComponentType(string uiName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(uiName)) return string.Empty;
|
if (string.IsNullOrEmpty(uiName))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return UIConfiguration.UIElementRegexConfigs
|
if (_componentTypeCache.TryGetValue(uiName, out var componentType))
|
||||||
|
{
|
||||||
|
return componentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
var componentTypeName = UIConfiguration.UIElementRegexConfigs
|
||||||
?.Where(pair => !string.IsNullOrEmpty(pair?.uiElementRegex))
|
?.Where(pair => !string.IsNullOrEmpty(pair?.uiElementRegex))
|
||||||
.FirstOrDefault(pair => uiName.StartsWith(pair.uiElementRegex, StringComparison.Ordinal))
|
.FirstOrDefault(pair => uiName.StartsWith(pair.uiElementRegex, StringComparison.Ordinal))
|
||||||
?.componentType ?? string.Empty;
|
?.componentType;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(componentTypeName))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentType = componentTypeName == nameof(GameObject)
|
||||||
|
? typeof(GameObject)
|
||||||
|
: AlicizaX.Utility.Assembly.GetType(componentTypeName);
|
||||||
|
|
||||||
|
if (componentType == null)
|
||||||
|
{
|
||||||
|
Debug.LogError($"UIScriptGeneratorHelper: Could not resolve component type '{componentTypeName}' for '{uiName}'.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_componentTypeCache[uiName] = componentType;
|
||||||
|
return componentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string[] SplitComponentName(string name)
|
private static string[] SplitComponentName(string name)
|
||||||
@ -181,21 +209,22 @@ namespace AlicizaX.UI.Editor
|
|||||||
private static void ProcessArrayComponent(Transform child, Transform root)
|
private static void ProcessArrayComponent(Transform child, Transform root)
|
||||||
{
|
{
|
||||||
var splitCode = UIConfiguration.UIGenerateCommonData.ArrayComSplitName;
|
var splitCode = UIConfiguration.UIGenerateCommonData.ArrayComSplitName;
|
||||||
var firstIndex = child.name.IndexOf(splitCode, StringComparison.Ordinal);
|
if (!TryGetArrayComponentGroupName(child.name, splitCode, out var groupName))
|
||||||
var lastIndex = child.name.LastIndexOf(splitCode, StringComparison.Ordinal);
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (firstIndex < 0 || lastIndex <= firstIndex) return;
|
var groupKey = $"{root.GetInstanceID()}::{groupName}";
|
||||||
|
if (!_arrayComponents.Add(groupKey))
|
||||||
var text = child.name.Substring(firstIndex + splitCode.Length, lastIndex - (firstIndex + splitCode.Length));
|
{
|
||||||
if (string.IsNullOrEmpty(text) || _arrayComponents.Contains(text)) return;
|
return;
|
||||||
|
}
|
||||||
_arrayComponents.Add(text);
|
|
||||||
|
|
||||||
var arrayComponents = root.Cast<Transform>()
|
var arrayComponents = root.Cast<Transform>()
|
||||||
.Where(sibling => sibling.name.Contains(text, StringComparison.Ordinal))
|
.Where(sibling => IsArrayGroupMember(sibling.name, splitCode, groupName))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
CollectArrayComponent(arrayComponents, text);
|
CollectArrayComponent(arrayComponents, groupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CollectComponent(Transform node)
|
private static void CollectComponent(Transform node)
|
||||||
@ -207,19 +236,16 @@ namespace AlicizaX.UI.Editor
|
|||||||
|
|
||||||
foreach (var com in componentArray.Where(com => !string.IsNullOrEmpty(com)))
|
foreach (var com in componentArray.Where(com => !string.IsNullOrEmpty(com)))
|
||||||
{
|
{
|
||||||
var typeName = GetUIElementComponentType(com);
|
var componentType = ResolveUIElementComponentType(com);
|
||||||
if (string.IsNullOrEmpty(typeName)) continue;
|
if (componentType == null)
|
||||||
|
|
||||||
|
|
||||||
bool isGameObject = typeName.Equals(nameof(GameObject));
|
|
||||||
if (!isGameObject)
|
|
||||||
{
|
{
|
||||||
var component = node.GetComponent(typeName);
|
continue;
|
||||||
if (component == null)
|
}
|
||||||
{
|
|
||||||
Debug.LogError($"{node.name} does not have component of type {typeName}");
|
if (componentType != typeof(GameObject) && node.GetComponent(componentType) == null)
|
||||||
continue;
|
{
|
||||||
}
|
Debug.LogError($"{node.name} does not have component of type {componentType.FullName}");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var keyName = UIGeneratorRuleHelper.GetPrivateComponentByNameRule(com, node.name, EBindType.None);
|
var keyName = UIGeneratorRuleHelper.GetPrivateComponentByNameRule(com, node.name, EBindType.None);
|
||||||
@ -229,7 +255,7 @@ namespace AlicizaX.UI.Editor
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_uiBindDatas.Add(new UIBindData(keyName, node.gameObject, typeName));
|
_uiBindDatas.Add(new UIBindData(keyName, node.gameObject, componentType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,7 +285,7 @@ namespace AlicizaX.UI.Editor
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_uiBindDatas.Add(new UIBindData(keyName, component.gameObject, component.GetType().FullName, EBindType.Widget));
|
_uiBindDatas.Add(new UIBindData(keyName, component.gameObject, component.GetType(), EBindType.Widget));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CollectArrayComponent(List<Transform> arrayNode, string nodeName)
|
private static void CollectArrayComponent(List<Transform> arrayNode, string nodeName)
|
||||||
@ -292,10 +318,10 @@ namespace AlicizaX.UI.Editor
|
|||||||
|
|
||||||
private static List<UIBindData> CreateTempBindDatas(string[] componentArray, string nodeName)
|
private static List<UIBindData> CreateTempBindDatas(string[] componentArray, string nodeName)
|
||||||
{
|
{
|
||||||
return componentArray.Select((com, index) =>
|
return componentArray.Select(com =>
|
||||||
{
|
{
|
||||||
var keyName = UIGeneratorRuleHelper.GetPrivateComponentByNameRule(com, nodeName, EBindType.ListCom);
|
var keyName = UIGeneratorRuleHelper.GetPrivateComponentByNameRule(com, nodeName, EBindType.ListCom);
|
||||||
return new UIBindData(keyName, new List<GameObject>(), com, EBindType.ListCom);
|
return new UIBindData(keyName, new List<GameObject>(), null, EBindType.ListCom);
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,13 +332,13 @@ namespace AlicizaX.UI.Editor
|
|||||||
var com = componentArray[index];
|
var com = componentArray[index];
|
||||||
if (string.IsNullOrEmpty(com)) continue;
|
if (string.IsNullOrEmpty(com)) continue;
|
||||||
|
|
||||||
var typeName = GetUIElementComponentType(com);
|
var componentType = ResolveUIElementComponentType(com);
|
||||||
if (string.IsNullOrEmpty(typeName)) continue;
|
if (componentType == null) continue;
|
||||||
tempBindDatas[index].TypeName = typeName;
|
tempBindDatas[index].SetComponentType(componentType);
|
||||||
foreach (var node in orderedNodes)
|
foreach (var node in orderedNodes)
|
||||||
{
|
{
|
||||||
var isGameObject = typeName.Equals(nameof(GameObject));
|
var isGameObject = componentType == typeof(GameObject);
|
||||||
var component = isGameObject ? null : node.GetComponent(typeName);
|
var component = isGameObject ? null : node.GetComponent(componentType);
|
||||||
|
|
||||||
if (component != null || isGameObject)
|
if (component != null || isGameObject)
|
||||||
{
|
{
|
||||||
@ -320,12 +346,38 @@ namespace AlicizaX.UI.Editor
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogError($"{node.name} does not have component of type {typeName}");
|
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)
|
private static int? ExtractArrayIndex(string nodeName, string splitCode)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(nodeName) || string.IsNullOrEmpty(splitCode)) return null;
|
if (string.IsNullOrEmpty(nodeName) || string.IsNullOrEmpty(splitCode)) return null;
|
||||||
@ -344,203 +396,322 @@ namespace AlicizaX.UI.Editor
|
|||||||
|
|
||||||
if (!PrefabChecker.IsPrefabAsset(targetObject))
|
if (!PrefabChecker.IsPrefabAsset(targetObject))
|
||||||
{
|
{
|
||||||
Debug.LogWarning("请将UI界面保存为对应的目录Prefab 在进行代码生成");
|
Debug.LogWarning("Please save the UI as a prefab asset before generating bindings.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ruleHelper = UIGeneratorRuleHelper;
|
var ruleHelper = UIGeneratorRuleHelper;
|
||||||
if (ruleHelper == null || !ruleHelper.CheckCanGenerate(targetObject, scriptGenerateData))
|
if (ruleHelper == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
InitializeGenerationContext(targetObject);
|
InitializeGenerationContext(targetObject);
|
||||||
|
CollectBindData(targetObject.transform);
|
||||||
|
|
||||||
var className = ruleHelper.GetClassGenerateName(targetObject, scriptGenerateData);
|
var generationContext = new UIGenerationContext(targetObject, scriptGenerateData, _uiBindDatas)
|
||||||
if (string.IsNullOrEmpty(className))
|
|
||||||
{
|
{
|
||||||
Debug.LogError("Generated className is empty.");
|
AssetPath = UIGenerateQuick.GetPrefabAssetPath(targetObject),
|
||||||
|
ClassName = GetClassGenerateName(ruleHelper, targetObject, scriptGenerateData)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!CheckCanGenerate(ruleHelper, generationContext))
|
||||||
|
{
|
||||||
|
CleanupContext();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectBindData(targetObject.transform);
|
var validationResult = ValidateGeneration(generationContext);
|
||||||
GenerateScript(targetObject, className, scriptGenerateData, ruleHelper);
|
if (!validationResult.IsValid)
|
||||||
|
{
|
||||||
|
Debug.LogError(validationResult.Message);
|
||||||
|
CleanupContext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GenerateScript(generationContext, ruleHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InitializeGenerationContext(GameObject targetObject)
|
private static void InitializeGenerationContext(GameObject targetObject)
|
||||||
{
|
{
|
||||||
EditorPrefs.SetInt("InstanceId", targetObject.GetInstanceID());
|
EditorPrefs.SetInt(GenerateInstanceIdKey, targetObject.GetInstanceID());
|
||||||
|
var assetPath = UIGenerateQuick.GetPrefabAssetPath(targetObject);
|
||||||
|
if (!string.IsNullOrEmpty(assetPath))
|
||||||
|
{
|
||||||
|
EditorPrefs.SetString(GenerateAssetPathKey, assetPath);
|
||||||
|
}
|
||||||
|
|
||||||
_uiBindDatas.Clear();
|
_uiBindDatas.Clear();
|
||||||
_arrayComponents.Clear();
|
_arrayComponents.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GenerateScript(GameObject targetObject, string className, UIScriptGenerateData scriptGenerateData, IUIGeneratorRuleHelper ruleHelper)
|
private static UIGenerationValidationResult ValidateGeneration(UIGenerationContext context)
|
||||||
{
|
{
|
||||||
var templateText = File.ReadAllText(UIGlobalPath.TemplatePath);
|
if (context.TargetObject == null)
|
||||||
var processedText = ProcessTemplateText(targetObject, templateText, className, scriptGenerateData, ruleHelper);
|
{
|
||||||
EditorPrefs.SetString("Generate", className);
|
return UIGenerationValidationResult.Fail("UI generation target is null.");
|
||||||
ruleHelper.WriteUIScriptContent(targetObject, className, processedText, scriptGenerateData);
|
}
|
||||||
|
|
||||||
|
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 string ProcessTemplateText(GameObject targetObject, string templateText, string className, UIScriptGenerateData scriptGenerateData, IUIGeneratorRuleHelper ruleHelper)
|
private static void GenerateScript(UIGenerationContext context, IUIGeneratorRuleHelper ruleHelper)
|
||||||
{
|
{
|
||||||
|
var templateText = File.ReadAllText(UIGlobalPath.TemplatePath);
|
||||||
|
var processedText = ProcessTemplateText(context, templateText, ruleHelper);
|
||||||
|
EditorPrefs.SetString(GenerateTypeNameKey, context.FullTypeName);
|
||||||
|
if (ContextualUIGeneratorRuleHelper != null)
|
||||||
|
{
|
||||||
|
ContextualUIGeneratorRuleHelper.WriteUIScriptContent(context, processedText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleHelper.WriteUIScriptContent(context.TargetObject, context.ClassName, processedText, context.ScriptGenerateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ProcessTemplateText(UIGenerationContext context, string templateText, IUIGeneratorRuleHelper ruleHelper)
|
||||||
|
{
|
||||||
|
var contextualRuleHelper = ContextualUIGeneratorRuleHelper;
|
||||||
return templateText
|
return templateText
|
||||||
.Replace("#ReferenceNameSpace#", ruleHelper.GetReferenceNamespace(_uiBindDatas))
|
.Replace("#ReferenceNameSpace#", contextualRuleHelper != null ? contextualRuleHelper.GetReferenceNamespace(context) : ruleHelper.GetReferenceNamespace(_uiBindDatas))
|
||||||
.Replace("#ClassNameSpace#", scriptGenerateData.NameSpace)
|
.Replace("#ClassNameSpace#", context.ScriptGenerateData.NameSpace)
|
||||||
.Replace("#ClassName#", className)
|
.Replace("#ClassName#", context.ClassName)
|
||||||
.Replace("#TagName#", ruleHelper.GetUIResourceSavePath(targetObject, scriptGenerateData))
|
.Replace("#TagName#", contextualRuleHelper != null ? contextualRuleHelper.GetUIResourceSavePath(context) : ruleHelper.GetUIResourceSavePath(context.TargetObject, context.ScriptGenerateData))
|
||||||
.Replace("#LoadType#", scriptGenerateData.LoadType.ToString())
|
.Replace("#LoadType#", context.ScriptGenerateData.LoadType.ToString())
|
||||||
.Replace("#Variable#", ruleHelper.GetVariableContent(_uiBindDatas));
|
.Replace("#Variable#", contextualRuleHelper != null ? contextualRuleHelper.GetVariableContent(context) : ruleHelper.GetVariableContent(_uiBindDatas));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetClassGenerateName(IUIGeneratorRuleHelper ruleHelper, GameObject targetObject, UIScriptGenerateData scriptGenerateData)
|
||||||
|
{
|
||||||
|
if (ContextualUIGeneratorRuleHelper != null)
|
||||||
|
{
|
||||||
|
return ContextualUIGeneratorRuleHelper.GetClassGenerateName(new UIGenerationContext(targetObject, scriptGenerateData, _uiBindDatas));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ruleHelper.GetClassGenerateName(targetObject, scriptGenerateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CheckCanGenerate(IUIGeneratorRuleHelper ruleHelper, UIGenerationContext context)
|
||||||
|
{
|
||||||
|
if (ContextualUIGeneratorRuleHelper != null)
|
||||||
|
{
|
||||||
|
return ContextualUIGeneratorRuleHelper.CheckCanGenerate(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ruleHelper.CheckCanGenerate(context.TargetObject, context.ScriptGenerateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DidReloadScripts]
|
[DidReloadScripts]
|
||||||
public static void BindUIScript()
|
public static void BindUIScript()
|
||||||
{
|
{
|
||||||
if (!EditorPrefs.HasKey("Generate")) return;
|
if (!EditorPrefs.HasKey(GenerateTypeNameKey)) return;
|
||||||
|
|
||||||
var className = EditorPrefs.GetString("Generate");
|
var scriptTypeName = EditorPrefs.GetString(GenerateTypeNameKey);
|
||||||
var instanceId = EditorPrefs.GetInt("InstanceId", -1);
|
var targetObject = ResolveGenerationTarget();
|
||||||
var targetObject = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
|
|
||||||
|
|
||||||
if (targetObject == null)
|
if (targetObject == null)
|
||||||
{
|
{
|
||||||
Debug.LogWarning("UI script generation attachment object missing!");
|
Debug.LogWarning("UI script generation attachment object missing.");
|
||||||
|
CleanupContext();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_uiBindDatas.Clear();
|
_uiBindDatas.Clear();
|
||||||
_arrayComponents.Clear();
|
_arrayComponents.Clear();
|
||||||
|
|
||||||
|
var bindSucceeded = false;
|
||||||
CollectBindData(targetObject.transform);
|
CollectBindData(targetObject.transform);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BindScriptPropertyField(targetObject, className);
|
bindSucceeded = BindScriptPropertyField(targetObject, scriptTypeName);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
|
Debug.LogException(exception);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
CleanupContext();
|
CleanupContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!bindSucceeded)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
EditorUtility.SetDirty(targetObject);
|
EditorUtility.SetDirty(targetObject);
|
||||||
Debug.Log($"Generate {className} Successfully attached to game object");
|
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()
|
private static void CleanupContext()
|
||||||
{
|
{
|
||||||
EditorPrefs.DeleteKey("Generate");
|
EditorPrefs.DeleteKey(GenerateTypeNameKey);
|
||||||
|
EditorPrefs.DeleteKey(GenerateInstanceIdKey);
|
||||||
|
EditorPrefs.DeleteKey(GenerateAssetPathKey);
|
||||||
_uiBindDatas.Clear();
|
_uiBindDatas.Clear();
|
||||||
_arrayComponents.Clear();
|
_arrayComponents.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void BindScriptPropertyField(GameObject targetObject, string scriptClassName)
|
private static bool BindScriptPropertyField(GameObject targetObject, string scriptTypeName)
|
||||||
{
|
{
|
||||||
if (targetObject == null) throw new ArgumentNullException(nameof(targetObject));
|
if (targetObject == null) throw new ArgumentNullException(nameof(targetObject));
|
||||||
if (string.IsNullOrEmpty(scriptClassName)) throw new ArgumentNullException(nameof(scriptClassName));
|
if (string.IsNullOrEmpty(scriptTypeName)) throw new ArgumentNullException(nameof(scriptTypeName));
|
||||||
|
|
||||||
var scriptType = FindScriptType(scriptClassName);
|
var scriptType = FindScriptType(scriptTypeName);
|
||||||
if (scriptType == null)
|
if (scriptType == null)
|
||||||
{
|
{
|
||||||
Debug.LogError($"Could not find the class: {scriptClassName}");
|
Debug.LogError($"Could not find the class: {scriptTypeName}");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetHolder = targetObject.GetOrAddComponent(scriptType);
|
var targetHolder = targetObject.GetOrAddComponent(scriptType);
|
||||||
BindFieldsToComponents(targetHolder, scriptType);
|
return BindFieldsToComponents(targetHolder, scriptType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Type FindScriptType(string scriptClassName)
|
private static Type FindScriptType(string scriptTypeName)
|
||||||
{
|
{
|
||||||
|
var resolvedType = AlicizaX.Utility.Assembly.GetType(scriptTypeName);
|
||||||
|
if (resolvedType != null)
|
||||||
|
{
|
||||||
|
return resolvedType;
|
||||||
|
}
|
||||||
|
|
||||||
return AppDomain.CurrentDomain.GetAssemblies()
|
return AppDomain.CurrentDomain.GetAssemblies()
|
||||||
.Where(asm => !asm.GetName().Name.EndsWith(".Editor") &&
|
.Where(assembly => !assembly.GetName().Name.EndsWith(".Editor", StringComparison.Ordinal) &&
|
||||||
!asm.GetName().Name.Equals("UnityEditor"))
|
!assembly.GetName().Name.Equals("UnityEditor", StringComparison.Ordinal))
|
||||||
.SelectMany(asm => asm.GetTypes())
|
.SelectMany(assembly => assembly.GetTypes())
|
||||||
.FirstOrDefault(type => type.IsClass && !type.IsAbstract &&
|
.FirstOrDefault(type => type.IsClass && !type.IsAbstract &&
|
||||||
type.Name.Equals(scriptClassName, StringComparison.Ordinal));
|
type.FullName.Equals(scriptTypeName, StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void BindFieldsToComponents(Component targetHolder, Type scriptType)
|
private static bool BindFieldsToComponents(Component targetHolder, Type scriptType)
|
||||||
{
|
{
|
||||||
FieldInfo[] allNonPublicInstanceFields = scriptType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
|
var fields = scriptType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(field =>
|
||||||
|
|
||||||
var fields = allNonPublicInstanceFields.Where(field =>
|
|
||||||
field.GetCustomAttributes(typeof(SerializeField), false).Length > 0
|
field.GetCustomAttributes(typeof(SerializeField), false).Length > 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var isSuccessful = true;
|
||||||
foreach (var field in fields.Where(field => !string.IsNullOrEmpty(field.Name)))
|
foreach (var field in fields.Where(field => !string.IsNullOrEmpty(field.Name)))
|
||||||
{
|
{
|
||||||
var bindData = _uiBindDatas.Find(data => data.Name == field.Name);
|
var bindData = _uiBindDatas.Find(data => data.Name == field.Name);
|
||||||
var components = bindData.Objs;
|
if (bindData == null)
|
||||||
if (components == null)
|
|
||||||
{
|
{
|
||||||
Debug.LogError($"Field {field.Name} did not find matching component binding");
|
Debug.LogError($"Field {field.Name} did not find matching component binding.");
|
||||||
|
isSuccessful = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetFieldValue(field, components, bindData.TypeName, targetHolder);
|
if (!SetFieldValue(field, bindData, targetHolder))
|
||||||
|
{
|
||||||
|
isSuccessful = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return isSuccessful;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetFieldValue(FieldInfo field, IReadOnlyList<GameObject> components, string typeName, Component targetComponent)
|
private static bool SetFieldValue(FieldInfo field, UIBindData bindData, Component targetComponent)
|
||||||
{
|
{
|
||||||
if (field.FieldType.IsArray)
|
if (field.FieldType.IsArray)
|
||||||
{
|
{
|
||||||
SetArrayFieldValue(field, components, typeName, targetComponent);
|
return SetArrayFieldValue(field, bindData, targetComponent);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SetSingleFieldValue(field, components, typeName, targetComponent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return SetSingleFieldValue(field, bindData, targetComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetArrayFieldValue(FieldInfo field, IReadOnlyList<GameObject> components, string typeName, Component targetComponent)
|
private static bool SetArrayFieldValue(FieldInfo field, UIBindData bindData, Component targetComponent)
|
||||||
{
|
{
|
||||||
var elementType = field.FieldType.GetElementType();
|
var elementType = field.FieldType.GetElementType();
|
||||||
if (elementType == null)
|
if (elementType == null)
|
||||||
{
|
{
|
||||||
Debug.LogError($"Field {field.Name} has unknown element type.");
|
Debug.LogError($"Field {field.Name} has unknown element type.");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var components = bindData.Objs;
|
||||||
var array = Array.CreateInstance(elementType, components.Count);
|
var array = Array.CreateInstance(elementType, components.Count);
|
||||||
|
var isSuccessful = true;
|
||||||
for (var i = 0; i < components.Count; i++)
|
for (var i = 0; i < components.Count; i++)
|
||||||
{
|
{
|
||||||
if (components[i] == null) continue;
|
if (components[i] == null) continue;
|
||||||
|
|
||||||
var isGameobject = typeName.Equals(nameof(GameObject));
|
var componentObject = ResolveBoundObject(components[i], bindData.ComponentType);
|
||||||
object ComponentObject = isGameobject ? components[i] : components[i].GetComponent(typeName);
|
|
||||||
|
|
||||||
if (elementType.IsInstanceOfType(ComponentObject))
|
if (componentObject != null && elementType.IsInstanceOfType(componentObject))
|
||||||
{
|
{
|
||||||
array.SetValue(ComponentObject, i);
|
array.SetValue(componentObject, i);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogError($"Element {i} type mismatch for field {field.Name}");
|
Debug.LogError($"Element {i} type mismatch for field {field.Name}");
|
||||||
|
isSuccessful = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field.SetValue(targetComponent, array);
|
field.SetValue(targetComponent, array);
|
||||||
|
return isSuccessful;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetSingleFieldValue(FieldInfo field, IReadOnlyList<GameObject> components, string typeName, Component targetComponent)
|
private static bool SetSingleFieldValue(FieldInfo field, UIBindData bindData, Component targetComponent)
|
||||||
{
|
{
|
||||||
if (components.Count == 0) return;
|
if (bindData.Objs.Count == 0)
|
||||||
|
|
||||||
var isGameobject = typeName.Equals(nameof(GameObject));
|
|
||||||
object firstComponent = isGameobject ? components[0] : components[0].GetComponent(typeName);
|
|
||||||
if (firstComponent == null) return;
|
|
||||||
|
|
||||||
if (field.FieldType.IsInstanceOfType(firstComponent))
|
|
||||||
{
|
{
|
||||||
field.SetValue(targetComponent, firstComponent);
|
return false;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
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");
|
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 : source.GetComponent(componentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PrefabChecker
|
public static class PrefabChecker
|
||||||
|
|||||||
@ -27,7 +27,9 @@ public class UIHolderObjectBaseEditor : Editor
|
|||||||
|
|
||||||
if (prop.isArray)
|
if (prop.isArray)
|
||||||
{
|
{
|
||||||
string arrayElementTypeName = prop.GetArrayElementAtIndex(0).type;
|
var fieldType = fields[i].FieldType;
|
||||||
|
var arrayElementType = fieldType.IsArray ? fieldType.GetElementType() : null;
|
||||||
|
string arrayElementTypeName = arrayElementType?.Name ?? "Element";
|
||||||
|
|
||||||
ReorderableList reorderableList = new ReorderableList(serializedObject, prop, true, true, true, true);
|
ReorderableList reorderableList = new ReorderableList(serializedObject, prop, true, true, true, true);
|
||||||
reorderableList.drawElementCallback = (rect, index, isActive, isFocused) => DrawElementCallback(rect, index, prop, isActive, isFocused);
|
reorderableList.drawElementCallback = (rect, index, isActive, isFocused) => DrawElementCallback(rect, index, prop, isActive, isFocused);
|
||||||
|
|||||||
@ -10,12 +10,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
{
|
{
|
||||||
public static class UIHolderFactory
|
public static class UIHolderFactory
|
||||||
{
|
{
|
||||||
private static readonly IResourceModule ResourceModule;
|
private static IResourceModule ResourceModule => ModuleSystem.GetModule<IResourceModule>();
|
||||||
|
|
||||||
static UIHolderFactory()
|
|
||||||
{
|
|
||||||
ResourceModule = ModuleSystem.GetModule<IResourceModule>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async UniTask<T> CreateUIHolderAsync<T>(Transform parent) where T : UIHolderObjectBase
|
public static async UniTask<T> CreateUIHolderAsync<T>(Transform parent) where T : UIHolderObjectBase
|
||||||
{
|
{
|
||||||
|
|||||||
@ -153,7 +153,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
|
|
||||||
private bool Interactable
|
private bool Interactable
|
||||||
{
|
{
|
||||||
get => _raycaster.enabled && _raycaster != null;
|
get => _raycaster != null && _raycaster.enabled;
|
||||||
|
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user