com.alicizax.unity.framework/Editor/UI/GenerateTool/UIScriptGeneratorHelper.cs
2025-09-10 14:26:54 +08:00

484 lines
18 KiB
C#

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