优化反射特性
This commit is contained in:
+31
-13
@@ -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 匹配替代
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8254aa7ff8b5fe443b04eb4b30a063d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+8
-5
@@ -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)
|
||||
|
||||
+90
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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>
|
||||
|
||||
+61
-25
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user