重构反射生成属性界面

This commit is contained in:
2026-07-23 15:56:29 +08:00
parent c694115893
commit e63f1fb000
105 changed files with 2156 additions and 3280 deletions
@@ -1,19 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bcd1c64483d2457ab9143f086dff511d, type: 3}
m_Name: XuiavrgButton-Default
m_EditorClassIdentifier:
themeName: Default
uiName: Default
valueTypeName: System.Single
prefab: {fileID: 6894309230704738580, guid: a58d335e7df1a994d9f1c0fd58aff68f, type: 3}
supportedTags: 010000001000000002000000
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 2f4eb311896b80d4b9ced716a740dfad
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,19 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bcd1c64483d2457ab9143f086dff511d, type: 3}
m_Name: XuiavrgDropdown-Default
m_EditorClassIdentifier:
themeName: Default
uiName: Default
valueTypeName: System.Enum
prefab: {fileID: 1100600009542095382, guid: 4f25b16125eac234e820506fff28a4b0, type: 3}
supportedTags: 01000000
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 8a13481739bcb4d468fced5fde896f0f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,19 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bcd1c64483d2457ab9143f086dff511d, type: 3}
m_Name: XuiavrgFieldInput-Default
m_EditorClassIdentifier:
themeName: Default
uiName: Default
valueTypeName: System.String
prefab: {fileID: 8186464640817935025, guid: 1715f06fee075eb45b68d43575e9aa88, type: 3}
supportedTags: 0100000080000000
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 4aa3fe7aa30f1b240a4486c12152cd4d
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,19 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bcd1c64483d2457ab9143f086dff511d, type: 3}
m_Name: XuiavrgFloatSlider-Default
m_EditorClassIdentifier:
themeName: Default
uiName: Default
valueTypeName: System.Single
prefab: {fileID: 3950712135413418637, guid: f601d441cf135fe479d319c5eb01abd8, type: 3}
supportedTags: 010000002000000024000000
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 496a772f348d94c419faf2f5c09b773f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,19 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bcd1c64483d2457ab9143f086dff511d, type: 3}
m_Name: XuiavrgToggle-Default
m_EditorClassIdentifier:
themeName: Default
uiName: Default
valueTypeName: System.Boolean
prefab: {fileID: 8328042880981269257, guid: 0dc894baa18f5cb4eb9c82fd3c40d2bd, type: 3}
supportedTags: 0100000008000000
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 41a9df21e888bf3409ef31a6079aab61
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,7 +1,15 @@
namespace XericUI.ReflectionUIGenerator
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 反射 UI Attribute 标记接口
/// 所有标记 Attribute 实现此接口,在元数据提取阶段按序执行 BindMetaData
/// </summary>
public interface IXuiavrgAttribute
{
public void BindProperty(XuiavrgInspectorProperty property);
/// <summary>
/// 将 Attribute 的配置绑定到元数据对象
/// </summary>
/// <param name="metaData">目标元数据</param>
public void BindMetaData(AvrgMemberMetaData metaData);
}
}
}
@@ -1,75 +0,0 @@
using System;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 反射属性标记
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class XuiavrgAttribute : Attribute, IXuiavrgAttribute
{
#region 字段属性
public readonly string LabelName;
public readonly string UIPropertyName;
public readonly string UnitLabelName;
public readonly int InspectorOrder;
public readonly string GroupName;
public readonly string OnTriggerCall;
public readonly XuiavrgSize ArgumentSize;
/// <summary>
/// 标记属性
/// </summary>
/// <param name="labelName">属性显示名称</param>
/// <param name="uiPropertyName">属性标记名(对应界面元素名称)</param>
/// <param name="unitLabelName">属性显示单位名称</param>
/// <param name="mustFillin">是否标记为必填项目</param>
/// <param name="inspectorOrder">属性显示顺序</param>
/// <param name="groupName">属性组名称(可以是以/&\分割的路径)</param>
/// <param name="onTriggerCall">属性附加的动作,调用此内联反射调用的函数</param>
/// <param name="size">属性组名称(可以是以/&\分割的路径)</param>
public XuiavrgAttribute(string labelName, string uiPropertyName, string unitLabelName,
int inspectorOrder = 0, string groupName = null, string onTriggerCall = "", XuiavrgSize size = XuiavrgSize.Normal)
{
LabelName = labelName;
UIPropertyName = uiPropertyName;
UnitLabelName = unitLabelName;
InspectorOrder = inspectorOrder;
GroupName = groupName;
OnTriggerCall = onTriggerCall;
ArgumentSize = size;
}
#endregion
#region 生成器
public void BindProperty(XuiavrgInspectorProperty property)
{
property.labelName = LabelName;
property.uiPropertyName = UIPropertyName;
property.unitLabelName = UnitLabelName;
property.inspectorOrder = InspectorOrder;
property.groupName = GroupName;
property.onTriggerCall = OnTriggerCall;
property.argumentSize = ArgumentSize;
}
#endregion
}
/// <summary>
/// 属性尺寸
/// </summary>
public enum XuiavrgSize
{
None = 0,
Tiny = 1000 - 2,
Small = 1000 - 1,
Normal = 1000,
Big,
Large,
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 10e270c86cab4817a0b8ce7877b2ab88
timeCreated: 1776505683
@@ -1,28 +0,0 @@
using System;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 集合检查
/// </summary>
public class XuiavrgCollectionCheckAttribute : Attribute, IXuiavrgAttribute
{
public readonly int minCount;
public readonly int maxCount;
public readonly bool notEmpty;
public XuiavrgCollectionCheckAttribute(int minCount, int maxCount, bool notEmpty)
{
this.minCount = minCount;
this.maxCount = maxCount;
this.notEmpty = notEmpty;
}
public void BindProperty(XuiavrgInspectorProperty property)
{
property.collectionMinCount = minCount;
property.collectionMaxCount = maxCount;
property.collectionNotEmpty = notEmpty;
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 4ec4224cb3844c439dd5fdb6b31c9730
timeCreated: 1777363387
@@ -38,10 +38,11 @@ namespace XericUI.ReflectionUIGenerator
TagIndex = tagIndex;
}
public void BindProperty(XuiavrgInspectorProperty property)
public void BindMetaData(AvrgMemberMetaData metaData)
{
// 新的 Entry 体系通过 Manager.BuildEntries 直接处理此 Attribute
// 保留 IXuiavrgAttribute 兼容旧的 SchemaHydration 流程
metaData.GroupPath = GroupPath;
metaData.Tag = Tag;
metaData.TagIndex = TagIndex;
}
}
}
@@ -24,9 +24,9 @@ namespace XericUI.ReflectionUIGenerator
Order = order;
}
public void BindProperty(XuiavrgInspectorProperty property)
public void BindMetaData(AvrgMemberMetaData metaData)
{
property.inspectorOrder = Order;
metaData.FieldOrder = Order;
}
}
}
@@ -21,16 +21,16 @@ namespace XericUI.ReflectionUIGenerator
/// 创建预制体覆盖标记
/// </summary>
/// <param name="prefabGroupName">
/// 目标预制体组名称,与 XuiavrgPropertyGroupScriptableObject.groupName 匹配
/// 目标预制体名称,在 AvrgTypePrefabMappingConfig 中查找匹配
/// </param>
public XuiavrgFieldPrefabOverrideAttribute(string prefabGroupName)
{
PrefabGroupName = prefabGroupName;
}
public void BindProperty(XuiavrgInspectorProperty property)
public void BindMetaData(AvrgMemberMetaData metaData)
{
// 新的 Entry 体系通过 Manager 直接处理此 Attribute
metaData.PrefabOverrideName = PrefabGroupName;
}
}
}
@@ -1,5 +1,4 @@
using System;
using UnityEngine.Serialization;
using System;
namespace XericUI.ReflectionUIGenerator
{
@@ -20,9 +19,9 @@ namespace XericUI.ReflectionUIGenerator
Hide = hideInInspector;
}
public void BindProperty(XuiavrgInspectorProperty property)
public void BindMetaData(AvrgMemberMetaData metaData)
{
property.hideInInspector = Hide;
metaData.Hide = Hide;
}
}
}
}
@@ -1,15 +0,0 @@
using System;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 不可为空
/// </summary>
public class XuiavrgNotEmptyAttribute : Attribute, IXuiavrgAttribute
{
public void BindProperty(XuiavrgInspectorProperty property)
{
property.notEmpty = true;
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 89e35e544d66421c8cc524d3b4d7b943
timeCreated: 1777360693
@@ -1,25 +0,0 @@
using System;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 数值检查
/// </summary>
public class XuiavrgNumberCheckAttribute : Attribute, IXuiavrgAttribute
{
public readonly float minValue;
public readonly float maxValue;
public XuiavrgNumberCheckAttribute(float minValue, float maxValue)
{
this.minValue = minValue;
this.maxValue = maxValue;
}
public void BindProperty(XuiavrgInspectorProperty property)
{
property.numberMinValue = minValue;
property.numberMaxValue = maxValue;
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: d5b533c6d6ca4de8811adc3913d1fd9b
timeCreated: 1777362005
@@ -0,0 +1,61 @@
using System;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 属性检查标记
/// 替代旧的 Required/NotEmpty 标记,扩展范围验证、警告、无效值检查
///
/// 使用示例:
/// [XuiavrgPropertyCheck(Required = true)] public string name;
/// [XuiavrgPropertyCheck(Required = true, ValidateReasonable = true)] public float speed;
/// [XuiavrgPropertyCheck(CheckKey = "MyCustomKey", ValidateWarning = true)] public int score;
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class XuiavrgPropertyCheckAttribute : Attribute, IXuiavrgAttribute
{
/// <summary>
/// 判断依据标识
/// 默认使用类型全称(如 "System.Single")与 AvrgValueRangeConfig 中的条目匹配
/// 也可自定义其他名称
/// </summary>
public string CheckKey { get; set; }
/// <summary>是否必填</summary>
public bool Required { get; set; }
/// <summary>是否启用合理值范围检查</summary>
public bool ValidateReasonable { get; set; }
/// <summary>是否启用警告状态检查</summary>
public bool ValidateWarning { get; set; }
/// <summary>是否启用无效值状态检查</summary>
public bool ValidateInvalid { get; set; }
public XuiavrgPropertyCheckAttribute()
{
}
/// <summary>
/// 创建必填检查标记
/// </summary>
public XuiavrgPropertyCheckAttribute(bool required)
{
Required = required;
}
public void BindMetaData(AvrgMemberMetaData metaData)
{
metaData.Required = Required;
// 如果指定了 CheckKey,使用它;否则保持元数据默认(元数据默认用 Type.FullName)
if (!string.IsNullOrEmpty(CheckKey))
metaData.PropertyCheckKey = CheckKey;
metaData.ValidateReasonable = ValidateReasonable;
metaData.ValidateWarning = ValidateWarning;
metaData.ValidateInvalid = ValidateInvalid;
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 211cf8030617ad143ba1f4f68f544658
guid: 217e7cb9b7af5af4186e2d3f10a89c7c
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -1,16 +0,0 @@
using System;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 必填
/// </summary>
public class XuiavrgRequiredAttribute : Attribute, IXuiavrgAttribute
{
public void BindProperty(XuiavrgInspectorProperty property)
{
property.required = true;
property.notEmpty = true;
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: bcc4b8b735e84251b11ef87e9fe5b2cb
timeCreated: 1777358718
@@ -1,48 +0,0 @@
using System;
using UnityEngine;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 字符串检查
/// </summary>
public class XuiavrgStringCheckAttribute : Attribute, IXuiavrgAttribute
{
public readonly int minLength;
public readonly int maxLength;
public readonly string regexPattern;
public readonly bool onlyLetters;
public readonly bool onlyNumber;
public readonly bool onlyEmail;
public readonly bool onlyPhone;
public readonly bool onlyUrl;
public XuiavrgStringCheckAttribute(
int minLength = 0, int maxLength = int.MaxValue,
string regexPattern = "",
bool onlyLetters = false, bool onlyNumber = false, bool onlyEmail = true, bool onlyPhone = true,
bool onlyUrl = true)
{
this.minLength = minLength;
this.maxLength = maxLength;
this.regexPattern = regexPattern;
this.onlyLetters = onlyLetters;
this.onlyNumber = onlyNumber;
this.onlyEmail = onlyEmail;
this.onlyPhone = onlyPhone;
this.onlyUrl = onlyUrl;
}
public void BindProperty(XuiavrgInspectorProperty property)
{
property.stringMinLength = minLength;
property.stringMaxLength = maxLength;
property.stringRegexPattern = regexPattern;
property.stringOnlyLetters = onlyLetters;
property.stringOnlyNumber = onlyNumber;
property.stringOnlyEmail = onlyEmail;
property.stringOnlyPhone = onlyPhone;
property.stringOnlyUrl = onlyUrl;
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 0a0e34665ed24a7c87e5eb84a45f6ef6
timeCreated: 1777360997
@@ -0,0 +1,23 @@
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 单个成员的验证结果枚举
/// </summary>
public enum AvrgMemberCheckResult
{
/// <summary>未标记检查或不适用(跳过)</summary>
Skipped = 0,
/// <summary>检查通过,值有效</summary>
Valid = 1,
/// <summary>必填但为空</summary>
Empty = 2,
/// <summary>值在警告范围内</summary>
Warning = 3,
/// <summary>值在无效范围内</summary>
Invalid = 4,
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ba7420972b684874dbab8713369d6f1b
guid: 51cc10b88f508db4495e8998f44e8b01
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 单个成员的元数据数据结构
/// 记录从反射 + Attribute 标记中提取的所有信息
/// </summary>
public class AvrgMemberMetaData
{
// ============ 反射信息 ============
/// <summary>字段/属性名</summary>
public string MemberName { get; set; }
/// <summary>反射信息 (FieldInfo 或 PropertyInfo)</summary>
public MemberInfo MemberInfo { get; set; }
/// <summary>值类型</summary>
public Type ValueType { get; set; }
/// <summary>是否为属性 (false 为字段)</summary>
public bool IsProperty { get; set; }
/// <summary>绑定的目标实例 (可选,Step1 时可空)</summary>
public object TargetInstance { get; set; }
// ============ 从 IXuiavrgAttribute 融合的元数据 ============
/// <summary>显示标签</summary>
public string LabelName { get; set; }
/// <summary>单位文本</summary>
public string UnitLabelName { get; set; }
/// <summary>功能标记</summary>
public XuiavrgFieldTag Tag { get; set; } = XuiavrgFieldTag.None;
/// <summary>标记索引 (同 Tag 多实例时区分)</summary>
public int TagIndex { get; set; }
/// <summary>排序值 (越小越靠前)</summary>
public int FieldOrder { get; set; }
/// <summary>编组路径 (以 "/" 分割层级)</summary>
public string GroupPath { get; set; }
/// <summary>预制体覆盖名称</summary>
public string PrefabOverrideName { get; set; }
/// <summary>是否在 UI 中隐藏</summary>
public bool Hide { get; set; }
/// <summary>值变更时触发的反射调用方法名</summary>
public string OnTriggerCall { get; set; }
/// <summary>
/// 类的初始默认值(由类字段初始化器决定)
/// 例如 public float speed = 10f; → DefaultValue = 10f
/// </summary>
public object DefaultValue { get; set; }
// ============ 属性检查 (由 [PropertyCheck] 填充) ============
/// <summary>
/// 判断依据标识
/// 默认使用 ValueType.FullName 与 AvrgValueRangeConfig 中的条目匹配
/// </summary>
public string PropertyCheckKey { get; set; }
/// <summary>是否必填</summary>
public bool Required { get; set; }
/// <summary>是否不可为空</summary>
public bool NotEmpty { get; set; }
/// <summary>是否启用合理值范围检查</summary>
public bool ValidateReasonable { get; set; }
/// <summary>是否启用警告状态检查</summary>
public bool ValidateWarning { get; set; }
/// <summary>是否启用无效值状态检查</summary>
public bool ValidateInvalid { get; set; }
// ============ (已弃用字段保留) 旧的验证约束 ============
/// <summary>字符串最小长度</summary>
[Obsolete("将移至 ValueRangeConfig 管理")]
public int StringMinLength { get; set; }
/// <summary>字符串最大长度</summary>
public int StringMaxLength { get; set; } = int.MaxValue;
/// <summary>数值最小值</summary>
[Obsolete("将移至 ValueRangeConfig 管理")]
public float NumberMinValue { get; set; }
/// <summary>数值最大值</summary>
[Obsolete("将移至 ValueRangeConfig 管理")]
public float NumberMaxValue { get; set; }
// ============ 原始 Attribute 引用 ============
/// <summary>
/// 收集到的所有 IXuiavrgAttribute 列表
/// 保留原始引用,供扩展和自定义处理使用
/// </summary>
public List<IXuiavrgAttribute> Attributes { get; set; } = new List<IXuiavrgAttribute>();
/// <summary>
/// 从绑定目标获取值
/// </summary>
public object GetValue()
{
if (TargetInstance == null) return null;
switch (MemberInfo)
{
case PropertyInfo pi:
return pi.GetValue(TargetInstance);
case FieldInfo fi:
return fi.GetValue(TargetInstance);
default:
return null;
}
}
/// <summary>
/// 设置值到绑定目标
/// </summary>
public void SetValue(object value)
{
if (TargetInstance == null) return;
switch (MemberInfo)
{
case PropertyInfo pi:
if (pi.CanWrite)
pi.SetValue(TargetInstance, value);
break;
case FieldInfo fi:
fi.SetValue(TargetInstance, value);
break;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f152de979aea6de48a4979fa509ac23f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,247 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 元数据集
/// 包含从对象反射提取的完整元数据,以及用于生成 MVVM 绑定的 BuildMDVM() 方法
/// </summary>
public class AvrgMemberMetaDataSet
{
private List<AvrgMemberMetaData> _metaDataList = new List<AvrgMemberMetaData>();
/// <summary>
/// 只读访问元数据列表
/// </summary>
public IReadOnlyList<AvrgMemberMetaData> MetaDataList => _metaDataList;
/// <summary>
/// 添加元数据
/// </summary>
public void Add(AvrgMemberMetaData metaData)
{
_metaDataList.Add(metaData);
}
/// <summary>
/// 处理编组合并 — 将 GroupPath 相同的元数据合并
/// 非编组元数据保持为独立 Entry
/// </summary>
internal void MergeGroupEntries()
{
// 提取编组和非编组
var grouped = _metaDataList
.Where(m => !string.IsNullOrEmpty(m.GroupPath))
.GroupBy(m => m.GroupPath)
.ToList();
var nonGrouped = _metaDataList
.Where(m => string.IsNullOrEmpty(m.GroupPath))
.ToList();
var merged = new List<AvrgMemberMetaData>();
foreach (var gEntry in grouped)
{
// 编组内第一个成员作为主元数据
var first = gEntry.First();
// 合成的 metaData 保持 GroupPath 和主 Tag
merged.Add(first);
}
// 非编组直接追加
merged.AddRange(nonGrouped);
_metaDataList = merged;
}
/// <summary>
/// 按 FieldOrder 排序
/// </summary>
internal void SortByFieldOrder()
{
_metaDataList.Sort((a, b) =>
{
int orderCompare = a.FieldOrder.CompareTo(b.FieldOrder);
if (orderCompare != 0) return orderCompare;
return string.Compare(a.MemberName, b.MemberName, StringComparison.Ordinal);
});
}
/// <summary>
/// 为元数据生成对应的 MVVM 值绑定模板
/// </summary>
/// <param name="businessTarget">可选的业务目标对象(如果元数据中没有绑定目标,则在此传入)</param>
/// <param name="resolverConfig">可选的冲突解决配置</param>
/// <returns>绑定集合</returns>
public AvrgMvvmBindingCollection BuildMDVM(
object businessTarget = null,
AvrgMvvmResolverConfig resolverConfig = null)
{
var bindingCollection = new AvrgMvvmBindingCollection();
var scheduler = new AvrgMvvmScheduler(resolverConfig);
foreach (var metaData in _metaDataList)
{
// 确定目标实例
var target = metaData.TargetInstance ?? businessTarget;
if (target == null)
continue;
// 创建绑定
var binding = new AvrgMvvmBinding
{
MetaData = metaData,
ReadFunc = () => metaData.GetValue(),
};
var capturedBinding = binding;
binding.WriteAction = (val) =>
{
metaData.SetValue(val);
scheduler.MarkDataDirty(capturedBinding);
};
// 注册到调度器
scheduler.Register(binding);
bindingCollection.Add(binding);
}
bindingCollection.AttachScheduler(scheduler);
return bindingCollection;
}
// ============ 验证方法 ============
/// <summary>
/// 对当前元数据集执行属性检查验证
/// </summary>
/// <param name="valueRangeConfig">值范围配置(可选,不传入则不执行范围检查)</param>
/// <returns>验证结果集合</returns>
public AvrgValidationResultSet Validate(AvrgValueRangeConfig valueRangeConfig = null)
{
var resultSet = new AvrgValidationResultSet();
foreach (var metaData in _metaDataList)
{
if (metaData.Hide)
continue;
// 只有标记了 PropertyCheck 的才验证
if (!metaData.Required && !metaData.ValidateReasonable
&& !metaData.ValidateWarning && !metaData.ValidateInvalid)
{
resultSet.Results.Add(new AvrgMemberValidationResult
{
MetaData = metaData,
Result = AvrgMemberCheckResult.Skipped,
CurrentValueString = metaData.GetValue()?.ToString(),
Message = "未标记属性检查",
});
continue;
}
var value = metaData.GetValue();
var checkResult = CheckSingleMember(metaData, value, valueRangeConfig);
resultSet.Results.Add(checkResult);
}
return resultSet;
}
/// <summary>
/// 验证单个成员
/// </summary>
private AvrgMemberValidationResult CheckSingleMember(
AvrgMemberMetaData metaData,
object value,
AvrgValueRangeConfig rangeConfig)
{
var result = new AvrgMemberValidationResult
{
MetaData = metaData,
CurrentValueString = value?.ToString() ?? "(null)",
};
// 根据 CheckKey 查找值范围规则
AvrgValueRangeEntry rangeEntry = null;
string checkKey = !string.IsNullOrEmpty(metaData.PropertyCheckKey)
? metaData.PropertyCheckKey
: metaData.ValueType?.FullName;
if (rangeConfig != null && !string.IsNullOrEmpty(checkKey))
{
rangeEntry = rangeConfig.FindEntry(checkKey);
}
// ——— 优先级 1: 空值检查 (Required) ———
if (metaData.Required)
{
bool isEmpty = false;
// 引用类型 null
if (value == null)
{
isEmpty = true;
}
// 字符串 null/空
else if (value is string str)
{
isEmpty = string.IsNullOrEmpty(str);
}
// 使用 ValueRangeEntry 的 IsEmpty 判断
else if (rangeEntry != null && !rangeEntry.skipEmptyCheck)
{
isEmpty = rangeEntry.IsEmpty(value);
}
if (isEmpty)
{
result.Result = AvrgMemberCheckResult.Empty;
result.Message = $"必填字段 '{metaData.MemberName}' 为空";
return result;
}
}
// 如果没有范围配置,无法继续检查
if (rangeEntry == null)
{
result.Result = AvrgMemberCheckResult.Valid;
result.Message = "通过(无范围配置)";
return result;
}
// ——— 优先级 2: 合理值检查(最高优先级,命中即通过) ———
if (metaData.ValidateReasonable && rangeEntry.IsReasonable(value))
{
result.Result = AvrgMemberCheckResult.Valid;
result.Message = "值在合理范围内";
return result;
}
// ——— 优先级 3: 无效值检查 ———
if (metaData.ValidateInvalid && rangeEntry.IsInvalid(value))
{
result.Result = AvrgMemberCheckResult.Invalid;
result.Message = $"值 {value} 在无效范围内";
return result;
}
// ——— 优先级 4: 警告值检查 ———
if (metaData.ValidateWarning && rangeEntry.IsWarning(value))
{
result.Result = AvrgMemberCheckResult.Warning;
result.Message = $"值 {value} 在警告范围内";
return result;
}
// ——— 最终: 通过 ———
result.Result = AvrgMemberCheckResult.Valid;
result.Message = "通过";
return result;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9382162bffd9c7c43ac24fca5ca83cb0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,69 @@
using System.Collections.Generic;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 单个成员的详细验证结果
/// </summary>
public struct AvrgMemberValidationResult
{
/// <summary>验证的元数据</summary>
public AvrgMemberMetaData MetaData { get; set; }
/// <summary>验证结果</summary>
public AvrgMemberCheckResult Result { get; set; }
/// <summary>当前值(字符串形式,便于显示)</summary>
public string CurrentValueString { get; set; }
/// <summary>验证消息(说明原因)</summary>
public string Message { get; set; }
/// <summary>是否通过验证</summary>
public bool IsPassed => Result == AvrgMemberCheckResult.Valid
|| Result == AvrgMemberCheckResult.Skipped
|| Result == AvrgMemberCheckResult.Warning; // Warning 视为通过但需注意
}
/// <summary>
/// 完整验证结果集合
/// </summary>
public class AvrgValidationResultSet
{
/// <summary>所有成员的验证结果</summary>
public List<AvrgMemberValidationResult> Results { get; set; } = new List<AvrgMemberValidationResult>();
/// <summary>未验证通过的条目数(Empty + Invalid)</summary>
public int FailedCount
{
get
{
int count = 0;
foreach (var r in Results)
{
if (r.Result == AvrgMemberCheckResult.Empty || r.Result == AvrgMemberCheckResult.Invalid)
count++;
}
return count;
}
}
/// <summary>警告条目数</summary>
public int WarningCount
{
get
{
int count = 0;
foreach (var r in Results)
{
if (r.Result == AvrgMemberCheckResult.Warning)
count++;
}
return count;
}
}
/// <summary>是否全部通过(无失败项)</summary>
public bool IsAllPassed => FailedCount == 0;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eb4d50aa593365b4abae59c7ac2ae557
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,98 @@
using System;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 值绑定模板
/// 为单个元数据成员创建的 MVVM 双向绑定,包含读/写委托和脏标记
/// </summary>
public class AvrgMvvmBinding
{
/// <summary>关联的元数据</summary>
public AvrgMemberMetaData MetaData { get; set; }
/// <summary>从数据源读取值的委托</summary>
public Func<object> ReadFunc { get; set; }
/// <summary>写入值到数据源的委托</summary>
public Action<object> WriteAction { get; set; }
// ============ 脏标记 ============
/// <summary>UI 侧是否标记为脏</summary>
public bool IsUIDirty { get; set; }
/// <summary>数据侧是否标记为脏</summary>
public bool IsDataDirty { get; set; }
/// <summary>UI 侧写入的缓存值</summary>
public object UICachedValue { get; set; }
/// <summary>数据侧写入的缓存值</summary>
public object DataCachedValue { get; set; }
// ============ UI 绑定 ============
/// <summary>UI 值变更回调委托 — UI 组件订阅此回调以更新显示</summary>
public Action<object> OnUIDataChanged;
/// <summary>通知 UI 更新值</summary>
public void NotifyUIUpdate(object value)
{
OnUIDataChanged?.Invoke(value);
}
/// <summary>清除所有 UI 变更订阅</summary>
public void ClearUIEvent()
{
OnUIDataChanged = null;
}
// ============ 便捷方法 ============
/// <summary>
/// UI 侧写入值(标记脏,不立即传播)
/// </summary>
public void WriteFromUI(object value)
{
UICachedValue = value;
IsUIDirty = true;
}
/// <summary>
/// 数据侧写入值(标记脏,不立即传播)
/// </summary>
public void WriteFromData(object value)
{
DataCachedValue = value;
IsDataDirty = true;
}
/// <summary>
/// 从数据源读取当前值
/// </summary>
public object ReadFromDataSource()
{
return ReadFunc?.Invoke();
}
/// <summary>
/// 将值实际写入数据源
/// </summary>
public void CommitToDataSource(object value)
{
WriteAction?.Invoke(value);
}
/// <summary>
/// 重置脏标记
/// </summary>
public void ClearDirty()
{
IsUIDirty = false;
IsDataDirty = false;
UICachedValue = null;
DataCachedValue = null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0d011f5c07bb55648b27e40166a1ca58
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,82 @@
using System.Collections;
using System.Collections.Generic;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 绑定集合 — 统一管理所有 AvrgMvvmBinding
/// </summary>
public class AvrgMvvmBindingCollection : IEnumerable<AvrgMvvmBinding>
{
private List<AvrgMvvmBinding> _bindings = new List<AvrgMvvmBinding>();
private AvrgMvvmScheduler _scheduler;
/// <summary>
/// 绑定的元数据集引用(可选)
/// </summary>
public AvrgMemberMetaDataSet SourceMetaDataSet { get; set; }
/// <summary>
/// 绑定数量
/// </summary>
public int Count => _bindings.Count;
/// <summary>
/// 添加绑定
/// </summary>
public void Add(AvrgMvvmBinding binding)
{
_bindings.Add(binding);
}
/// <summary>
/// 获取指定索引的绑定
/// </summary>
public AvrgMvvmBinding this[int index] => _bindings[index];
/// <summary>
/// 获取指定成员名称的绑定
/// </summary>
public AvrgMvvmBinding FindByMemberName(string memberName)
{
return _bindings.Find(b => b.MetaData?.MemberName == memberName);
}
/// <summary>
/// 获取指定 Tag 类型的绑定列表
/// </summary>
public List<AvrgMvvmBinding> FindByTag(XuiavrgFieldTag tag)
{
return _bindings.FindAll(b => (b.MetaData?.Tag & tag) == tag);
}
/// <summary>
/// 附加调度器
/// </summary>
internal void AttachScheduler(AvrgMvvmScheduler scheduler)
{
_scheduler = scheduler;
}
/// <summary>
/// 触发调度器 Flush,处理所有脏标记
/// </summary>
public void Flush()
{
_scheduler?.Flush();
}
/// <summary>
/// 清空所有绑定
/// </summary>
public void Clear()
{
_bindings.Clear();
_scheduler?.Clear();
}
public IEnumerator<AvrgMvvmBinding> GetEnumerator() => _bindings.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2cd947ac831d21b4288a0b3268d79ffd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 可配置的冲突解决器 ScriptableObject
/// 预配置各种冲突场景下的决策方法,以及可注册的自定义解析器列表
/// </summary>
[CreateAssetMenu(
fileName = "Xeric UI avr Resolver Config",
menuName = "Xeric Library/UI Action Vessel/Reflection UI Gen/UI avr Resolver Config",
order = 20)]
public class AvrgMvvmResolverConfig : ScriptableObject, IAvrgMvvmConflictResolver
{
/// <summary>全局默认冲突解决策略</summary>
[Tooltip("全局默认冲突解决策略")]
public ConflictResolutionStrategy defaultStrategy = ConflictResolutionStrategy.PreferUI;
/// <summary>自定义解析器列表(按序尝试)</summary>
[Tooltip("自定义解析器类型全名列表(按序尝试,需实现 IAvrgMvvmConflictResolver)")]
public List<string> customResolverTypeNames = new List<string>();
// 运行时已实例化的解析器缓存
[NonSerialized]
private List<IAvrgMvvmConflictResolver> _resolverInstances;
/// <summary>
/// 获取或实例化自定义解析器
/// </summary>
private List<IAvrgMvvmConflictResolver> GetResolverInstances()
{
if (_resolverInstances != null)
return _resolverInstances;
_resolverInstances = new List<IAvrgMvvmConflictResolver>();
foreach (var typeName in customResolverTypeNames)
{
try
{
var type = Type.GetType(typeName);
if (type != null && typeof(IAvrgMvvmConflictResolver).IsAssignableFrom(type))
{
var instance = Activator.CreateInstance(type) as IAvrgMvvmConflictResolver;
if (instance != null)
_resolverInstances.Add(instance);
}
}
catch (Exception e)
{
Debug.LogWarning($"[AvrgMvvmResolverConfig] 无法实例化解析器 {typeName}: {e.Message}");
}
}
return _resolverInstances;
}
/// <summary>
/// 尝试按配置解决冲突
/// </summary>
public bool TryResolve(AvrgMvvmBinding binding, out object resolvedValue)
{
// 按序尝试自定义解析器
var resolvers = GetResolverInstances();
foreach (var resolver in resolvers)
{
if (resolver.TryResolve(binding, defaultStrategy, out resolvedValue))
return true;
}
resolvedValue = null;
return false;
}
/// <summary>
/// IAvrgMvvmConflictResolver 接口实现
/// </summary>
bool IAvrgMvvmConflictResolver.TryResolve(
AvrgMvvmBinding binding,
ConflictResolutionStrategy defaultStrategy,
out object resolvedValue)
{
return TryResolve(binding, out resolvedValue);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e2b7e3ef1b0932a4a9f2cbbb1ae11546
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,209 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// MVVM 调度器 — 管理脏标记、触发 Flush 更新、检测并解决冲突
///
/// 冲突解决策略:
/// - 仅数据脏 → 通知 UI 更新
/// - 仅 UI 脏 → 写入数据源
/// - 双方都脏 → 走配置的冲突解决策略
/// </summary>
public class AvrgMvvmScheduler
{
/// <summary>用户可注册的外部冲突解决事件</summary>
public event Func<AvrgMvvmBinding, ConflictResolutionStrategy, object> OnConflictDetected;
/// <summary>当前配置的冲突解决策略</summary>
public ConflictResolutionStrategy DefaultStrategy { get; set; } = ConflictResolutionStrategy.PreferUI;
private List<AvrgMvvmBinding> _bindings = new List<AvrgMvvmBinding>();
private HashSet<AvrgMvvmBinding> _dirtySet = new HashSet<AvrgMvvmBinding>();
private AvrgMvvmResolverConfig _resolverConfig;
public AvrgMvvmScheduler(AvrgMvvmResolverConfig resolverConfig = null)
{
_resolverConfig = resolverConfig;
}
/// <summary>
/// 注册绑定到调度器
/// </summary>
public void Register(AvrgMvvmBinding binding)
{
if (!_bindings.Contains(binding))
_bindings.Add(binding);
}
/// <summary>
/// UI 侧写入时调用 — 标记脏
/// </summary>
public void MarkUIDirty(AvrgMvvmBinding binding)
{
binding.IsUIDirty = true;
_dirtySet.Add(binding);
}
/// <summary>
/// 数据侧写入时调用 — 标记脏
/// </summary>
public void MarkDataDirty(AvrgMvvmBinding binding)
{
binding.IsDataDirty = true;
_dirtySet.Add(binding);
}
/// <summary>
/// 处理所有脏绑定
/// </summary>
public void Flush()
{
if (_dirtySet.Count == 0) return;
foreach (var binding in _dirtySet)
{
ResolveBinding(binding);
binding.ClearDirty();
}
_dirtySet.Clear();
}
private void ResolveBinding(AvrgMvvmBinding binding)
{
bool uiDirty = binding.IsUIDirty;
bool dataDirty = binding.IsDataDirty;
if (uiDirty && dataDirty)
{
// 双方都脏 — 走冲突解决
ResolveConflict(binding);
}
else if (dataDirty)
{
// 仅数据侧更新 → 通知 UI
var value = binding.DataCachedValue ?? binding.ReadFromDataSource();
binding.CommitToDataSource(value);
binding.NotifyUIUpdate(value);
}
else if (uiDirty)
{
// 仅 UI 侧更新 → 写入数据源
binding.CommitToDataSource(binding.UICachedValue);
}
}
private void ResolveConflict(AvrgMvvmBinding binding)
{
// 1. 检查外部事件处理
if (OnConflictDetected != null)
{
try
{
var resolvedValue = OnConflictDetected.Invoke(binding, DefaultStrategy);
if (resolvedValue != null)
{
binding.CommitToDataSource(resolvedValue);
binding.NotifyUIUpdate(resolvedValue);
return;
}
}
catch (Exception e)
{
Debug.LogError($"[AvrgMvvmScheduler] 外部冲突解决事件异常: {e.Message}");
}
}
// 2. 检查 ResolverConfig 中的自定义策略
if (_resolverConfig != null && _resolverConfig.TryResolve(binding, out object configResolvedValue))
{
binding.CommitToDataSource(configResolvedValue);
binding.NotifyUIUpdate(configResolvedValue);
return;
}
// 3. 按默认策略解决
switch (DefaultStrategy)
{
case ConflictResolutionStrategy.PreferUI:
binding.CommitToDataSource(binding.UICachedValue);
binding.NotifyUIUpdate(binding.UICachedValue);
break;
case ConflictResolutionStrategy.PreferData:
var dataValue = binding.DataCachedValue ?? binding.ReadFromDataSource();
binding.CommitToDataSource(dataValue);
binding.NotifyUIUpdate(dataValue);
break;
case ConflictResolutionStrategy.CompareValues:
// 比较UI值和数据值
object uiVal = binding.UICachedValue;
object dataVal = binding.DataCachedValue ?? binding.ReadFromDataSource();
if (Equals(uiVal, dataVal))
{
// 值相同,忽略冲突
binding.NotifyUIUpdate(dataVal);
break;
}
// 值不同,回退到 PreferUI
binding.CommitToDataSource(binding.UICachedValue);
binding.NotifyUIUpdate(binding.UICachedValue);
Debug.LogWarning($"[AvrgMvvmScheduler] 值冲突(CompareValues回退PreferUI): {binding.MetaData?.MemberName}");
break;
default:
binding.CommitToDataSource(binding.UICachedValue);
binding.NotifyUIUpdate(binding.UICachedValue);
break;
}
}
/// <summary>
/// 清空所有注册
/// </summary>
public void Clear()
{
_bindings.Clear();
_dirtySet.Clear();
}
/// <summary>
/// 手动将绑定的 UI 值标记为脏
/// </summary>
public void MarkUI(AvrgMvvmBinding binding, object value)
{
binding.WriteFromUI(value);
MarkUIDirty(binding);
}
/// <summary>
/// 手动将绑定的数据值写入并标记脏
/// </summary>
public void MarkData(AvrgMvvmBinding binding, object value)
{
binding.WriteFromData(value);
MarkDataDirty(binding);
}
}
/// <summary>
/// 冲突解决策略枚举
/// </summary>
public enum ConflictResolutionStrategy
{
/// <summary>优先使用 UI 侧的值</summary>
PreferUI,
/// <summary>优先使用数据源侧的值</summary>
PreferData,
/// <summary>比较两者,相同则忽略,不同则优先 UI</summary>
CompareValues,
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c0f505fa03fa4e74399ff1e62540dfd8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 类型-预制体映射配置 ScriptableObject
/// 用于在 GenerateUI 阶段根据数据类型查找匹配的预制体
/// </summary>
[CreateAssetMenu(
fileName = "Xeric UI avr Type-Prefab Mapping",
menuName = "Xeric Library/UI Action Vessel/Reflection UI Gen/UI avr Type-Prefab Mapping",
order = 15)]
public class AvrgTypePrefabMappingConfig : ScriptableObject
{
/// <summary>
/// 单个类型映射条目
/// </summary>
[Serializable]
public class TypePrefabEntry
{
[Tooltip("C# 数据类型全名 (如 System.Single, System.String, System.Boolean)")]
public string valueTypeName;
[Tooltip("对应的 UI 预制体")]
public GameObject prefab;
/// <summary>缓存的类型</summary>
[NonSerialized] public Type cachedType;
}
/// <summary>类型映射列表</summary>
[Tooltip("类型映射列表:数据类型 → UI 预制体")]
public List<TypePrefabEntry> typePrefabEntries = new List<TypePrefabEntry>();
/// <summary>默认预制体(无精确匹配时的回退)</summary>
[Tooltip("默认预制体(没有类型匹配时的回退方案)")]
public GameObject defaultPrefab;
[NonSerialized]
private Dictionary<Type, GameObject> _typeCache;
private void BuildCache()
{
if (_typeCache != null) return;
_typeCache = new Dictionary<Type, GameObject>();
foreach (var entry in typePrefabEntries)
{
if (entry.prefab == null) continue;
if (entry.cachedType == null && !string.IsNullOrEmpty(entry.valueTypeName))
entry.cachedType = Type.GetType(entry.valueTypeName);
if (entry.cachedType != null && !_typeCache.ContainsKey(entry.cachedType))
_typeCache[entry.cachedType] = entry.prefab;
}
}
/// <summary>
/// 根据数据类型查找匹配的预制体
/// </summary>
public GameObject FindPrefabByType(Type valueType)
{
if (valueType == null) return defaultPrefab;
BuildCache();
if (_typeCache.TryGetValue(valueType, out var prefab))
return prefab;
// 精确匹配失败,尝试基类/接口匹配
foreach (var kvp in _typeCache)
{
if (kvp.Key.IsAssignableFrom(valueType))
return kvp.Value;
}
return defaultPrefab;
}
/// <summary>
/// 根据绑定的 Entry Bindings 查找匹配的预制体
/// </summary>
public GameObject FindPrefabByBindings(IReadOnlyList<AvrgMvvmBinding> bindings)
{
if (bindings == null || bindings.Count == 0) return defaultPrefab;
var metaData = bindings[0].MetaData;
if (metaData?.ValueType == null) return defaultPrefab;
// 优先按 PrefabOverrideName 查找预制体
if (!string.IsNullOrEmpty(metaData.PrefabOverrideName))
{
foreach (var entry in typePrefabEntries)
{
if (entry.prefab != null && entry.prefab.name == metaData.PrefabOverrideName)
return entry.prefab;
}
}
// 按类型匹配
var typeMatch = FindPrefabByType(metaData.ValueType);
if (typeMatch != null) return typeMatch;
return defaultPrefab;
}
/// <summary>
/// 按预制体名称查找
/// </summary>
public GameObject FindPrefabByName(string prefabName)
{
foreach (var entry in typePrefabEntries)
{
if (entry.prefab != null && entry.prefab.name == prefabName)
return entry.prefab;
}
return null;
}
/// <summary>
/// 清空缓存
/// </summary>
public void ClearCache()
{
_typeCache = null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 31a97ff1112e7a84e9ade9ca81ea19a2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 值范围配置 ScriptableObject
/// 定义不同类型的空/无效/警告/合理值范围判断依据
/// </summary>
[CreateAssetMenu(
fileName = "Xeric UI avr Value Range Config",
menuName = "Xeric Library/UI Action Vessel/Reflection UI Gen/UI avr Value Range Config",
order = 25)]
public class AvrgValueRangeConfig : ScriptableObject
{
/// <summary>值范围规则列表</summary>
[Tooltip("按类型定义的取值范围规则")]
public List<AvrgValueRangeEntry> entries = new List<AvrgValueRangeEntry>();
[NonSerialized]
private Dictionary<string, AvrgValueRangeEntry> _entryCache;
private void BuildCache()
{
if (_entryCache != null) return;
_entryCache = new Dictionary<string, AvrgValueRangeEntry>();
foreach (var entry in entries)
{
if (!string.IsNullOrEmpty(entry.key) && !_entryCache.ContainsKey(entry.key))
_entryCache[entry.key] = entry;
}
}
/// <summary>
/// 根据键查找值范围规则
/// </summary>
/// <param name="key">类型标识(通常为 Type.FullName)</param>
/// <returns>匹配的规则,未找到则返回 null</returns>
public AvrgValueRangeEntry FindEntry(string key)
{
if (string.IsNullOrEmpty(key)) return null;
BuildCache();
_entryCache.TryGetValue(key, out var entry);
return entry;
}
/// <summary>
/// 根据 Type 查找值范围规则
/// </summary>
public AvrgValueRangeEntry FindEntryByType(Type type)
{
if (type == null) return null;
return FindEntry(type.FullName);
}
/// <summary>
/// 清空缓存(用于编辑器热重载后重新构建)
/// </summary>
public void ClearCache()
{
_entryCache = null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5a432178c6615374f89344e6e53c75dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,164 @@
using System;
using System.Collections.Generic;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 值范围规则条目
/// 定义某个类型(由 CheckKey 标识)的空/无效/警告/合理值范围
/// </summary>
[Serializable]
public class AvrgValueRangeEntry
{
/// <summary>
/// 类型标识符(默认使用 Type.FullName,如 "System.Single", "System.Int32")
/// 也支持自定义字符串以匹配特定语义
/// </summary>
public string key = "";
// ===== 空值范围 =====
/// <summary>
/// 是否跳过空值检查(如 bool 类型无法判断空)
/// </summary>
public bool skipEmptyCheck = false;
/// <summary>
/// 视为空的最小值(>= 此值视为空)
/// </summary>
public float emptyRangeMin = float.MinValue;
/// <summary>
/// 视为空的最大值(<= 此值视为空)
/// </summary>
public float emptyRangeMax = float.MaxValue;
// ===== 合理值范围(最高优先级) =====
/// <summary>
/// 合理值最小值
/// </summary>
public float reasonableRangeMin = float.MinValue;
/// <summary>
/// 合理值最大值
/// </summary>
public float reasonableRangeMax = float.MaxValue;
// ===== 无效值范围 =====
/// <summary>
/// 无效值最小值
/// </summary>
public float invalidRangeMin = float.MinValue;
/// <summary>
/// 无效值最大值
/// </summary>
public float invalidRangeMax = float.MaxValue;
// ===== 警告值范围 =====
/// <summary>
/// 警告值最小值
/// </summary>
public float warningRangeMin = float.MinValue;
/// <summary>
/// 警告值最大值
/// </summary>
public float warningRangeMax = float.MaxValue;
// ===== 字符串相关 =====
/// <summary>
/// 警告关键词列表(仅字符串类型使用)
/// </summary>
public List<string> warningKeywords = new List<string>();
// ===== 便捷判断方法 =====
/// <summary>
/// 将值转为 float 进行比较(bool→0/1, 数值→直接转, 字符串→长度)
/// </summary>
public static float ToComparableFloat(object value)
{
if (value == null) return float.NaN;
switch (value)
{
case float f: return f;
case double d: return (float)d;
case int i: return i;
case long l: return l;
case short s: return s;
case byte b: return b;
case uint ui: return ui;
case bool bVal: return bVal ? 1f : 0f;
case string str: return str.Length;
default: return float.NaN;
}
}
/// <summary>
/// 判断值是否为空
/// </summary>
public bool IsEmpty(object value)
{
if (skipEmptyCheck) return false;
// 引用类型 null
if (value == null) return true;
// 字符串 null/空
if (value is string str) return string.IsNullOrEmpty(str);
// 数值范围判断
float f = ToComparableFloat(value);
if (float.IsNaN(f)) return false; // 无法判断的类型跳过
return f >= emptyRangeMin && f <= emptyRangeMax;
}
/// <summary>
/// 判断值是否在合理范围内
/// </summary>
public bool IsReasonable(object value)
{
float f = ToComparableFloat(value);
if (float.IsNaN(f)) return false;
return f >= reasonableRangeMin && f <= reasonableRangeMax;
}
/// <summary>
/// 判断值是否在无效范围内
/// </summary>
public bool IsInvalid(object value)
{
float f = ToComparableFloat(value);
if (float.IsNaN(f)) return false;
return f >= invalidRangeMin && f <= invalidRangeMax;
}
/// <summary>
/// 判断值是否在警告范围内
/// </summary>
public bool IsWarning(object value)
{
// 先检查关键词(字符串)
if (value is string str && warningKeywords.Count > 0)
{
foreach (var kw in warningKeywords)
{
if (str.Contains(kw, StringComparison.OrdinalIgnoreCase))
return true;
}
}
// 数值范围
float f = ToComparableFloat(value);
if (float.IsNaN(f)) return false;
return f >= warningRangeMin && f <= warningRangeMax;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3e5066f03b4a03a4f8b2993f027f713b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 冲突解决器接口
/// 用户可实现此接口注册自定义冲突解决逻辑
/// </summary>
public interface IAvrgMvvmConflictResolver
{
/// <summary>
/// 尝试解决冲突
/// </summary>
/// <param name="binding">发生冲突的绑定</param>
/// <param name="defaultStrategy">当前配置的默认策略</param>
/// <param name="resolvedValue">返回解决后的值</param>
/// <returns>true 表示已解决,false 表示未处理(回退到默认策略)</returns>
bool TryResolve(AvrgMvvmBinding binding, ConflictResolutionStrategy defaultStrategy, out object resolvedValue);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7c4e7c4a0ce16c64082ee641e03ce5d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,21 +1,10 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.Pool;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// UI 字段标记提供者接口
/// 所有 Tag 增强的 UI 组件需实现此接口,便于 Entry 在绑定阶段自动匹配
/// </summary>
public interface IXuiavrgFieldTagProvider
{
XuiavrgFieldTag EntryTag { get; }
int TagIndex { get; }
}
/// <summary>
/// 字段 Entry 接口
/// 数据对象字段与 UI 组件之间的中间缓存层
@@ -49,12 +38,15 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>生成的 UI 实例引用</summary>
GameObject UIInstance { get; }
/// <summary>绑定的预制体组</summary>
XuiavrgPropertyGroupScriptableObject BoundPrefabGroup { get; set; }
/// <summary>绑定的预制体引用</summary>
GameObject BoundPrefab { get; set; }
/// <summary>覆盖的预制体组名称</summary>
string PrefabOverrideName { get; set; }
/// <summary>关联的 MVVM 绑定列表</summary>
IReadOnlyList<AvrgMvvmBinding> Bindings { get; }
// === 生命周期 ===
/// <summary>Entry 创建时调用</summary>
@@ -69,39 +61,22 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>销毁时调用</summary>
void OnEntryDestroy();
// === 数据访问 ===
// === 绑定管理 ===
/// <summary>获取指定成员的数据值</summary>
object GetDataValue(string memberName);
/// <summary>设置指定成员的数据值</summary>
void SetDataValue(string memberName, object value);
/// <summary>获取 Entry 下所有绑定成员的名称</summary>
IEnumerable<string> GetMemberNames();
/// <summary>添加 MVVM 绑定到此 Entry</summary>
void AddBinding(AvrgMvvmBinding binding, AvrgMvvmScheduler scheduler);
// === UI 绑定 ===
/// <summary>
/// 绑定 UI 组件
/// </summary>
/// <param name="uiInstance">UI 预制体实例</param>
/// <param name="componentCache">
/// 组件缓存列表。传入 null 时会自动收集 uiInstance 上所有 Component。
/// 父 Entry 可将已收集的缓存传递给子 Entry 以提升性能
/// </param>
void BindUIComponent(GameObject uiInstance, List<Component> componentCache);
}
/// <summary>
/// 字段临时 Entry 实现类
/// 作为数据对象字段和 UI 组件之间的中间缓存层
///
/// 支持:
/// - 多字段合并为一个 Entry(通过 XuiavrgFieldEntryGroupAttribute)
/// - 嵌套 Entry(父 Entry 包含子 Entry)
/// - 对象池复用(通过静态 ObjectPool)
/// - MVVM 双向绑定的生命周期
/// 作为数据对象和 UI 组件之间的中间缓存层
/// </summary>
public class XuiFieldTempEntry : IFieldEntry
{
@@ -130,73 +105,19 @@ namespace XericUI.ReflectionUIGenerator
#endregion
#region 内部绑定数据结构
/// <summary>
/// 成员反射绑定信息
/// </summary>
internal class MemberBinding
{
/// <summary>绑定的数据目标对象</summary>
public object valueTarget;
/// <summary>是否为属性(false 为字段)</summary>
public bool isProperty;
/// <summary>属性信息</summary>
public PropertyInfo propertyInfo;
/// <summary>字段信息</summary>
public FieldInfo fieldInfo;
/// <summary>功能标记</summary>
public XuiavrgFieldTag tag;
/// <summary>标记索引</summary>
public int tagIndex;
/// <summary>
/// 从绑定目标获取值
/// </summary>
public object GetValue()
{
if (valueTarget == null) return null;
return isProperty
? propertyInfo.GetValue(valueTarget)
: fieldInfo.GetValue(valueTarget);
}
/// <summary>
/// 设置值到绑定目标
/// </summary>
public void SetValue(object value)
{
if (valueTarget == null) return;
if (isProperty)
{
if (propertyInfo.CanWrite)
propertyInfo.SetValue(valueTarget, value);
}
else
{
fieldInfo.SetValue(valueTarget, value);
}
}
}
#endregion
#region 字段
private string _entryName;
private string _entryPath;
private IFieldEntry _parent;
private List<XuiFieldTempEntry> _children = new List<XuiFieldTempEntry>();
private Dictionary<string, MemberBinding> _bindings = new Dictionary<string, MemberBinding>();
private List<AvrgMvvmBinding> _bindings = new List<AvrgMvvmBinding>();
private AvrgMvvmScheduler _scheduler;
private GameObject _uiInstance;
private XuiavrgPropertyGroupScriptableObject _boundPrefabGroup;
private GameObject _boundPrefab;
private string _prefabOverrideName;
private int _fieldOrder;
private bool _uiBound; // 防止重复绑定
#endregion
@@ -237,10 +158,10 @@ namespace XericUI.ReflectionUIGenerator
public GameObject UIInstance => _uiInstance;
public XuiavrgPropertyGroupScriptableObject BoundPrefabGroup
public GameObject BoundPrefab
{
get => _boundPrefabGroup;
set => _boundPrefabGroup = value;
get => _boundPrefab;
set => _boundPrefab = value;
}
public string PrefabOverrideName
@@ -249,6 +170,8 @@ namespace XericUI.ReflectionUIGenerator
set => _prefabOverrideName = value;
}
public IReadOnlyList<AvrgMvvmBinding> Bindings => _bindings;
#endregion
#region 初始化方法
@@ -263,6 +186,7 @@ namespace XericUI.ReflectionUIGenerator
Tag = tag;
TagIndex = tagIndex;
_fieldOrder = 0;
_uiBound = false;
}
/// <summary>
@@ -271,7 +195,7 @@ namespace XericUI.ReflectionUIGenerator
public void SetParent(IFieldEntry parent)
{
_parent = parent;
_entryPath = null; // 重置路径,下次访问重新计算
_entryPath = null;
}
/// <summary>
@@ -285,98 +209,19 @@ namespace XericUI.ReflectionUIGenerator
}
/// <summary>
/// 注册成员绑定
/// 添加 MVVM 绑定到此 Entry
/// </summary>
/// <param name="memberName">成员名称</param>
/// <param name="memberInfo">反射信息</param>
/// <param name="target">数据目标对象</param>
/// <param name="tag">功能标记</param>
/// <param name="tagIndex">标记索引</param>
public void RegisterBinding(string memberName, MemberInfo memberInfo, object target,
XuiavrgFieldTag tag = XuiavrgFieldTag.None, int tagIndex = 0)
public void AddBinding(AvrgMvvmBinding binding, AvrgMvvmScheduler scheduler)
{
MemberBinding binding = new MemberBinding
{
valueTarget = target,
tag = tag,
tagIndex = tagIndex
};
if (binding == null) return;
_bindings.Add(binding);
_scheduler = scheduler;
switch (memberInfo)
if (Tag == XuiavrgFieldTag.None && binding.MetaData?.Tag != XuiavrgFieldTag.None)
{
case PropertyInfo propInfo:
binding.isProperty = true;
binding.propertyInfo = propInfo;
break;
case FieldInfo fieldInfo:
binding.isProperty = false;
binding.fieldInfo = fieldInfo;
break;
Tag = binding.MetaData.Tag;
TagIndex = binding.MetaData.TagIndex;
}
_bindings[memberName] = binding;
// 如果是第一个注册的绑定,设置主 Tag
if (Tag == XuiavrgFieldTag.None && tag != XuiavrgFieldTag.None)
{
Tag = tag;
TagIndex = tagIndex;
}
}
#endregion
#region 数据访问
public object GetDataValue(string memberName)
{
if (_bindings.TryGetValue(memberName, out var binding))
return binding.GetValue();
// 查找子 Entry
foreach (var child in _children)
{
var value = child.GetDataValue(memberName);
if (value != null) return value;
}
return null;
}
public void SetDataValue(string memberName, object value)
{
if (_bindings.TryGetValue(memberName, out var binding))
{
binding.SetValue(value);
return;
}
// 查找子 Entry
foreach (var child in _children)
{
if (child.HasBinding(memberName))
{
child.SetDataValue(memberName, value);
return;
}
}
}
public IEnumerable<string> GetMemberNames() => _bindings.Keys;
/// <summary>
/// 检查是否包含指定成员绑定
/// </summary>
public bool HasBinding(string memberName) => _bindings.ContainsKey(memberName);
/// <summary>
/// 获取指定成员的标记
/// </summary>
public XuiavrgFieldTag GetMemberTag(string memberName)
{
if (_bindings.TryGetValue(memberName, out var binding))
return binding.tag;
return XuiavrgFieldTag.None;
}
#endregion
@@ -386,7 +231,7 @@ namespace XericUI.ReflectionUIGenerator
public void BindUIComponent(GameObject uiInstance, List<Component> componentCache)
{
_uiInstance = uiInstance;
if (_uiInstance == null) return;
if (_uiInstance == null || _uiBound) return;
if (componentCache == null)
{
@@ -394,183 +239,131 @@ namespace XericUI.ReflectionUIGenerator
_uiInstance.GetComponentsInChildren(true, componentCache);
}
BindUIComponentWithCache(_uiInstance, componentCache);
// 遍历组件缓存,寻找 XUIAvrgMDValue 按 Tag 匹配
foreach (var comp in componentCache)
{
if (comp is XUIAvrgMDValue mdValue)
{
if (mdValue.EntryTag == this.Tag &&
(this.TagIndex == 0 || mdValue.TagIndex == this.TagIndex))
{
BindComponentWithBinding(mdValue);
}
}
}
// 子 Entry 复用组件缓存
foreach (var child in _children)
{
child.BindUIComponent(_uiInstance, componentCache);
}
}
private void BindUIComponentWithCache(GameObject uiInstance, List<Component> componentCache)
{
// 遍历组件缓存,匹配 tag
var locators = new List<XuiavrgLocator>();
foreach (var comp in componentCache)
{
if (comp is XuiavrgLocator locator)
{
locators.Add(locator);
}
if (comp is IXuiavrgFieldTagProvider tagProvider)
{
// 匹配 Tag 和 TagIndex
if (tagProvider.EntryTag == this.Tag &&
(this.TagIndex == 0 || tagProvider.TagIndex == this.TagIndex))
{
// 找到匹配的 UI 组件,建立绑定
BuildComponentBinding(comp);
}
}
}
// 如果没有 IXuiavrgFieldTagProvider,尝试通过 Locator 匹配
if (locators.Count > 0)
{
BuildLocatorBindings(locators);
}
_uiBound = true;
}
/// <summary>
/// 通过标签提供者建立组件绑定
/// 将 XUIAvrgMDValue 与 MVVM 绑定连接
/// </summary>
private void BuildComponentBinding(Component comp)
private void BindComponentWithBinding(XUIAvrgMDValue mdValue)
{
// 根据组件类型和标记类型进行数据同步
switch (comp)
{
case UnityEngine.UI.Text uiText when Tag == XuiavrgFieldTag.Label || Tag == XuiavrgFieldTag.Output:
uiText.text = GetFirstBindingValue()?.ToString() ?? "";
break;
case UnityEngine.UI.Button _ when Tag == XuiavrgFieldTag.Button:
// 按钮绑定在 BindUIEvents 中处理
break;
case UnityEngine.UI.Toggle uiToggle when Tag == XuiavrgFieldTag.Toggle:
if (GetFirstBindingValue() is bool boolVal)
uiToggle.isOn = boolVal;
break;
case UnityEngine.UI.Slider uiSlider when Tag == XuiavrgFieldTag.Slider:
if (GetFirstBindingValue() is float floatVal)
uiSlider.value = floatVal;
break;
case UnityEngine.UI.InputField uiInput when Tag == XuiavrgFieldTag.Input:
uiInput.text = GetFirstBindingValue()?.ToString() ?? "";
break;
case UnityEngine.UI.Dropdown uiDropdown when Tag == XuiavrgFieldTag.Dropdown:
if (GetFirstBindingValue() is int intVal)
uiDropdown.value = intVal;
break;
}
}
if (_bindings.Count == 0) return;
/// <summary>
/// 通过 Locator 建立绑定
/// </summary>
private void BuildLocatorBindings(List<XuiavrgLocator> locators)
{
foreach (var locator in locators)
var primaryBinding = _bindings[0];
// 数据 → UI:订阅数据变更
primaryBinding.OnUIDataChanged += value => UpdateUIComponent(mdValue, value);
// UI → 数据:订阅 UI 交互事件
// 根据组件类型决定 UI 事件
if (mdValue.TryGetComponent(out UnityEngine.UI.Button uiButton))
{
foreach (var locProp in locator.Properties)
uiButton.onClick.AddListener(() =>
{
// 按 propertyName 匹配成员名
if (_bindings.TryGetValue(locProp.propertyName, out var binding))
{
// 从数据端同步到 UI
var dataValue = binding.GetValue();
locProp.SetValue(dataValue);
}
}
primaryBinding.WriteFromUI(true);
_scheduler?.MarkUIDirty(primaryBinding);
});
}
}
/// <summary>
/// 获取第一个绑定的值
/// </summary>
private object GetFirstBindingValue()
{
foreach (var kvp in _bindings)
if (mdValue.TryGetComponent(out UnityEngine.UI.Toggle uiToggle))
{
var val = kvp.Value.GetValue();
if (val != null) return val;
}
return null;
}
#endregion
#region 事件绑定
/// <summary>
/// 绑定 UI 交互事件(双向绑定)
/// </summary>
public void BindUIEvents()
{
if (_uiInstance == null) return;
var allComponents = new List<Component>();
_uiInstance.GetComponentsInChildren(true, allComponents);
foreach (var comp in allComponents)
{
if (comp is IXuiavrgFieldTagProvider tagProvider)
uiToggle.onValueChanged.AddListener(val =>
{
if (tagProvider.EntryTag != this.Tag) continue;
BindComponentEvents(comp);
}
primaryBinding.WriteFromUI(val);
_scheduler?.MarkUIDirty(primaryBinding);
});
}
// 子 Entry 事件绑定
foreach (var child in _children)
if (mdValue.TryGetComponent(out UnityEngine.UI.Slider uiSlider))
{
child.BindUIEvents();
uiSlider.onValueChanged.AddListener(val =>
{
primaryBinding.WriteFromUI(val);
_scheduler?.MarkUIDirty(primaryBinding);
});
}
if (mdValue.TryGetComponent(out UnityEngine.UI.InputField uiInput))
{
uiInput.onEndEdit.AddListener(val =>
{
primaryBinding.WriteFromUI(val);
_scheduler?.MarkUIDirty(primaryBinding);
});
}
if (mdValue.TryGetComponent(out UnityEngine.UI.Dropdown uiDropdown))
{
uiDropdown.onValueChanged.AddListener(val =>
{
primaryBinding.WriteFromUI(val);
_scheduler?.MarkUIDirty(primaryBinding);
});
}
// 广播元数据到 XUIAvrgMDValue(触发 IXUIAvrgMDValueStyle 子组件)
mdValue.InitializeWithMetaData(primaryBinding.MetaData);
}
private void BindComponentEvents(Component comp)
/// <summary>
/// 更新 UI 组件的显示值(通过 XUIAvrgMDValue 所在 GameObject 上的具体组件)
/// </summary>
private static void UpdateUIComponent(XUIAvrgMDValue mdValue, object value)
{
switch (comp)
{
case UnityEngine.UI.Button uiButton:
uiButton.onClick.AddListener(() =>
{
SetFirstBindingValue(true);
});
break;
case UnityEngine.UI.Toggle uiToggle:
uiToggle.onValueChanged.AddListener(val =>
{
SetFirstBindingValue(val);
});
break;
case UnityEngine.UI.Slider uiSlider:
uiSlider.onValueChanged.AddListener(val =>
{
SetFirstBindingValue(val);
});
break;
case UnityEngine.UI.InputField uiInput:
uiInput.onEndEdit.AddListener(val =>
{
SetFirstBindingValue(val);
});
break;
case UnityEngine.UI.Dropdown uiDropdown:
uiDropdown.onValueChanged.AddListener(val =>
{
SetFirstBindingValue(val);
});
break;
}
}
if (mdValue == null || value == null) return;
private void SetFirstBindingValue(object value)
{
foreach (var kvp in _bindings)
// Text
if (mdValue.TryGetComponent(out UnityEngine.UI.Text uiText))
{
kvp.Value.SetValue(value);
break;
uiText.text = value.ToString();
return;
}
// Toggle
if (value is bool boolVal && mdValue.TryGetComponent(out UnityEngine.UI.Toggle uiToggle))
{
uiToggle.isOn = boolVal;
return;
}
// Slider
if (value is float floatVal && mdValue.TryGetComponent(out UnityEngine.UI.Slider uiSlider))
{
uiSlider.value = floatVal;
return;
}
// InputField
if (mdValue.TryGetComponent(out UnityEngine.UI.InputField uiInput))
{
uiInput.text = value.ToString();
return;
}
// Dropdown
if (value is int intVal && mdValue.TryGetComponent(out UnityEngine.UI.Dropdown uiDropdown))
{
uiDropdown.value = intVal;
return;
}
}
@@ -585,11 +378,16 @@ namespace XericUI.ReflectionUIGenerator
_parent = null;
_fieldOrder = 0;
_prefabOverrideName = null;
_uiBound = false;
}
public void OnEntryBind()
{
BindUIEvents();
foreach (var binding in _bindings)
{
var value = binding.ReadFromDataSource();
binding.NotifyUIUpdate(value);
}
}
public void OnEntryRelease()
@@ -598,16 +396,20 @@ namespace XericUI.ReflectionUIGenerator
_entryPath = null;
_parent = null;
_uiInstance = null;
_boundPrefabGroup = null;
_boundPrefab = null;
_prefabOverrideName = null;
Tag = XuiavrgFieldTag.None;
TagIndex = 0;
_fieldOrder = 0;
_uiBound = false;
// 清理绑定引用
foreach (var binding in _bindings)
{
binding.ClearUIEvent();
}
_bindings.Clear();
_scheduler = null;
// 递归释放子 Entry
foreach (var child in _children)
{
Release(child);
@@ -617,9 +419,14 @@ namespace XericUI.ReflectionUIGenerator
public void OnEntryDestroy()
{
foreach (var binding in _bindings)
{
binding.ClearUIEvent();
}
_bindings.Clear();
_children.Clear();
_uiInstance = null;
_scheduler = null;
}
#endregion
@@ -1,107 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector;
#endif
using UnityEngine;
using XericLibrary.Runtime.MacroLibrary;
using Object = UnityEngine.Object;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// [已弃用] 属性映射器。请使用 XuiavrgPropertyGroupScriptableObject 替代。
/// 功能已整合到 PropertyGroup 中,此类保留仅用于向后兼容。
/// </summary>
[Serializable]
[Obsolete("Use XuiavrgPropertyGroupScriptableObject instead. Property mapping is now integrated directly into the group.")]
public class XuiavrPropertyScriptableObject : ScriptableObject
{
#if ODIN_INSPECTOR
[LabelText("属性类型")]
[ValueDropdown("AssemblyValueTypeNames")]
#endif
public string valueType;
#if ODIN_INSPECTOR
[LabelText("属性名称")]
#endif
public string propertyName;
#if ODIN_INSPECTOR
[LabelText("界面预制体")]
#endif
public GameObject prefab;
/// <summary>
/// 属性类型
/// </summary>
public System.Type ValueType
{
get
{
if (_valueType != null)
return _valueType;
return _valueType = Type.GetType(valueType);
}
}
private System.Type _valueType;
#if ODIN_INSPECTOR // 属性类型名称枚举
private IEnumerable AssemblyValueTypeNames
{
get
{
if (_assemblyValueTypeName != null)
return _assemblyValueTypeName;
_assemblyValueTypeName = new ValueDropdownList<string>();
// foreach (var typeName in MacroReflection.GetAllAssignableTypeInAppDomainAssembly(
// typeof(float),
// typeof(string),
// typeof(bool),
// typeof(Enum),
// typeof(int)
// ).Select(a => a.FullName))
// {
// _assemblyValueTypeName.Add(typeName);
// }
_assemblyValueTypeName.AddRange(AllValueType.Select(a => new ValueDropdownItem<string>(a.Name, a.FullName)));
return _assemblyValueTypeName;
}
}
private static ValueDropdownList<string> _assemblyValueTypeName = null;
#endif
/// <summary>
/// 直接定义所有需要检查的类型
/// </summary>
public static IEnumerable<Type> AllValueType
{
get
{
yield return typeof(Enum);
yield return typeof(decimal);
yield return typeof(double);
yield return typeof(float);
yield return typeof(ulong);
yield return typeof(long);
yield return typeof(int);
yield return typeof(uint);
yield return typeof(short);
yield return typeof(ushort);
yield return typeof(byte);
yield return typeof(sbyte);
yield return typeof(string);
yield return typeof(char);
yield return typeof(bool);
}
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: be0acbdac8e549578dcccf8c076a5433
timeCreated: 1776998687
@@ -1,133 +0,0 @@
using System;
using System.Collections.Generic;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 测试用枚举 - 用于下拉选择测试
/// </summary>
public enum XuiavrgTestQuality
{
Low,
Medium,
High,
Epic,
Legendary
}
/// <summary>
/// 反射界面生成器测试数据类
/// 涵盖新版 Entry 体系的所有特性:编组、排序、预制体覆盖、多种字段类型
///
/// 使用方式:
/// 1. 在 Hierarchy 右键 -&gt; Xeric UI -&gt; Create Reflection UI Example 创建示例节点
/// 2. 创建 XuiavrgPropertyGroupScriptableObject 并设置 groupName 和 supportedTags
/// 3. 将编组对应的预制体拖入
/// 4. 调用 manager.BuildEntries(new XuiavrgExampleTestData()) 构建 Entry
/// 5. 调用 manager.GenerateUI() 生成界面
/// 或直接使用兼容方法: manager.SchemaHydration(new XuiavrgExampleTestData())
/// </summary>
[Serializable]
public class XuiavrgExampleTestData
{
// ==================== 基础数值类型 ====================
[XuiavrgFieldOrder(1)]
[XuiavrgNumberCheck(0, 100)]
public int intValue = 42;
[XuiavrgFieldOrder(2)]
[XuiavrgNumberCheck(0f, 999f)]
public float floatValue = 3.14f;
[XuiavrgFieldOrder(3)]
public double doubleValue = 123.456;
[XuiavrgFieldOrder(4)]
[XuiavrgNumberCheck(0, 999999)]
public long longValue = 100000;
// ==================== 多字段编组示例 ====================
// 以下三个字段合并为一个 Entry,共享一个 UI 组件实例
// UI 组件上需要有 Label、Context、Unit 对应的 IXuiavrgFieldTagProvider
[XuiavrgFieldEntryGroup("speed", XuiavrgFieldTag.Label)]
[XuiavrgFieldOrder(5)]
public string speedLabel = "速度";
[XuiavrgFieldEntryGroup("speed", XuiavrgFieldTag.Context)]
[XuiavrgNumberCheck(-999f, 999f)]
public float speedValue = 0.1f;
[XuiavrgFieldEntryGroup("speed", XuiavrgFieldTag.Unit)]
public string speedUnit = "m/s";
// 第二个编组示例 - 健康值
[XuiavrgFieldEntryGroup("health", XuiavrgFieldTag.Label)]
[XuiavrgFieldOrder(6)]
public string healthLabel = "生命值";
[XuiavrgFieldEntryGroup("health", XuiavrgFieldTag.Context)]
[XuiavrgNumberCheck(0, 9999)]
public float healthValue = 100f;
[XuiavrgFieldEntryGroup("health", XuiavrgFieldTag.Unit)]
public string healthUnit = "HP";
// ==================== 字符串类型 ====================
[XuiavrgFieldOrder(7)]
[XuiavrgRequired]
[XuiavrgStringCheck(minLength: 2, maxLength: 16)]
public string playerName = "DefaultName";
[XuiavrgFieldOrder(8)]
[XuiavrgNotEmpty]
[XuiavrgStringCheck(onlyEmail: false)]
public string emailAddress = "example@mail.com";
[XuiavrgFieldOrder(9)]
[XuiavrgFieldPrefabOverride("MultilineTextArea")]
[XuiavrgStringCheck(maxLength: 500)]
public string biography = "这是一段个人简介...";
// ==================== 布尔类型 ====================
[XuiavrgFieldOrder(10)]
public bool enableFeature = true;
[XuiavrgFieldOrder(11)]
public bool advancedMode = false;
// ==================== 枚举类型 ====================
[XuiavrgFieldOrder(12)]
public XuiavrgTestQuality quality = XuiavrgTestQuality.High;
// ==================== 隐藏属性 ====================
[XuiavrgHide(true)]
public string hiddenField = "此字段被隐藏,不会生成 Entry";
// ==================== 不同尺寸 ====================
[XuiavrgFieldOrder(13)]
[XuiavrgAttribute("小号", "size_small", null, size: XuiavrgSize.Small)]
public string smallSizeField = "小尺寸";
[XuiavrgFieldOrder(14)]
[XuiavrgAttribute("大号", "size_large", null, size: XuiavrgSize.Large)]
public string largeSizeField = "大尺寸";
// ==================== 集合类型(兼容旧标记) ====================
[XuiavrgFieldOrder(15)]
[XuiavrgCollectionCheck(1, 10, true)]
public List<string> tags = new List<string> { "Unity", "C#", "UI" };
// ==================== 按钮触发器 ====================
[XuiavrgFieldOrder(16)]
public bool submitTrigger = false;
}
}
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.Pool;
using Object = UnityEngine.Object;
@@ -9,41 +7,22 @@ using Object = UnityEngine.Object;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 节点下生成反射属性管理器
/// 节点下生成反射属性管理器 — Metadata-Driven View Model (MDVM 元数据驱动的视图数据模型)
/// 负责:构建 Entry → 创建组容器 → 生成 UI → 绑定数据
/// </summary>
/// <remarks>
/// 管理器需要挂载在 Unity 节点中,管理器中指定的属性承载节点将用于生成属性内容。
///
/// 新的生命周期流程:
/// 1. BuildEntries(object payload) — 传入数据对象,构建 Entry 列表
/// 2. OnPostBuildEntries(entries) — 虚方法,可重写添加额外 Entry
/// 3. 自动按 FieldOrder 排序
/// 4. GenerateUI() — 根据 Entry 列表生成 UI 实例并绑定
///
/// 对象池说明:
/// - Entry 通过 XuiFieldTempEntry.Pool 静态池管理
/// - UI 预制体通过内部 PropertyGroupPools 字典管理,以 PropertyGroupScriptableObject 为键
/// </remarks>
public class XuiavrgInspectorManager : MonoBehaviour
{
#region 对象池
/// <summary>
/// UI 预制体对象池,以 PropertyGroupScriptableObject 为键
/// </summary>
private Dictionary<XuiavrgPropertyGroupScriptableObject, ObjectPool<GameObject>> _propertyGroupPools
= new Dictionary<XuiavrgPropertyGroupScriptableObject, ObjectPool<GameObject>>();
private Dictionary<GameObject, ObjectPool<GameObject>> _prefabPools
= new Dictionary<GameObject, ObjectPool<GameObject>>();
/// <summary>
/// 从指定 PropertyGroup 的预制体池获取 UI 实例
/// </summary>
public GameObject PropertyGroupPoolGet(XuiavrgPropertyGroupScriptableObject group)
public GameObject PrefabPoolGet(GameObject prefab)
{
if (group == null) return null;
if (prefab == null) return null;
if (!_propertyGroupPools.TryGetValue(group, out var pool))
if (!_prefabPools.TryGetValue(prefab, out var pool))
{
var prefab = group.prefab;
pool = new ObjectPool<GameObject>(
createFunc: () =>
prefab != null
@@ -59,19 +38,16 @@ namespace XericUI.ReflectionUIGenerator
defaultCapacity: 4,
maxSize: 64
);
_propertyGroupPools[group] = pool;
_prefabPools[prefab] = pool;
}
return pool.Get();
}
/// <summary>
/// 将 UI 实例归还到指定 PropertyGroup 的预制体池
/// </summary>
public void PropertyGroupPoolRelease(XuiavrgPropertyGroupScriptableObject group, GameObject instance)
public void PrefabPoolRelease(GameObject prefab, GameObject instance)
{
if (group == null || instance == null) return;
if (_propertyGroupPools.TryGetValue(group, out var pool))
if (prefab == null || instance == null) return;
if (_prefabPools.TryGetValue(prefab, out var pool))
{
pool.Release(instance);
}
@@ -81,219 +57,109 @@ namespace XericUI.ReflectionUIGenerator
}
}
/// <summary>
/// 清空所有预制体对象池
/// </summary>
public void ClearAllPropertyGroupPools()
public void ClearAllPrefabPools()
{
foreach (var pool in _propertyGroupPools.Values)
foreach (var pool in _prefabPools.Values)
{
pool.Clear();
}
_propertyGroupPools.Clear();
_prefabPools.Clear();
}
#endregion
#region 字段
[SerializeField]
[Tooltip("当前使用的 UI 主题名称。切换主题会立即销毁所有已生成 UI 和对象池,需重新 BuildEntries + GenerateUI")]
private string _currentTheme = XuiavrgPropertyGroupScriptableObject.DefaultThemeName;
/// <summary>
/// 当前 UI 主题名称
/// </summary>
public string CurrentTheme => _currentTheme;
/// <summary>
/// 当前活跃的 Entry 列表
/// </summary>
public List<IFieldEntry> CurrentEntries { get; private set; } = new List<IFieldEntry>();
public AvrgMvvmBindingCollection CurrentBindingCollection { get; private set; }
public AvrgMemberMetaDataSet CurrentMetaDataSet { get; private set; }
/// <summary>
/// 当前传入的数据负载
/// 当前活跃的组容器字典
/// </summary>
public object CurrentPayload { get; private set; }
private Dictionary<string, XuiAvrgMDGroup> _activeGroups = new Dictionary<string, XuiAvrgMDGroup>();
/// <summary>
/// 切换 UI 主题。会立即销毁所有已生成 UI 和对象池。
/// 调用后需重新 BuildEntries + GenerateUI。
/// </summary>
/// <param name="themeName">新的主题名称</param>
public void SetTheme(string themeName)
{
if (_currentTheme == themeName) return;
_currentTheme = string.IsNullOrEmpty(themeName)
? XuiavrgPropertyGroupScriptableObject.DefaultThemeName
: themeName;
// 销毁所有已生成 UI 和对象池
ClearAll();
}
[Tooltip("类型-预制体映射配置 ScriptableObject")]
public AvrgTypePrefabMappingConfig typePrefabMappingConfig;
#endregion
#region 核心生命周期
/// <summary>
/// 传入对象实例,构建 Entry 列表
/// </summary>
/// <param name="payload">数据端对象</param>
public void BuildEntries(object payload)
public void BuildFromMetaDataSet(AvrgMemberMetaDataSet metaDataSet,
AvrgMvvmBindingCollection bindingCollection = null)
{
// 清空之前的 Entry
ClearCurrentEntries();
CurrentPayload = payload;
CurrentMetaDataSet = metaDataSet;
CurrentBindingCollection = bindingCollection;
if (payload == null)
if (metaDataSet == null)
{
Debug.LogError("[Xuiavrg] 未指定有效负载,无法构建 Entry");
Debug.LogError("[Xuiavrg] 未指定元数据集,无法构建 Entry");
return;
}
var type = payload.GetType();
var memberInfos = type.GetRuntimeMemberInfo().ToList();
// 用于合并编组 Entry 的临时字典
var groupEntries = new Dictionary<string, XuiFieldTempEntry>();
foreach (var memberInfo in memberInfos)
foreach (var metaData in metaDataSet.MetaDataList)
{
// 检查隐藏标记
var hideAttr = memberInfo.GetCustomAttribute<XuiavrgHideAttribute>();
if (hideAttr != null && hideAttr.Hide)
if (metaData.Hide)
continue;
// 检查编组标记
var groupAttr = memberInfo.GetCustomAttribute<XuiavrgFieldEntryGroupAttribute>();
var entry = XuiFieldTempEntry.Get();
entry.Initialize(
string.IsNullOrEmpty(metaData.GroupPath) ? metaData.MemberName : metaData.GroupPath,
metaData.Tag,
metaData.TagIndex);
if (groupAttr != null)
if (!string.IsNullOrEmpty(metaData.PrefabOverrideName))
entry.PrefabOverrideName = metaData.PrefabOverrideName;
if (bindingCollection != null)
{
// 编组字段:合并到同名 Entry
if (!groupEntries.TryGetValue(groupAttr.GroupPath, out var groupEntry))
var matchedBindings = bindingCollection.FindByMemberName(metaData.MemberName);
if (matchedBindings != null)
{
groupEntry = XuiFieldTempEntry.Get();
groupEntry.Initialize(groupAttr.GroupPath);
groupEntries[groupAttr.GroupPath] = groupEntry;
CurrentEntries.Add(groupEntry);
}
groupEntry.RegisterBinding(
memberInfo.Name, memberInfo, payload,
groupAttr.Tag, groupAttr.TagIndex);
}
else
{
// 普通字段:创建独立 Entry
var entry = XuiFieldTempEntry.Get();
entry.Initialize(memberInfo.Name);
entry.RegisterBinding(memberInfo.Name, memberInfo, payload);
// 检查预制体覆盖
var overrideAttr = memberInfo.GetCustomAttribute<XuiavrgFieldPrefabOverrideAttribute>();
if (overrideAttr != null)
{
entry.PrefabOverrideName = overrideAttr.PrefabGroupName;
}
CurrentEntries.Add(entry);
}
// 应用排序标记
var orderAttr = memberInfo.GetCustomAttribute<XuiavrgFieldOrderAttribute>();
if (orderAttr != null)
{
// 找到该成员对应的 Entry(可能是编组 Entry)
if (groupAttr != null && groupEntries.TryGetValue(groupAttr.GroupPath, out var gEntry))
{
if (gEntry.FieldOrder == 0 || orderAttr.Order < gEntry.FieldOrder)
gEntry.FieldOrder = orderAttr.Order;
entry.AddBinding(matchedBindings, null);
}
else
{
// 普通字段的排序应用到其 Entry
var normalEntry = CurrentEntries.LastOrDefault();
if (normalEntry != null)
normalEntry.FieldOrder = orderAttr.Order;
foreach (var binding in bindingCollection)
{
entry.AddBinding(binding, null);
break;
}
}
}
// 应用旧的 IXuiavrgAttribute(兼容)
ApplyLegacyAttributes(memberInfo, payload, groupAttr, groupEntries);
entry.FieldOrder = metaData.FieldOrder;
CurrentEntries.Add(entry);
}
// 调用后期回调
OnPostBuildEntries(CurrentEntries);
// 排序
SortEntries();
}
/// <summary>
/// 应用旧的 IXuiavrgAttribute 体系中的属性(兼容保留)
/// </summary>
private void ApplyLegacyAttributes(
MemberInfo memberInfo, object payload,
XuiavrgFieldEntryGroupAttribute groupAttr,
Dictionary<string, XuiFieldTempEntry> groupEntries)
public void SetBindingCollection(AvrgMvvmBindingCollection bindingCollection)
{
// 处理旧的 XuiavrgAttribute(设置 labelName, inspectorOrder 等)
var oldAttr = memberInfo.GetCustomAttribute<XuiavrgAttribute>();
if (oldAttr != null)
CurrentBindingCollection = bindingCollection;
if (CurrentEntries != null && bindingCollection != null)
{
// 创建临时的 InspectorProperty 来接收绑定
var tempProp = new XuiavrgInspectorProperty();
tempProp.RegsiterDataValue(memberInfo, payload);
oldAttr.BindProperty(tempProp);
// 将相关属性同步到 Entry
IFieldEntry targetEntry;
if (groupAttr != null && groupEntries.TryGetValue(groupAttr.GroupPath, out var gEntry))
targetEntry = gEntry;
else
targetEntry = CurrentEntries.LastOrDefault(e => !e.IsGroupEntry || e == null) ?? CurrentEntries.LastOrDefault();
if (targetEntry != null)
foreach (var entry in CurrentEntries)
{
if (tempProp.inspectorOrder != 0 && targetEntry.FieldOrder == 0)
targetEntry.FieldOrder = tempProp.inspectorOrder;
targetEntry.PrefabOverrideName ??= tempProp.uiPropertyName;
if (entry is XuiFieldTempEntry tempEntry)
{
var matched = bindingCollection.FindByMemberName(entry.EntryName);
if (matched != null)
tempEntry.AddBinding(matched, null);
}
}
}
// 处理其他旧的验证 Attribute
foreach (var attr in memberInfo.GetCustomAttributes().OfType<IXuiavrgAttribute>())
{
if (attr is XuiavrgAttribute) continue; // 已处理
if (attr is XuiavrgHideAttribute) continue; // 已处理
if (attr is XuiavrgFieldEntryGroupAttribute) continue; // 已处理
if (attr is XuiavrgFieldOrderAttribute) continue; // 已处理
if (attr is XuiavrgFieldPrefabOverrideAttribute) continue; // 已处理
// 其他旧 Attribute(Required, StringCheck, NumberCheck 等)
var tempProp = new XuiavrgInspectorProperty();
tempProp.RegsiterDataValue(memberInfo, payload);
attr.BindProperty(tempProp);
// 这些验证信息目前由 InspectorProperty 保留,后续可迁移到 Entry
}
}
/// <summary>
/// 生成后期回调(可重写)
/// 在此方法中可以手动添加额外的 Entry(如 Preview 结构、不在数据对象中的内容)
/// </summary>
/// <param name="entries">当前 Entry 列表</param>
protected virtual void OnPostBuildEntries(List<IFieldEntry> entries)
{
// 默认无操作,子类可重写
}
/// <summary>
/// 对 Entry 列表按 FieldOrder 排序
/// </summary>
private void SortEntries()
{
CurrentEntries.Sort((a, b) =>
@@ -304,106 +170,181 @@ namespace XericUI.ReflectionUIGenerator
});
}
// ============ 三阶段 GenerateUI ============
/// <summary>
/// 根据 Entry 列表生成 UI
/// 生成 UI — 三阶段流程
/// Phase A: 按 GroupPath 分组
/// Phase B: 创建 XuiAvrgMDGroup 组容器
/// Phase C: 在组容器内生成 UI 组件并绑定
/// </summary>
public void GenerateUI()
{
if (CurrentEntries == null || CurrentEntries.Count == 0)
{
Debug.LogWarning("[Xuiavrg] 没有可生成的 Entry,请先调用 BuildEntries");
Debug.LogWarning("[Xuiavrg] 没有可生成的 Entry,请先调用 BuildFromMetaDataSet");
return;
}
// Phase A: 收集并分组
var groupedEntries = new Dictionary<string, List<IFieldEntry>>();
var ungroupedEntries = new List<IFieldEntry>();
foreach (var entry in CurrentEntries)
{
if (entry is not XuiFieldTempEntry tempEntry) continue;
string groupKey = !string.IsNullOrEmpty(entry.EntryName)
? GetGroupPathFromEntry(entry)
: null;
// 查找匹配的 PropertyGroup
var group = ResolvePropertyGroup(entry);
if (group == null)
if (!string.IsNullOrEmpty(groupKey))
{
Debug.LogWarning($"[Xuiavrg] 未找到 Entry '{entry.EntryName}' 的匹配预制体组,跳过");
continue;
if (!groupedEntries.ContainsKey(groupKey))
groupedEntries[groupKey] = new List<IFieldEntry>();
groupedEntries[groupKey].Add(entry);
}
else
{
ungroupedEntries.Add(entry);
}
}
entry.BoundPrefabGroup = group;
// Phase B: 创建组容器
_activeGroups.Clear();
foreach (var kvp in groupedEntries)
{
string groupPath = kvp.Key;
var entries = kvp.Value;
// 从对象池获取 UI 实例
var uiInstance = PropertyGroupPoolGet(group);
if (uiInstance == null) continue;
// 检查是否已创建(相同 GroupPath 只创建一个容器)
if (_activeGroups.ContainsKey(groupPath)) continue;
// 设置父节点和命名
uiInstance.name = entry.EntryName;
uiInstance.transform.SetParent(transform, false);
var groupGo = new GameObject($"Group [{groupPath}]", typeof(RectTransform));
groupGo.transform.SetParent(transform, false);
// 绑定 UI 组件(首次绑定不传缓存,由 Entry 内部收集)
entry.BindUIComponent(uiInstance, null);
var rect = groupGo.GetComponent<RectTransform>();
rect.anchorMin = new Vector2(0, 1);
rect.anchorMax = new Vector2(1, 1);
rect.pivot = new Vector2(0.5f, 1);
rect.sizeDelta = new Vector2(0, 0);
// 绑定事件
entry.OnEntryBind();
var group = groupGo.AddComponent<XuiAvrgMDGroup>();
int firstOrder = entries.Count > 0 ? entries[0].FieldOrder : 0;
group.Initialize(groupPath, firstOrder);
_activeGroups[groupPath] = group;
}
// Phase C: 生成 UI 组件
foreach (var kvp in groupedEntries)
{
string groupPath = kvp.Key;
var entries = kvp.Value;
if (!_activeGroups.TryGetValue(groupPath, out var group)) continue;
foreach (var entry in entries)
{
GenerateSingleUI(entry, group.contentRoot);
}
}
foreach (var entry in ungroupedEntries)
{
GenerateSingleUI(entry, transform);
}
}
/// <summary>
/// 解析 Entry 对应的 PropertyGroup
/// 优先使用 PrefabOverrideName,否则按 Tag 匹配,所有查找在 currentTheme 下进行
/// 生成单个 Entry 的 UI
/// </summary>
private XuiavrgPropertyGroupScriptableObject ResolvePropertyGroup(IFieldEntry entry)
private void GenerateSingleUI(IFieldEntry entry, Transform parent)
{
if (entry is not XuiFieldTempEntry tempEntry) return;
var prefab = ResolvePrefab(entry);
if (prefab == null)
{
Debug.LogWarning($"[Xuiavrg] 未找到 Entry '{entry.EntryName}' 的匹配预制体,跳过");
return;
}
entry.BoundPrefab = prefab;
var uiInstance = PrefabPoolGet(prefab);
if (uiInstance == null) return;
uiInstance.name = entry.EntryName;
uiInstance.transform.SetParent(parent, false);
// 检查 XUIAvrgMDValue 是否存在
if (uiInstance.GetComponentInChildren<XUIAvrgMDValue>(true) == null)
{
Debug.LogError($"[Xuiavrg] 预制体 '{prefab.name}' 不符合生成要求:缺少 XUIAvrgMDValue 组件。请确保预制体根节点或其子节点包含 XUIAvrgMDValue 脚本。");
PrefabPoolRelease(prefab, uiInstance);
return;
}
entry.BindUIComponent(uiInstance, null);
entry.OnEntryBind();
}
/// <summary>
/// 从 Entry 提取 GroupPath(通过 EntryName 判断)
/// </summary>
private string GetGroupPathFromEntry(IFieldEntry entry)
{
// 通过 Bindings 中的元数据找到 GroupPath
if (entry.Bindings != null && entry.Bindings.Count > 0)
{
var metaData = entry.Bindings[0].MetaData;
if (metaData != null && !string.IsNullOrEmpty(metaData.GroupPath))
return metaData.GroupPath;
}
return null;
}
// ============ 预制体解析 ============
private GameObject ResolvePrefab(IFieldEntry entry)
{
// 1. 优先检查 PrefabOverrideName(按主题 + UI 名称查找)
if (!string.IsNullOrEmpty(entry.PrefabOverrideName))
{
var overrideGroup = XuiavrgPropertyGroupScriptableObject.FindGroupByUiName(_currentTheme, entry.PrefabOverrideName);
if (overrideGroup != null) return overrideGroup;
}
// 2. 按 Tag 查找匹配的组(在当前主题下)
if (entry.Tag != XuiavrgFieldTag.None)
{
var tags = new List<XuiavrgFieldTag> { entry.Tag };
// 收集子 Entry 的 Tag(如果是编组 Entry)
if (entry.IsGroupEntry)
if (typePrefabMappingConfig != null)
{
foreach (var child in entry.Children)
{
if (child.Tag != XuiavrgFieldTag.None && !tags.Contains(child.Tag))
tags.Add(child.Tag);
}
var namedPrefab = typePrefabMappingConfig.FindPrefabByName(entry.PrefabOverrideName);
if (namedPrefab != null) return namedPrefab;
}
var matchedGroup = XuiavrgPropertyGroupScriptableObject.FindGroup(_currentTheme, tags.ToArray());
if (matchedGroup != null) return matchedGroup;
}
// 3. 回退到当前主题下的 "Default" UI 名称
return XuiavrgPropertyGroupScriptableObject.FindGroupByUiName(_currentTheme, "Default");
if (typePrefabMappingConfig != null)
{
var typeMatch = typePrefabMappingConfig.FindPrefabByBindings(entry.Bindings);
if (typeMatch != null) return typeMatch;
}
return typePrefabMappingConfig != null ? typePrefabMappingConfig.defaultPrefab : null;
}
/// <summary>
/// 清空当前界面和 Entry 列表
/// </summary>
// ============ 清理 ============
public void ClearAll()
{
ClearCurrentEntries();
ClearAllPropertyGroupPools();
CurrentPayload = null;
ClearAllPrefabPools();
ClearGroups();
CurrentMetaDataSet = null;
CurrentBindingCollection = null;
}
/// <summary>
/// 清空当前 Entry 列表(归还到池中)
/// </summary>
private void ClearCurrentEntries()
{
foreach (var entry in CurrentEntries)
{
// 释放 UI 实例
if (entry.UIInstance != null && entry.BoundPrefabGroup != null)
if (entry.UIInstance != null && entry.BoundPrefab != null)
{
PropertyGroupPoolRelease(entry.BoundPrefabGroup, entry.UIInstance);
PrefabPoolRelease(entry.BoundPrefab, entry.UIInstance);
}
// 归还 Entry 到池
if (entry is XuiFieldTempEntry tempEntry)
{
XuiFieldTempEntry.Release(tempEntry);
@@ -412,29 +353,35 @@ namespace XericUI.ReflectionUIGenerator
CurrentEntries.Clear();
}
#endregion
#region 生命周期
private void ClearGroups()
{
foreach (var kvp in _activeGroups)
{
if (kvp.Value != null && kvp.Value.gameObject != null)
{
Destroy(kvp.Value.gameObject);
}
}
_activeGroups.Clear();
}
private void OnDestroy()
{
ClearAll();
}
#endregion
// ============ 兼容旧 API ============
#region 兼容旧 API
/// <summary>
/// [已弃用] 请使用 BuildEntries + GenerateUI 替代
/// </summary>
[Obsolete("Use BuildEntries + GenerateUI instead", false)]
[Obsolete("Use BuildFromMetaDataSet + GenerateUI instead", false)]
public void SchemaHydration(object payload)
{
BuildEntries(payload);
var metaDataSet = XuiavrgUtilities.ExtractAvrgMemberMetaData(payload);
var bindingCollection = metaDataSet.BuildMDVM(payload);
BuildFromMetaDataSet(metaDataSet, bindingCollection);
GenerateUI();
}
#endregion
}
}
@@ -1,171 +0,0 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEngine.Serialization;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 反射属性中间层
/// </summary>
[Serializable]
public class XuiavrgInspectorProperty
{
#region 字段属性
public object valueTarget;
public bool isProperty;
public PropertyInfo propertyInfo;
public FieldInfo fieldInfo;
// 基本信息
public string labelName; // 字段名称
public string uiPropertyName; // ui属性名称
public string unitLabelName; // 单位名称
public bool required; // 项目必填
public bool notEmpty; // 不可为空
public int inspectorOrder; // 显示在检查器上的顺序
public string groupName; // 编组名称
public string onTriggerCall; // 值变更时触发的反射
public XuiavrgSize argumentSize; // 属性尺寸
public bool hideInInspector; // 在检查器上隐藏显示
// 字符串检查
public int stringMinLength; // 最小长度
public int stringMaxLength = int.MaxValue; // 最大长度
public string stringRegexPattern = ""; // 正则检查
public bool stringOnlyLetters; // 允许字符
public bool stringOnlyNumber; // 允许数字
public bool stringOnlyEmail = true; // 允许邮箱
public bool stringOnlyPhone = true; // 允许电话号码
public bool stringOnlyUrl = true; // 允许网址
// 数值检查
public float numberMinValue; // 最小值
public float numberMaxValue; // 最大值
// 集合检查
public int collectionMinCount; // 集合的最小尺寸
public int collectionMaxCount = int.MaxValue; // 集合的最大尺寸
public bool collectionNotEmpty; // 集合不可为空
#endregion
#region 检查方法
/// <summary>
/// 绑定到数据端
/// </summary>
/// <param name="memberInfo"></param>
/// <param name="target"></param>
public void RegsiterDataValue(MemberInfo memberInfo, object target)
{
switch (memberInfo)
{
case PropertyInfo propertyInfo:
RegsiterDataValue(propertyInfo, target);
break;
case FieldInfo fieldInfo:
RegsiterDataValue(fieldInfo, target);
break;
}
}
/// <summary>
/// 绑定到数据端
/// </summary>
/// <param name="memberInfo"></param>
/// <param name="target"></param>
public virtual void RegsiterDataValue(PropertyInfo memberInfo, object target)
{
valueTarget = target;
isProperty = true;
propertyInfo = memberInfo;
}
/// <summary>
/// 绑定到数据端
/// </summary>
/// <param name="memberInfo"></param>
/// <param name="target"></param>
public virtual void RegsiterDataValue(FieldInfo memberInfo, object target)
{
valueTarget = target;
isProperty = false;
fieldInfo = memberInfo;
}
/// <summary>
/// 绑定到界面端
/// </summary>
public virtual void RegsiterUIElement()
{
}
/// <summary>
/// 数据端值发生变更
/// </summary>
public void CallDataValueChange()
{
}
/// <summary>
/// 界面端数据发生变更
/// </summary>
public void CallUIValueChange()
{
}
/// <summary>
/// 从数据端获取值
/// </summary>
/// <returns></returns>
public object GetValueFromTargetMember()
{
if (isProperty)
return propertyInfo.GetValue(valueTarget);
else
return fieldInfo.GetValue(valueTarget);
}
/// <summary>
/// 设置值到数据端
/// </summary>
/// <param name="value"></param>
public void SetValueToTargetMember(object value)
{
if (isProperty)
propertyInfo.SetValue(valueTarget, value);
else
fieldInfo.SetValue(valueTarget, value);
}
/// <summary>
/// 从界面端获取值
/// </summary>
/// <returns></returns>
public object GetValueFormUIElement()
{
return null;
}
/// <summary>
/// 设置值到界面端
/// </summary>
/// <param name="value"></param>
public void SetValueToUIElement(object value)
{
}
public void Clear()
{
}
#endregion
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5d82ae110adb48b397c09939694cc3b2
timeCreated: 1777011022
@@ -1,309 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector;
#endif
using UnityEngine;
using UnityEngine.Serialization;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// ui反射属性定位器,放在UI对象的根节点上,设定好接下来的目标即可
/// </summary>
public class XuiavrgLocator : MonoBehaviour
{
public delegate void AnyTrigger(LocatorProperty locator, object value, int selectStart, int selectEnd);
#if ODIN_INSPECTOR
[LabelText("映射组件")]
#endif
public List<LocatorProperty> Properties;
[Serializable]
public class LocatorProperty
{
#if ODIN_INSPECTOR
[LabelText("组件标识名")]
#endif
[Tooltip("建议使用 name, label, unit, sign, input, output, button, slider, edit, select 等简单易懂的名称进行标记")]
public string propertyName;
#if ODIN_INSPECTOR
[LabelText("组件类型")]
#endif
public UIElementClassify classify;
#if dUI_TextMeshPro
#if ODIN_INSPECTOR
[ShowIf("classify", UIElementClassify.TMPText)]
#endif
public TMPro.TMP_Text TMPText;
#if ODIN_INSPECTOR
[ShowIf("classify", UIElementClassify.TMPInputField)]
#endif
public TMPro.TMP_InputField TMPInputField;
#endif
#if ODIN_INSPECTOR
[ShowIf("classify", UIElementClassify.UIText)]
#endif
public UnityEngine.UI.Text UIText;
#if ODIN_INSPECTOR
[ShowIf("classify", UIElementClassify.UIInputField)]
#endif
public UnityEngine.UI.InputField UIInputField;
#if ODIN_INSPECTOR
[ShowIf("classify", UIElementClassify.UIToggle)]
#endif
public UnityEngine.UI.Toggle UIToggle;
#if ODIN_INSPECTOR
[ShowIf("classify", UIElementClassify.UIButton)]
#endif
public UnityEngine.UI.Button UIButton;
#if ODIN_INSPECTOR
[ShowIf("classify", UIElementClassify.UISlider)]
#endif
public UnityEngine.UI.Slider UISlider;
#if ODIN_INSPECTOR
[ShowIf("classify", UIElementClassify.UIDropdown)]
#endif
public UnityEngine.UI.Dropdown UIDropdown;
#if dUI_TextMeshPro
#if ODIN_INSPECTOR
[ShowIf("classify", UIElementClassify.TMPInputField)]
#endif
public TMPInputTriggerEvent TMPInputTriggerOnEventType;
#endif
#if ODIN_INSPECTOR
[ShowIf("classify", UIElementClassify.UIInputField)]
#endif
public UIInputTriggerEvent UIInputTriggerOnEventType;
/// <summary>
/// 支持的组件类型
/// </summary>
public enum UIElementClassify
{
None,
#if dUI_TextMeshPro
TMPText,
TMPInputField,
#endif
UIText,
UIInputField,
UIToggle,
UIButton,
UISlider,
UIDropdown,
}
[Flags]
public enum TMPInputTriggerEvent
{
none,
onEndEdit = 1 << 1,
onSubmit = 1 << 2,
onSelect = 1 << 3,
onDeselect = 1 << 4,
onTextSelection = 1 << 5,
onEndTextSelection = 1 << 6,
onValueChanged = 1 << 7,
onTouchScreenKeyboardStatusChanged = 1 << 8,
}
public enum UIInputTriggerEvent
{
none,
onEndEdit,
onSubmit,
onValueChanged,
}
/// <summary>
/// 获取值
/// </summary>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public object GetValue()
{
switch (classify)
{
#if dUI_TextMeshPro
case UIElementClassify.TMPText:
return TMPText.text;
case UIElementClassify.TMPInputField:
return TMPInputField.text;
#endif
case UIElementClassify.UIText:
return UIText.text;
case UIElementClassify.UIInputField:
return UIInputField.text;
case UIElementClassify.UIToggle:
return UIToggle.isOn;
case UIElementClassify.UIButton:
return false;
case UIElementClassify.UISlider:
return UISlider.value;
case UIElementClassify.UIDropdown:
return UIDropdown.value;
default:
return null;
}
}
/// <summary>
/// 设置值
/// </summary>
/// <param name="value"></param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public void SetValue(object value)
{
switch (classify)
{
#if dUI_TextMeshPro
case UIElementClassify.TMPText:
TMPText.text = value.ToString();
break;
case UIElementClassify.TMPInputField:
TMPInputField.text = value.ToString();
break;
#endif
case UIElementClassify.UIText:
UIText.text = value.ToString();
break;
case UIElementClassify.UIInputField:
UIInputField.text = value.ToString();
break;
case UIElementClassify.UIToggle:
UIToggle.isOn = Convert.ToBoolean(value);
break;
case UIElementClassify.UIButton:
// UIButton do nothing
break;
case UIElementClassify.UISlider:
UISlider.value = Convert.ToSingle(value);
break;
case UIElementClassify.UIDropdown:
UIDropdown.value = Convert.ToInt32(value);
break;
default:
break;
}
}
/// <summary>
/// ui触发事件
/// </summary>
public event AnyTrigger OnUITrigger
{
add
{
switch (classify)
{
#if dUI_TextMeshPro
case UIElementClassify.TMPInputField:
switch (TMPInputTriggerOnEventType)
{
case TMPInputTriggerEvent.onEndEdit:
TMPInputField.onEndEdit.AddListener(a => value?.Invoke(this, a, 0, 0));
break;
case TMPInputTriggerEvent.onSubmit:
TMPInputField.onSubmit.AddListener(a => value?.Invoke(this, a, 0, 0));
break;
case TMPInputTriggerEvent.onSelect:
TMPInputField.onSelect.AddListener(a => value?.Invoke(this, a, 0, 0));
break;
case TMPInputTriggerEvent.onDeselect:
TMPInputField.onDeselect.AddListener(a => value?.Invoke(this, a, 0, 0));
break;
case TMPInputTriggerEvent.onTextSelection:
TMPInputField.onTextSelection.AddListener((content, start, end) =>
value?.Invoke(this, content, start, end));
break;
case TMPInputTriggerEvent.onEndTextSelection:
TMPInputField.onEndTextSelection.AddListener((content, start, end) =>
value?.Invoke(this, content, start, end));
break;
case TMPInputTriggerEvent.onValueChanged:
TMPInputField.onValueChanged.AddListener(a => value?.Invoke(this, a, 0, 0));
break;
case TMPInputTriggerEvent.onTouchScreenKeyboardStatusChanged:
TMPInputField.onTouchScreenKeyboardStatusChanged.AddListener(a =>
value?.Invoke(this, a, 0, 0));
break;
default:
break;
}
break;
#endif
case UIElementClassify.UIInputField:
switch (UIInputTriggerOnEventType)
{
case UIInputTriggerEvent.onEndEdit:
UIInputField.onEndEdit.AddListener(a => value?.Invoke(this, a, 0, 0));
break;
case UIInputTriggerEvent.onSubmit:
UIInputField.onSubmit.AddListener(a => value?.Invoke(this, a, 0, 0));
break;
case UIInputTriggerEvent.onValueChanged:
UIInputField.onValueChanged.AddListener(a => value?.Invoke(this, a, 0, 0));
break;
default: break;
}
break;
case UIElementClassify.UIToggle:
UIToggle.onValueChanged.AddListener(a => value?.Invoke(this, value, 0, 0));
break;
case UIElementClassify.UIButton:
UIButton.onClick.AddListener(() => value?.Invoke(this, true, 0, 0));
break;
case UIElementClassify.UISlider:
UISlider.onValueChanged.AddListener(a => value?.Invoke(this, a, 0, 0));
break;
case UIElementClassify.UIDropdown:
UIDropdown.onValueChanged.AddListener(a => value?.Invoke(this, a, 0, 0));
break;
default:
break;
}
}
remove { Debug.Log("任意触发器无法卸载特定事件"); }
}
/// <summary>
/// 清空ui触发事件
/// </summary>
public void ClearTriggerEvent()
{
#if dUI_TextMeshPro
if (TMPInputField)
{
TMPInputField.onEndEdit.RemoveAllListeners();
TMPInputField.onSubmit.RemoveAllListeners();
TMPInputField.onSelect.RemoveAllListeners();
TMPInputField.onDeselect.RemoveAllListeners();
TMPInputField.onTextSelection.RemoveAllListeners();
TMPInputField.onEndTextSelection.RemoveAllListeners();
TMPInputField.onValueChanged.RemoveAllListeners();
TMPInputField.onTouchScreenKeyboardStatusChanged.RemoveAllListeners();
}
#endif
if (UIInputField)
{
UIInputField.onEndEdit.RemoveAllListeners();
UIInputField.onSubmit.RemoveAllListeners();
UIInputField.onValueChanged.RemoveAllListeners();
}
UIToggle?.onValueChanged.RemoveAllListeners();
UIButton?.onClick.RemoveAllListeners();
UISlider?.onValueChanged.RemoveAllListeners();
UIDropdown?.onValueChanged.RemoveAllListeners();
}
}
// todo 使用名称,类型筛选得到组件
// todo 根据名称组建字典树加速查找(或许不用)
}
}
@@ -1,299 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Object = UnityEngine.Object;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 界面属性编组
///
/// 将一种 C# 数据类型与一个 UI 预制体绑定,并归属于一个主题。
/// 类似 Newtonsoft.Json 的 Converter 概念 — 针对特定 Type,指定特定的 UI 模板。
///
/// 同一类型可以有多个不同 uiName/主题的重载(如 "简单输入框" vs "带标签的输入框")。
/// Manager 根据当前主题名 + 字段 Tag 自动匹配对应的编组。
///
/// 全局注册表以 themeName 为键分组管理,OnEnable/OnDisable 自动注册/注销。
/// </summary>
[Serializable]
[CreateAssetMenu(fileName = "Xeric UI avr Property group", menuName = "Xeric Library/UI Action Vessel/Reflection UI Gen/UI avr Property Group", order = 10)]
public class XuiavrgPropertyGroupScriptableObject : ScriptableObject
{
#region 全局注册表
/// <summary>
/// 全局预制体组字典,以主题名称为键分组
/// </summary>
internal static Dictionary<string, List<XuiavrgPropertyGroupScriptableObject>> GlobalPrefabDict
= new Dictionary<string, List<XuiavrgPropertyGroupScriptableObject>>();
/// <summary>
/// 根据主题名获取该主题下所有编组
/// </summary>
public static List<XuiavrgPropertyGroupScriptableObject> GetGroupsByTheme(string themeName)
{
if (string.IsNullOrEmpty(themeName))
themeName = DefaultThemeName;
GlobalPrefabDict.TryGetValue(themeName, out var list);
return list ?? new List<XuiavrgPropertyGroupScriptableObject>();
}
/// <summary>
/// 根据主题名 + 标记查找编组
/// </summary>
public static XuiavrgPropertyGroupScriptableObject FindGroup(string themeName, params XuiavrgFieldTag[] tags)
{
var groups = GetGroupsByTheme(themeName);
return groups.FirstOrDefault(g => tags.All(t => g.supportedTags.Contains(t)));
}
/// <summary>
/// 根据主题名 + UI 名称查找编组
/// </summary>
public static XuiavrgPropertyGroupScriptableObject FindGroupByUiName(string themeName, string uiName)
{
var groups = GetGroupsByTheme(themeName);
return groups.FirstOrDefault(g => g.uiName == uiName);
}
/// <summary>
/// 根据主题名 + 绑定类型查找所有匹配的编组
/// </summary>
public static List<XuiavrgPropertyGroupScriptableObject> FindGroupsByType(string themeName, string valueTypeName)
{
var groups = GetGroupsByTheme(themeName);
return groups.Where(g => g.valueTypeName == valueTypeName).ToList();
}
/// <summary>
/// 默认主题名称
/// </summary>
public const string DefaultThemeName = "Default";
#endregion
#region 属性字段
/// <summary>
/// 主题名称。同一主题下的 UI 组件共享统一风格。
/// </summary>
[Tooltip("主题名称。同一主题下的 UI 组件共享统一风格。Manager 根据其 currentTheme 查找对应编组")]
public string themeName = DefaultThemeName;
/// <summary>
/// UI 个体名称。同一类型可有多个不同名称的重载。
/// 在数据字段上通过 XuiavrgFieldPrefabOverride 按此名称指定。
/// </summary>
[Tooltip("UI 个体名称。用于 XuiavrgFieldPrefabOverride 按名指定,同一类型可有多个重载")]
public string uiName = "Default";
/// <summary>
/// 此编组绑定的 C# 数据类型全名(如 System.Single、System.String)
/// </summary>
[Tooltip("绑定的 C# 数据类型全名,如 System.Single、System.Boolean")]
public string valueTypeName = "System.Single";
/// <summary>
/// 解析后的类型缓存
/// </summary>
public Type ValueType
{
get
{
if (_valueType != null) return _valueType;
if (!string.IsNullOrEmpty(valueTypeName))
_valueType = Type.GetType(valueTypeName);
return _valueType;
}
}
private Type _valueType;
/// <summary>
/// 所有程序集中类型名的缓存 — 简单名列表(如 "Boolean"、"Int32"、"String")
/// 与 _allTypeFullNames 下标一一对应
/// </summary>
private static List<string> _allTypeSimpleNames;
/// <summary>
/// 所有程序集中类型名的缓存 — 全名列表(如 "System.Boolean"、"System.Int32")
/// 与 _allTypeSimpleNames 下标一一对应
/// </summary>
private static List<string> _allTypeFullNames;
/// <summary>
/// 初始化类型名缓存 — 反射扫描所有已加载程序集中的类型
/// </summary>
private static void EnsureTypeCacheBuilt()
{
if (_allTypeFullNames != null) return;
_allTypeSimpleNames = new List<string>();
_allTypeFullNames = new List<string>();
var seen = new HashSet<string>();
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
foreach (var type in asm.GetTypes())
{
var fullName = type.FullName;
if (string.IsNullOrEmpty(fullName)) continue;
// 跳过泛型参数位置和编译器生成的类型
if (fullName.Contains("`") || fullName.Contains("<")) continue;
if (!seen.Add(fullName)) continue;
_allTypeSimpleNames.Add(type.Name);
_allTypeFullNames.Add(fullName);
}
}
catch
{
// 某些程序集可能无法反射(如动态程序集),忽略
}
}
}
/// <summary>
/// 尝试将用户输入的类型名纠正为标准全名。
/// 通过 LevenshteinDistance 在所有已知类型中找到最匹配的名称。
/// 如 "boolean" → "System.Boolean", "single" → "System.Single"。
/// 如果已经是标准全名或无法匹配则返回原值(不做修改)。
/// </summary>
/// <param name="userInput">用户输入的类型名</param>
/// <returns>纠正后的标准类型全名,无法识别时返回原值</returns>
public static string NormalizeTypeName(string userInput)
{
if (string.IsNullOrWhiteSpace(userInput))
return userInput;
userInput = userInput.Trim();
// 已经是标准全名(包含 "."),直接视为 OK,不做修改
if (userInput.Contains('.'))
return userInput;
// 初始化类型缓存
EnsureTypeCacheBuilt();
// 通过 Levenshtein 距离在所有类型简单名中找到最匹配的
var bestMatch = XericLibrary.Runtime.MacroLibrary.MacroMath.LevenshteinDistance(
_allTypeSimpleNames, userInput,
out int dis, out int index);
// 阈值:距离超过输入长度 2 倍 + 2,认为不匹配,返回原值
if (bestMatch == null || dis > userInput.Length * 2 + 2)
return userInput;
// 返回对应的全名
return _allTypeFullNames[index];
}
/// <summary>
/// 尝试将此实例的 valueTypeName 纠正为标准全名
/// </summary>
public void NormalizeValueTypeName()
{
valueTypeName = NormalizeTypeName(valueTypeName);
_valueType = null; // 清空缓存,下次访问时重新解析
}
/// <summary>
/// 对应的 UI 预制体
/// </summary>
[Tooltip("拖入 UI 预制体,系统会自动扫描其中的 IXuiavrgFieldTagProvider 组件")]
public GameObject prefab;
/// <summary>
/// 此编组支持的 UI 功能标记集合
/// </summary>
[Tooltip("此编组包含的 UI 功能标记。Manager 根据字段 Tag 匹配对应编组")]
public List<XuiavrgFieldTag> supportedTags = new List<XuiavrgFieldTag>();
#endregion
#region 组件自动识别(运行时 + Editor 共用)
/// <summary>
/// 从预制体中自动扫描到的 IXuiavrgFieldTagProvider 组件列表(非序列化,仅供 Editor 展示)
/// </summary>
[NonSerialized]
public List<Component> detectedComponents = new List<Component>();
/// <summary>
/// 重新扫描预制体中的标记组件
/// </summary>
public void ScanPrefabComponents()
{
detectedComponents.Clear();
if (prefab == null) return;
var components = prefab.GetComponentsInChildren<Component>(true);
foreach (var comp in components)
{
if (comp is IXuiavrgFieldTagProvider tagProvider && tagProvider.EntryTag != XuiavrgFieldTag.None)
{
detectedComponents.Add(comp);
}
}
// 自动同步 supportedTags
HashSet<XuiavrgFieldTag> autoTags = new HashSet<XuiavrgFieldTag>();
foreach (var comp in detectedComponents)
{
if (comp is IXuiavrgFieldTagProvider tp && tp.EntryTag != XuiavrgFieldTag.None)
autoTags.Add(tp.EntryTag);
}
foreach (var tag in autoTags)
{
if (!supportedTags.Contains(tag))
supportedTags.Add(tag);
}
}
#endregion
#region 生命周期
void OnEnable()
{
RegisterToGlobalDict();
}
void OnDisable()
{
UnregisterFromGlobalDict();
}
private void RegisterToGlobalDict()
{
string key = string.IsNullOrEmpty(themeName) ? DefaultThemeName : themeName;
if (!GlobalPrefabDict.TryGetValue(key, out var list))
{
list = new List<XuiavrgPropertyGroupScriptableObject>();
GlobalPrefabDict[key] = list;
}
if (!list.Contains(this))
list.Add(this);
}
private void UnregisterFromGlobalDict()
{
string key = string.IsNullOrEmpty(themeName) ? DefaultThemeName : themeName;
if (GlobalPrefabDict.TryGetValue(key, out var list))
{
list.Remove(this);
if (list.Count == 0)
GlobalPrefabDict.Remove(key);
}
}
#endregion
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: bcd1c64483d2457ab9143f086dff511d
timeCreated: 1777024131
@@ -1,5 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace XericUI.ReflectionUIGenerator
@@ -9,8 +10,6 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>
/// 获取所有运行时成员(字段和属性)
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IEnumerable<MemberInfo> GetRuntimeMemberInfo(this Type type)
{
foreach (var field in type.GetRuntimeFields())
@@ -18,5 +17,97 @@ namespace XericUI.ReflectionUIGenerator
foreach (var property in type.GetRuntimeProperties())
yield return property;
}
/// <summary>
/// 从任意对象提取 AvrgMemberMetaData 元数据集
/// 反射识别成员、识别 IXuiavrgAttribute 标记、执行标记绑定的完整流程
/// </summary>
/// <param name="source">待提取的数据对象</param>
/// <returns>提取完成的元数据集</returns>
public static AvrgMemberMetaDataSet ExtractAvrgMemberMetaData(this object source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var metaDataSet = new AvrgMemberMetaDataSet();
var type = source.GetType();
foreach (var memberInfo in type.GetRuntimeMemberInfo())
{
// 检查隐藏标记
var hideAttr = memberInfo.GetCustomAttribute<XuiavrgHideAttribute>();
if (hideAttr != null && hideAttr.Hide)
continue;
// 创建元数据
var metaData = new AvrgMemberMetaData
{
MemberName = memberInfo.Name,
MemberInfo = memberInfo,
TargetInstance = source,
};
// 识别值类型和 IsProperty
switch (memberInfo)
{
case PropertyInfo pi:
metaData.IsProperty = true;
metaData.ValueType = pi.PropertyType;
break;
case FieldInfo fi:
metaData.IsProperty = false;
metaData.ValueType = fi.FieldType;
break;
}
// 提取字段默认值(类初始值)
// Unity 序列化字段可以通过反射读取实例上的当前值作为"默认"参考
// 但更准确的在类声明阶段的初始值需要通过运行时观察或 Fallback 判断
// 此处策略:如果 TargetInstance 非 null,直接 ReadValue 作为 DefaultValue
if (source != null && memberInfo is FieldInfo fi2)
{
metaData.DefaultValue = fi2.GetValue(source);
}
else if (source != null && memberInfo is PropertyInfo pi2 && pi2.CanRead)
{
metaData.DefaultValue = pi2.GetValue(source);
}
// 设置 PropertyCheckKey 默认值(用类型全称)
if (metaData.ValueType != null)
metaData.PropertyCheckKey = metaData.ValueType.FullName;
// 收集所有 IXuiavrgAttribute
var attributes = memberInfo.GetCustomAttributes()
.OfType<IXuiavrgAttribute>()
.ToList();
metaData.Attributes = attributes;
// 按序执行 BindMetaData
foreach (var attr in attributes)
{
attr.BindMetaData(metaData);
}
metaDataSet.Add(metaData);
}
// 处理编组合并
metaDataSet.MergeGroupEntries();
// 排序
metaDataSet.SortByFieldOrder();
return metaDataSet;
}
/// <summary>
/// 从任意对象提取 AvrgMemberMetaData 元数据集的泛型版本
/// </summary>
public static AvrgMemberMetaDataSet ExtractAvrgMemberMetaData<T>(this T source)
{
return ExtractAvrgMemberMetaData((object)source);
}
}
}
}