优化反射特性

This commit is contained in:
2026-07-24 15:25:16 +08:00
parent 39220c761f
commit f3672df6a7
12 changed files with 496 additions and 96 deletions
@@ -3,34 +3,52 @@ using System;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 预制体覆盖特性
/// 覆盖对应 Entry 或普通字段生成 UI 时使用的预制体名称
/// 字段显示名称特性
/// 设置字段在 UI 上的显示标签、单位文本和格式化字符串
///
/// 使用示例:
/// [XuiavrgFieldPrefabOverride("CustomSpeedPrefab")] public float speed;
/// [XuiavrgFieldName("角色名称")]
/// public string playerName = "勇者";
///
/// Manager 在 GenerateUI 阶段会优先使用此名称查找 PropertyGroup
/// [XuiavrgFieldName("速度", "m/s", "{0:F1}")]
/// public float speed = 5f;
///
/// 字段定义:
/// - labelName: UI 上的显示标签(必填)
/// - unitLabelName: 单位文本(可选),如 "m/s"、"%"、"px"
/// - formatText: 格式化字符串(可选),如 "{0:F2}",控制值在 UI 上的显示格式
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class XuiavrgFieldNameAttribute : Attribute, IXuiavrgAttribute
{
/// <summary>覆盖的预制体组名称</summary>
public readonly string PrefabGroupName;
/// <summary>字段的显示标签</summary>
public readonly string LabelName;
/// <summary>单位文本(可选)</summary>
public readonly string UnitLabelName;
/// <summary>格式化字符串(可选),如 "{0:F2}"</summary>
public readonly string FormatText;
/// <summary>
/// 创建预制体覆盖标记
/// 创建字段显示名称标记
/// </summary>
/// <param name="prefabGroupName">
/// 目标预制体名称,在 AvrgTypePrefabMappingConfig 中查找匹配
/// </param>
public XuiavrgFieldNameAttribute(string prefabGroupName)
/// <param name="labelName">UI 上的显示标签</param>
/// <param name="unitLabelName">可选的单位文本,如 "m/s"</param>
/// <param name="formatText">可选的格式化字符串,如 "{0:F2}"</param>
public XuiavrgFieldNameAttribute(string labelName, string unitLabelName = null, string formatText = null)
{
PrefabGroupName = prefabGroupName;
LabelName = labelName;
UnitLabelName = unitLabelName;
FormatText = formatText;
}
public void BindMetaData(AvrgMemberMetaData metaData)
{
metaData.PrefabOverrideName = PrefabGroupName;
metaData.LabelName = LabelName;
metaData.UnitLabelName = UnitLabelName;
metaData.FormatText = FormatText;
// PrefabOverrideName 已移除 — 由类型+Tag 匹配替代
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8254aa7ff8b5fe443b04eb4b30a063d4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -33,16 +33,19 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>是否启用无效值状态检查</summary>
public bool ValidateInvalid { get; set; }
public XuiavrgPropertyCheckAttribute()
{
}
/// <summary>
/// 创建必填检查标记
/// </summary>
public XuiavrgPropertyCheckAttribute(bool required)
/// <param name="required">是否必填</param>
/// <param name="validateReasonable">是否启用合理值范围检查</param>
/// <param name="validateWarning">是否启用警告状态检查</param>
/// <param name="validateInvalid">是否启用无效值状态检查</param>
public XuiavrgPropertyCheckAttribute(bool required = true, bool validateReasonable = false, bool validateWarning = false, bool validateInvalid = false)
{
Required = required;
ValidateReasonable = validateReasonable;
ValidateWarning = validateWarning;
ValidateInvalid = validateInvalid;
}
public void BindMetaData(AvrgMemberMetaData metaData)
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 拆分属性标注
/// 将一个复合类型(如 Vector2)拆分为多个子属性,分别绑定到不同的 UI 元素
///
/// 使用示例:
/// [XuiavrgFieldEntryGroup("speed")]
/// [XuiavrgSplit("x", "最小")]
/// [XuiavrgSplit("y", "最大")]
/// public Vector2 speedRange = new Vector2(0, 100);
///
/// 上述代码创建一个 "speed" 编组的 Entry,内部包含两个子绑定:
/// speedRange.x 绑定到 UI 的"最小"输入,speedRange.y 绑定到"最大"输入
/// 子绑定通过 XUIAvrgMDValue.BindingSlots 与预制体上的 UI 组件匹配
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public class XuiavrgSplitAttribute : Attribute, IXuiavrgAttribute
{
/// <summary>子属性路径(如 "x"、"y"),访问父值上的子字段/属性</summary>
public readonly string PropertyPath;
/// <summary>可选的显示标签</summary>
public readonly string DisplayLabel;
/// <summary>
/// 创建拆分属性标记
/// </summary>
/// <param name="propertyPath">子属性路径(如 Vector2 的 "x" 或 "y")</param>
/// <param name="displayLabel">可选的显示标签(为空时自动推导)</param>
public XuiavrgSplitAttribute(string propertyPath, string displayLabel = null)
{
PropertyPath = propertyPath;
DisplayLabel = displayLabel;
}
public void BindMetaData(AvrgMemberMetaData metaData)
{
// 创建子元数据
var subMetaData = new AvrgMemberMetaData
{
MemberName = $"{metaData.MemberName}.{PropertyPath}",
PropertyPath = PropertyPath,
LabelName = DisplayLabel ?? PropertyPath,
TargetInstance = null, // 由运行时闭包解析
Hide = metaData.Hide,
Required = metaData.Required,
FieldOrder = metaData.FieldOrder,
};
// 通过反射解析子属性的值类型
if (metaData.ValueType != null)
{
var subType = ResolveSubPropertyType(metaData.ValueType, PropertyPath);
if (subType != null)
subMetaData.ValueType = subType;
}
// 添加到父元数据的子元数据列表
if (metaData.SubMetaDataList == null)
metaData.SubMetaDataList = new List<AvrgMemberMetaData>();
metaData.SubMetaDataList.Add(subMetaData);
}
/// <summary>
/// 通过反射解析子属性的类型
/// </summary>
private static Type ResolveSubPropertyType(Type parentType, string propertyPath)
{
if (parentType == null || string.IsNullOrEmpty(propertyPath))
return null;
// 仅支持单层路径
if (propertyPath.Contains('/'))
return null;
var field = parentType.GetField(propertyPath);
if (field != null) return field.FieldType;
var prop = parentType.GetProperty(propertyPath);
if (prop != null) return prop.PropertyType;
return null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d1ecca25a43f5b24d878a1f88278b289
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -27,6 +27,20 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>绑定的目标实例 (可选,Step1 时可空)</summary>
public object TargetInstance { get; set; }
// ============ 拆分属性支持 ============
/// <summary>
/// 子属性路径(仅拆分属性有效)
/// 用于从父值中访问子字段/属性,如 "x"、"y"、"width"
/// </summary>
public string PropertyPath { get; set; }
/// <summary>
/// 拆分子元数据列表
/// 由 [XuiavrgSplit] 特性填充,每个子元数据对应一个拆分属性
/// </summary>
public List<AvrgMemberMetaData> SubMetaDataList { get; set; }
// ============ 从 IXuiavrgAttribute 融合的元数据 ============
/// <summary>显示标签</summary>
@@ -35,6 +49,9 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>单位文本</summary>
public string UnitLabelName { get; set; }
/// <summary>格式化字符串(如 "{0:F2}")</summary>
public string FormatText { get; set; }
/// <summary>功能标记</summary>
public XuiavrgFieldTag Tag { get; set; } = XuiavrgFieldTag.None;
@@ -47,9 +64,6 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>编组路径 (以 "/" 分割层级)</summary>
public string GroupPath { get; set; }
/// <summary>预制体覆盖名称</summary>
public string PrefabOverrideName { get; set; }
/// <summary>是否在 UI 中隐藏</summary>
public bool Hide { get; set; }
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace XericUI.ReflectionUIGenerator
@@ -48,7 +49,16 @@ namespace XericUI.ReflectionUIGenerator
{
// 编组内第一个成员作为主元数据
var first = gEntry.First();
// 合成的 metaData 保持 GroupPath 和主 Tag
// 收集组内所有成员的 SubMetaDataList(合并拆分属性)
foreach (var member in gEntry)
{
if (member.SubMetaDataList?.Count > 0)
{
if (first.SubMetaDataList == null)
first.SubMetaDataList = new List<AvrgMemberMetaData>();
first.SubMetaDataList.AddRange(member.SubMetaDataList);
}
}
merged.Add(first);
}
@@ -91,7 +101,7 @@ namespace XericUI.ReflectionUIGenerator
if (target == null)
continue;
// 创建绑定
// 创建主绑定
var binding = new AvrgMvvmBinding
{
MetaData = metaData,
@@ -105,6 +115,35 @@ namespace XericUI.ReflectionUIGenerator
scheduler.MarkDataDirty(capturedBinding);
};
// 如果元数据有拆分子属性,创建子绑定
if (metaData.SubMetaDataList?.Count > 0)
{
binding.SubBindings = new List<AvrgMvvmBinding>();
foreach (var subMetaData in metaData.SubMetaDataList)
{
string subPath = subMetaData.PropertyPath;
if (string.IsNullOrEmpty(subPath)) continue;
var subBinding = new AvrgMvvmBinding
{
MetaData = subMetaData,
// 从父值中读取子属性
ReadFunc = () => ReadSubProperty(metaData.GetValue(), subPath),
// 修改子属性并写回父值
WriteAction = (val) =>
{
var parentVal = metaData.GetValue();
var modifiedParent = WriteSubProperty(parentVal, subPath, val);
metaData.SetValue(modifiedParent);
scheduler.MarkDataDirty(capturedBinding);
},
};
binding.SubBindings.Add(subBinding);
}
}
// 注册到调度器
scheduler.Register(binding);
bindingCollection.Add(binding);
@@ -114,6 +153,69 @@ namespace XericUI.ReflectionUIGenerator
return bindingCollection;
}
// ============ 子属性访问辅助方法 ============
/// <summary>
/// 从父值中读取子属性(支持单层路径如 "x"、"y")
/// 例如:ReadSubProperty(Vector2(1,2), "x") → 1f
/// </summary>
private static object ReadSubProperty(object parentValue, string propertyPath)
{
if (parentValue == null || string.IsNullOrEmpty(propertyPath))
return null;
// 仅支持单层路径
if (propertyPath.Contains('/'))
{
Debug.LogWarning($"[Xuiavrg] 暂不支持多层嵌套路径 '{propertyPath}'");
return null;
}
var type = parentValue.GetType();
var field = type.GetRuntimeField(propertyPath);
if (field != null) return field.GetValue(parentValue);
var prop = type.GetRuntimeProperty(propertyPath);
if (prop != null) return prop.GetValue(parentValue);
Debug.LogWarning($"[Xuiavrg] 在类型 '{type.Name}' 上找不到子属性 '{propertyPath}'");
return null;
}
/// <summary>
/// 修改父值中的子属性并返回修改后的父值
/// 适用于值类型(struct)和引用类型
/// </summary>
private static object WriteSubProperty(object parentValue, string propertyPath, object value)
{
if (parentValue == null || string.IsNullOrEmpty(propertyPath))
return parentValue;
if (propertyPath.Contains('/'))
{
Debug.LogWarning($"[Xuiavrg] 暂不支持多层嵌套路径 '{propertyPath}'");
return parentValue;
}
var type = parentValue.GetType();
var field = type.GetRuntimeField(propertyPath);
if (field != null)
{
field.SetValue(parentValue, value);
return parentValue;
}
var prop = type.GetRuntimeProperty(propertyPath);
if (prop != null && prop.CanWrite)
{
prop.SetValue(parentValue, value);
return parentValue;
}
Debug.LogWarning($"[Xuiavrg] 在类型 '{type.Name}' 上找不到可写的子属性 '{propertyPath}'");
return parentValue;
}
// ============ 验证方法 ============
/// <summary>
@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 值绑定模板
/// 为单个元数据成员创建的 MVVM 双向绑定,包含读/写委托和脏标记
/// 支持通过 SubBindings 实现拆分属性的子绑定
/// </summary>
public class AvrgMvvmBinding
{
@@ -17,6 +20,12 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>写入值到数据源的委托</summary>
public Action<object> WriteAction { get; set; }
/// <summary>
/// 拆分属性子绑定列表
/// 由 [XuiavrgSplit] 特性展开生成,每个子绑定对应一个拆分后的子属性
/// </summary>
public List<AvrgMvvmBinding> SubBindings { get; set; }
// ============ 脏标记 ============
/// <summary>UI 侧是否标记为脏</summary>
@@ -6,7 +6,7 @@ namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 类型-预制体映射配置 ScriptableObject
/// 用于在 GenerateUI 阶段根据数据类型查找匹配的预制体
/// 用于在 GenerateUI 阶段根据数据类型 + Tag 查找匹配的预制体
/// </summary>
[CreateAssetMenu(
fileName = "Xeric UI avr Type-Prefab Mapping",
@@ -14,6 +14,20 @@ namespace XericUI.ReflectionUIGenerator
order = 15)]
public class AvrgTypePrefabMappingConfig : ScriptableObject
{
/// <summary>
/// Tag 预制体覆盖 — 同一类型在不同 Tag 下使用不同的预制体
/// 例如 float + Slider → FloatSlider, float + Dropdown → FloatDropdown
/// </summary>
[Serializable]
public class TagPrefabOverride
{
[Tooltip("功能标记")]
public XuiavrgFieldTag tag;
[Tooltip("该 Tag 对应的 UI 预制体")]
public GameObject prefab;
}
/// <summary>
/// 单个类型映射条目
/// </summary>
@@ -23,9 +37,12 @@ namespace XericUI.ReflectionUIGenerator
[Tooltip("C# 数据类型全名 (如 System.Single, System.String, System.Boolean)")]
public string valueTypeName;
[Tooltip("对应的 UI 预制体")]
[Tooltip("对应此类型的默认 UI 预制体")]
public GameObject prefab;
[Tooltip("按 Tag 覆盖预制体(同一类型在不同标记下使用不同 UI)")]
public List<TagPrefabOverride> tagOverrides = new List<TagPrefabOverride>();
/// <summary>缓存的类型</summary>
[NonSerialized] public Type cachedType;
}
@@ -80,6 +97,48 @@ namespace XericUI.ReflectionUIGenerator
return defaultPrefab;
}
/// <summary>
/// 按数据类型 + Tag 查找匹配的预制体
/// 优先匹配 tagOverrides,未匹配则按类型查找
/// </summary>
/// <param name="valueType">数据类型</param>
/// <param name="tag">功能标记(None 时等同 FindPrefabByType)</param>
public GameObject FindPrefabByTypeAndTag(Type valueType, XuiavrgFieldTag tag)
{
if (valueType == null) return defaultPrefab;
if (tag == XuiavrgFieldTag.None)
return FindPrefabByType(valueType);
BuildCache();
// 查找类型条目
foreach (var entry in typePrefabEntries)
{
if (entry.cachedType == null && !string.IsNullOrEmpty(entry.valueTypeName))
entry.cachedType = Type.GetType(entry.valueTypeName);
if (entry.cachedType == null) continue;
// 类型匹配
if (entry.cachedType == valueType || entry.cachedType.IsAssignableFrom(valueType))
{
// 优先查 tagOverrides
foreach (var overrideEntry in entry.tagOverrides)
{
if (overrideEntry.tag == tag && overrideEntry.prefab != null)
return overrideEntry.prefab;
}
// 没有 Tag 覆盖则返回类型默认预制体
if (entry.prefab != null)
return entry.prefab;
}
}
// 类型匹配 + Tag 匹配失败,尝试纯类型回退
return defaultPrefab;
}
/// <summary>
/// 根据绑定的 Entry Bindings 查找匹配的预制体
/// </summary>
@@ -90,16 +149,6 @@ namespace XericUI.ReflectionUIGenerator
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;
@@ -107,19 +156,6 @@ namespace XericUI.ReflectionUIGenerator
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>
@@ -41,9 +41,6 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>绑定的预制体引用</summary>
GameObject BoundPrefab { get; set; }
/// <summary>覆盖的预制体组名称</summary>
string PrefabOverrideName { get; set; }
/// <summary>关联的 MVVM 绑定列表</summary>
IReadOnlyList<AvrgMvvmBinding> Bindings { get; }
@@ -115,7 +112,6 @@ namespace XericUI.ReflectionUIGenerator
private AvrgMvvmScheduler _scheduler;
private GameObject _uiInstance;
private GameObject _boundPrefab;
private string _prefabOverrideName;
private int _fieldOrder;
private bool _uiBound; // 防止重复绑定
@@ -164,12 +160,6 @@ namespace XericUI.ReflectionUIGenerator
set => _boundPrefab = value;
}
public string PrefabOverrideName
{
get => _prefabOverrideName;
set => _prefabOverrideName = value;
}
public IReadOnlyList<AvrgMvvmBinding> Bindings => _bindings;
#endregion
@@ -263,6 +253,7 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>
/// 将 XUIAvrgMDValue 与 MVVM 绑定连接
/// 支持主绑定 + 拆分属性子绑定(通过 BindingSlots)
/// </summary>
private void BindComponentWithBinding(XUIAvrgMDValue mdValue)
{
@@ -270,17 +261,61 @@ namespace XericUI.ReflectionUIGenerator
var primaryBinding = _bindings[0];
// 数据 → UI:订阅数据变更
// ——— 1. 主绑定:数据 → UI ———
primaryBinding.OnUIDataChanged += value => UpdateUIComponent(mdValue, value);
// UI → 数据:订阅 UI 交互事件
// 根据组件类型决定 UI 事件
// ——— 主绑定:UI → 数据 ———
WireUpUIEvents(mdValue, primaryBinding);
// ——— 2. 拆分属性子绑定匹配 ———
if (primaryBinding.SubBindings?.Count > 0 && mdValue.AvailableBindingSlots?.Count > 0)
{
// 收集所有子节点上的 XUIAvrgMDValue
var childMDValues = new List<XUIAvrgMDValue>();
mdValue.GetComponentsInChildren(true, childMDValues);
foreach (var slot in mdValue.AvailableBindingSlots)
{
// 按 propertyName 匹配子绑定
var subBinding = primaryBinding.SubBindings.Find(
sb => sb.MetaData?.PropertyPath == slot.propertyName);
if (subBinding == null) continue;
// 在子节点中查找匹配 Tag 的 XUIAvrgMDValue
foreach (var childMD in childMDValues)
{
if (childMD == mdValue) continue; // 跳过自身
if (childMD.EntryTag == slot.tag || slot.tag == XuiavrgFieldTag.None)
{
// 子绑定:数据 → UI
subBinding.OnUIDataChanged += value => UpdateUIComponent(childMD, value);
// 子绑定:UI → 数据(订阅子节点的交互事件)
WireUpUIEvents(childMD, subBinding);
// 广播子元数据到子节点样式组件
childMD.InitializeWithMetaData(subBinding.MetaData);
break;
}
}
}
}
// 广播主元数据到 XUIAvrgMDValue(触发 IXUIAvrgMDValueStyle 子组件)
mdValue.InitializeWithMetaData(primaryBinding.MetaData);
}
/// <summary>
/// 订阅 UI 组件的交互事件 → 写入绑定
/// </summary>
private void WireUpUIEvents(XUIAvrgMDValue mdValue, AvrgMvvmBinding binding)
{
if (mdValue.TryGetComponent(out UnityEngine.UI.Button uiButton))
{
uiButton.onClick.AddListener(() =>
{
primaryBinding.WriteFromUI(true);
_scheduler?.MarkUIDirty(primaryBinding);
binding.WriteFromUI(true);
_scheduler?.MarkUIDirty(binding);
});
}
@@ -288,8 +323,8 @@ namespace XericUI.ReflectionUIGenerator
{
uiToggle.onValueChanged.AddListener(val =>
{
primaryBinding.WriteFromUI(val);
_scheduler?.MarkUIDirty(primaryBinding);
binding.WriteFromUI(val);
_scheduler?.MarkUIDirty(binding);
});
}
@@ -297,8 +332,8 @@ namespace XericUI.ReflectionUIGenerator
{
uiSlider.onValueChanged.AddListener(val =>
{
primaryBinding.WriteFromUI(val);
_scheduler?.MarkUIDirty(primaryBinding);
binding.WriteFromUI(val);
_scheduler?.MarkUIDirty(binding);
});
}
@@ -306,8 +341,8 @@ namespace XericUI.ReflectionUIGenerator
{
uiInput.onEndEdit.AddListener(val =>
{
primaryBinding.WriteFromUI(val);
_scheduler?.MarkUIDirty(primaryBinding);
binding.WriteFromUI(val);
_scheduler?.MarkUIDirty(binding);
});
}
@@ -315,13 +350,10 @@ namespace XericUI.ReflectionUIGenerator
{
uiDropdown.onValueChanged.AddListener(val =>
{
primaryBinding.WriteFromUI(val);
_scheduler?.MarkUIDirty(primaryBinding);
binding.WriteFromUI(val);
_scheduler?.MarkUIDirty(binding);
});
}
// 广播元数据到 XUIAvrgMDValue(触发 IXUIAvrgMDValueStyle 子组件)
mdValue.InitializeWithMetaData(primaryBinding.MetaData);
}
/// <summary>
@@ -377,7 +409,6 @@ namespace XericUI.ReflectionUIGenerator
_entryPath = null;
_parent = null;
_fieldOrder = 0;
_prefabOverrideName = null;
_uiBound = false;
}
@@ -387,6 +418,16 @@ namespace XericUI.ReflectionUIGenerator
{
var value = binding.ReadFromDataSource();
binding.NotifyUIUpdate(value);
// 子绑定也做初始值推送
if (binding.SubBindings?.Count > 0)
{
foreach (var subBinding in binding.SubBindings)
{
var subValue = subBinding.ReadFromDataSource();
subBinding.NotifyUIUpdate(subValue);
}
}
}
}
@@ -397,7 +438,6 @@ namespace XericUI.ReflectionUIGenerator
_parent = null;
_uiInstance = null;
_boundPrefab = null;
_prefabOverrideName = null;
Tag = XuiavrgFieldTag.None;
TagIndex = 0;
_fieldOrder = 0;
@@ -406,6 +446,12 @@ namespace XericUI.ReflectionUIGenerator
foreach (var binding in _bindings)
{
binding.ClearUIEvent();
// 子绑定也清理
if (binding.SubBindings?.Count > 0)
{
foreach (var sub in binding.SubBindings)
sub.ClearUIEvent();
}
}
_bindings.Clear();
_scheduler = null;
@@ -422,6 +468,11 @@ namespace XericUI.ReflectionUIGenerator
foreach (var binding in _bindings)
{
binding.ClearUIEvent();
if (binding.SubBindings?.Count > 0)
{
foreach (var sub in binding.SubBindings)
sub.ClearUIEvent();
}
}
_bindings.Clear();
_children.Clear();
@@ -63,6 +63,7 @@ namespace XericUI.ReflectionUIGenerator
{
pool.Clear();
}
_prefabPools.Clear();
}
@@ -110,9 +111,6 @@ namespace XericUI.ReflectionUIGenerator
metaData.Tag,
metaData.TagIndex);
if (!string.IsNullOrEmpty(metaData.PrefabOverrideName))
entry.PrefabOverrideName = metaData.PrefabOverrideName;
if (bindingCollection != null)
{
var matchedBindings = bindingCollection.FindByMemberName(metaData.MemberName);
@@ -238,7 +236,6 @@ namespace XericUI.ReflectionUIGenerator
{
var rootNode = new GroupTreeNode(null);
// Step 1: 将每个完整路径分裂后插入树
foreach (var fullPath in allGroupPaths)
{
if (string.IsNullOrEmpty(fullPath)) continue;
@@ -255,7 +252,6 @@ namespace XericUI.ReflectionUIGenerator
}
}
// Step 2: 深度优先遍历,创建层级 GameObject
CreateGroupGameObjects(rootNode, null, transform, "");
}
@@ -286,7 +282,6 @@ namespace XericUI.ReflectionUIGenerator
var group = groupGo.AddComponent<XuiAvrgMDGroup>();
group.Initialize(childFullPath, 0, parentGroup);
// 叶节点映射到完整路径
if (childNode.Children.Count == 0)
{
_leafGroupMap[childFullPath] = group;
@@ -331,7 +326,8 @@ namespace XericUI.ReflectionUIGenerator
if (uiInstance.GetComponentInChildren<XUIAvrgMDValue>(true) == null)
{
Debug.LogError($"[Xuiavrg] 预制体 '{prefab.name}' 不符合生成要求:缺少 XUIAvrgMDValue 组件。请确保预制体根节点或其子节点包含 XUIAvrgMDValue 脚本。");
Debug.LogError(
$"[Xuiavrg] 预制体 '{prefab.name}' 不符合生成要求:缺少 XUIAvrgMDValue 组件。请确保预制体根节点或其子节点包含 XUIAvrgMDValue 脚本。");
PrefabPoolRelease(prefab, uiInstance);
return;
}
@@ -348,29 +344,62 @@ namespace XericUI.ReflectionUIGenerator
if (metaData != null && !string.IsNullOrEmpty(metaData.GroupPath))
return metaData.GroupPath;
}
return null;
}
// ============ 预制体解析 ============
// ============ 预制体解析 (Tag + 类型匹配) ============
/// <summary>
/// 预制体解析优先级:
/// 1. Tag != None → 按 Tag + 类型匹配(无匹配时报错)
/// 2. Tag == None → 按类型匹配
/// 3. defaultPrefab 回退
/// PrefabOverrideName 已移除,由 Tag + 类型匹配替代
/// </summary>
private GameObject ResolvePrefab(IFieldEntry entry)
{
if (!string.IsNullOrEmpty(entry.PrefabOverrideName))
if (typePrefabMappingConfig == null)
{
if (typePrefabMappingConfig != null)
{
var namedPrefab = typePrefabMappingConfig.FindPrefabByName(entry.PrefabOverrideName);
if (namedPrefab != null) return namedPrefab;
}
Debug.LogError("[Xuiavrg] 未配置 typePrefabMappingConfig");
return null;
}
if (typePrefabMappingConfig != null)
var bindings = entry.Bindings;
var valueType = bindings?.Count > 0 ? bindings[0].MetaData?.ValueType : null;
// 优先级 1: Tag != None → Tag + 类型匹配
if (entry.Tag != XuiavrgFieldTag.None && valueType != null)
{
var typeMatch = typePrefabMappingConfig.FindPrefabByBindings(entry.Bindings);
if (typeMatch != null) return typeMatch;
var taggedPrefab = typePrefabMappingConfig.FindPrefabByTypeAndTag(valueType, entry.Tag);
if (taggedPrefab != null)
return taggedPrefab;
Debug.LogError(
$"[Xuiavrg] 指定 Tag '{entry.Tag}' 的预制体不存在 " +
$"(Entry: '{entry.EntryName}', 类型: {valueType.Name}),跳过 UI 生成");
return null;
}
return typePrefabMappingConfig != null ? typePrefabMappingConfig.defaultPrefab : null;
// 优先级 2: Tag == None → 按类型匹配
if (valueType != null)
{
var typeMatch = typePrefabMappingConfig.FindPrefabByType(valueType);
if (typeMatch != null)
return typeMatch;
}
// 优先级 3: defaultPrefab 回退
if (typePrefabMappingConfig.defaultPrefab != null)
{
Debug.LogWarning(
$"[Xuiavrg] 类型 '{valueType?.Name ?? "未知"}' 无精确匹配,使用默认预制体 '{typePrefabMappingConfig.defaultPrefab.name}'");
return typePrefabMappingConfig.defaultPrefab;
}
Debug.LogError(
$"[Xuiavrg] 类型 '{valueType?.Name ?? "未知"}' 无匹配预制体,且未配置 defaultPrefab");
return null;
}
// ============ 清理 ============
@@ -398,6 +427,7 @@ namespace XericUI.ReflectionUIGenerator
XuiFieldTempEntry.Release(tempEntry);
}
}
CurrentEntries.Clear();
}
@@ -410,6 +440,7 @@ namespace XericUI.ReflectionUIGenerator
Destroy(kvp.Value.gameObject);
}
}
_leafGroupMap.Clear();
}
@@ -417,6 +448,7 @@ namespace XericUI.ReflectionUIGenerator
{
ClearAll();
}
}
}
#endregion
}
+23
View File
@@ -11,10 +11,26 @@ namespace XericUI.ReflectionUIGenerator
/// 1. 提供 EntryTag/TagIndex 供 XuiFieldTempEntry 匹配
/// 2. 持有绑定的 AvrgMemberMetaData 引用
/// 3. 初始化时广播元数据到所有子节点上的 IXUIAvrgMDValueStyle
/// 4. 定义绑定槽(BindingSlots),供拆分属性子绑定匹配
/// </summary>
[AddComponentMenu("Xeric Library/UI Action Vessel/XUIAvrgMDValue")]
public class XUIAvrgMDValue : MonoBehaviour
{
/// <summary>
/// 绑定槽定义 — 用于拆分属性(Split)的子绑定匹配
/// propertyName 匹配 XuiavrgSplit 的 PropertyPath
/// tag 在预制体内查找对应子节点的 XUIAvrgMDValue
/// </summary>
[System.Serializable]
public class AvrgBindingSlot
{
[Tooltip("绑定的子属性名(匹配 XuiavrgSplit 的 PropertyPath,如 \"x\"、\"y\")")]
public string propertyName;
[Tooltip("子属性在预制体上的 Tag 标记,用于查找子节点 XUIAvrgMDValue")]
public XuiavrgFieldTag tag = XuiavrgFieldTag.Context;
}
[SerializeField]
[Tooltip("功能标记,用于与 Entry 的 Tag 匹配")]
private XuiavrgFieldTag _entryTag = XuiavrgFieldTag.None;
@@ -23,12 +39,19 @@ namespace XericUI.ReflectionUIGenerator
[Tooltip("同 Tag 多实例时区分(0 表示不限制)")]
private int _tagIndex;
[SerializeField]
[Tooltip("拆分属性子绑定槽列表 — 用于 XuiavrgSplit 的子属性匹配")]
private List<AvrgBindingSlot> _bindingSlots = new List<AvrgBindingSlot>();
/// <summary>绑定的元数据引用</summary>
public AvrgMemberMetaData BoundMetaData { get; private set; }
public XuiavrgFieldTag EntryTag => _entryTag;
public int TagIndex => _tagIndex;
/// <summary>只读访问绑定槽列表</summary>
public IReadOnlyList<AvrgBindingSlot> AvailableBindingSlots => _bindingSlots;
/// <summary>
/// 由绑定系统调用,传递元数据并广播到 IXUIAvrgMDValueStyle 子组件
/// </summary>