MeowmentDebugTool/Packages/com.bywaystudios.meowmentdebugtool/Runtime/CustomValuesModule.cs
zhang hongbo cf352f2a22 11
2026-04-06 10:03:28 +08:00

993 lines
36 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace MeowmentDebugTool
{
/// <summary>
/// 自定义数值模块 - 通过反射加载标记为DebugValue的方法
/// </summary>
public class CustomValuesModule : IDebugModule
{
#region
private const float SubTabCellWidth = 200f;
private const float SubTabCellHeight = 70f;
private const float SubTabSpacingX = 8f;
private const float SubTabSpacingY = 8f;
private const int SubTabPaddingHorizontal = 20;
private const int SubTabPaddingVertical = 20;
private GameObject customValuesPage;
private RectTransform valueContainer;
private GameObject valuePrefab;
private ScrollRect valuesScrollRect;
// 保存的SDF字体资源
private TMP_FontAsset savedFontAsset = null;
// 保存每个数值的当前值
private Dictionary<string, int> valueStates = new Dictionary<string, int>();
// 保存每个数值的滑动条和输入框引用(用于动态更新范围)
private Dictionary<string, ValueControlData> valueControls = new Dictionary<string, ValueControlData>();
// Tab相关
private Dictionary<string, List<ValueInfo>> tabGroups = new Dictionary<string, List<ValueInfo>>();
private Dictionary<string, GameObject> tabPanels = new Dictionary<string, GameObject>();
private Dictionary<string, Color> tabColors = new Dictionary<string, Color>();
private List<Button> subTabButtons = new List<Button>();
private string currentSubTab = string.Empty;
private bool valueDefinitionsDirty = true;
private bool valueViewInitialized = false;
private HashSet<string> builtSubTabs = new HashSet<string>();
private GameObject subTabBarObject;
private GameObject tabContentRootObject;
// 关闭窗口回调
private Action onCloseWindowCallback;
/// <summary>
/// 数值控件数据结构
/// </summary>
private class ValueControlData
{
public Slider slider;
public TMP_InputField inputField;
public MethodInfo method;
public DebugValueAttribute attribute;
}
/// <summary>
/// 数值信息
/// </summary>
private class ValueInfo
{
public MethodInfo Method;
public DebugValueAttribute Attribute;
public string TabName;
}
#endregion
#region
public CustomValuesModule(GameObject page, RectTransform container, GameObject prefab,
ScrollRect scrollRect, Action closeWindowCallback)
{
customValuesPage = page;
valueContainer = container;
valuePrefab = prefab;
valuesScrollRect = scrollRect;
onCloseWindowCallback = closeWindowCallback;
}
#endregion
#region IDebugModule
public void Initialize()
{
Debug.Log("[CustomValuesModule] 初始化自定义数值模块...");
valueDefinitionsDirty = true;
valueViewInitialized = false;
}
public GameObject GetPage()
{
return customValuesPage;
}
public string GetModuleName()
{
return "数值";
}
#endregion
#region
/// <summary>
/// 设置SDF字体
/// </summary>
public void SetSDFFont(TMP_FontAsset fontAsset)
{
savedFontAsset = fontAsset;
ApplyFontToExistingUI();
}
/// <summary>
/// 重新加载自定义数值
/// </summary>
public void ReloadCustomValues()
{
ReloadValueDefinitions();
InvalidateValueView();
if (customValuesPage != null && customValuesPage.activeInHierarchy)
{
EnsurePageViewInitialized();
}
}
public void EnsurePageViewInitialized()
{
if (valueDefinitionsDirty)
{
ReloadValueDefinitions();
}
if (!valueViewInitialized)
{
BuildValueViewSkeleton();
}
if (string.IsNullOrEmpty(currentSubTab) && tabGroups.Count > 0)
{
currentSubTab = tabGroups.OrderBy(kvp => kvp.Key == "默认" ? string.Empty : kvp.Key).First().Key;
}
if (!string.IsNullOrEmpty(currentSubTab))
{
EnsureSubTabContentBuilt(currentSubTab);
}
UpdateSubTabButtonStyles();
}
private void ApplyFontToExistingUI()
{
if (savedFontAsset == null || customValuesPage == null)
{
return;
}
TMP_Text[] texts = customValuesPage.GetComponentsInChildren<TMP_Text>(true);
foreach (TMP_Text text in texts)
{
text.font = savedFontAsset;
}
}
/// <summary>
/// 更新指定方法的数值范围
/// </summary>
/// <param name="methodName">方法名称(类名.方法名 或 方法名)</param>
/// <param name="minValue">新的最小值</param>
/// <param name="maxValue">新的最大值</param>
/// <returns>是否成功更新</returns>
public bool UpdateValueRange(string methodName, int minValue, int maxValue)
{
if (minValue >= maxValue)
{
Debug.LogWarning($"[CustomValuesModule] 无效的范围: minValue({minValue}) >= maxValue({maxValue})");
return false;
}
// 尝试精确匹配
if (valueControls.ContainsKey(methodName))
{
return UpdateValueRangeInternal(methodName, minValue, maxValue);
}
// 尝试模糊匹配(只用方法名)
foreach (var kvp in valueControls)
{
string key = kvp.Key;
string simpleMethodName = key.Substring(key.LastIndexOf('.') + 1);
if (simpleMethodName == methodName)
{
return UpdateValueRangeInternal(key, minValue, maxValue);
}
}
Debug.LogWarning($"[CustomValuesModule] 未找到方法: {methodName}");
return false;
}
/// <summary>
/// 更新指定方法的当前值
/// </summary>
/// <param name="methodName">方法名称(类名.方法名 或 方法名)</param>
/// <param name="value">新的值</param>
/// <returns>是否成功更新</returns>
public bool UpdateValue(string methodName, int value)
{
// 尝试精确匹配
if (valueControls.ContainsKey(methodName))
{
return UpdateValueInternal(methodName, value);
}
// 尝试模糊匹配(只用方法名)
foreach (var kvp in valueControls)
{
string key = kvp.Key;
string simpleMethodName = key.Substring(key.LastIndexOf('.') + 1);
if (simpleMethodName == methodName)
{
return UpdateValueInternal(key, value);
}
}
Debug.LogWarning($"[CustomValuesModule] 未找到方法: {methodName}");
return false;
}
private bool UpdateValueRangeInternal(string methodKey, int minValue, int maxValue)
{
var data = valueControls[methodKey];
// 更新attribute的范围
data.attribute.MinValue = minValue;
data.attribute.MaxValue = maxValue;
// 更新slider的范围
if (data.slider != null)
{
data.slider.minValue = minValue;
data.slider.maxValue = maxValue;
// 确保当前值在新范围内
int currentValue = valueStates[methodKey];
int clampedValue = Mathf.Clamp(currentValue, minValue, maxValue);
if (currentValue != clampedValue)
{
valueStates[methodKey] = clampedValue;
data.slider.value = clampedValue;
if (data.inputField != null)
{
data.inputField.text = clampedValue.ToString();
}
}
}
Debug.Log($"[CustomValuesModule] 已更新 {methodKey} 的范围: [{minValue}, {maxValue}]");
return true;
}
private bool UpdateValueInternal(string methodKey, int value)
{
var data = valueControls[methodKey];
// 确保值在范围内
int clampedValue = Mathf.Clamp(value, data.attribute.MinValue, data.attribute.MaxValue);
if (clampedValue != value)
{
Debug.LogWarning($"[CustomValuesModule] 值 {value} 超出范围 [{data.attribute.MinValue}, {data.attribute.MaxValue}],已限制为 {clampedValue}");
}
// 更新保存的状态
valueStates[methodKey] = clampedValue;
// 更新slider
if (data.slider != null)
{
data.slider.value = clampedValue;
}
// 更新输入框
if (data.inputField != null)
{
data.inputField.text = clampedValue.ToString();
}
Debug.Log($"[CustomValuesModule] 已更新 {methodKey} 的值: {clampedValue}");
return true;
}
#endregion
#region
/// <summary>
/// 加载所有自定义数值(使用反射)
/// </summary>
private void ReloadValueDefinitions()
{
if (valueContainer == null || valuePrefab == null)
{
Debug.LogWarning("[CustomValuesModule] 数值容器或数值预制件未设置");
return;
}
// 清空控件引用
valueControls.Clear();
tabGroups.Clear();
tabColors.Clear();
// 查找所有标记为DebugValue的方法
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
try
{
var types = assembly.GetTypes();
foreach (var type in types)
{
var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var method in methods)
{
var attribute = method.GetCustomAttribute<DebugValueAttribute>();
if (attribute != null)
{
var classAttribute = type.GetCustomAttribute<DebugValueClassAttribute>(true);
string tabName = ResolveTabName(attribute, classAttribute);
Color tabColor = ResolveTabColor(classAttribute);
RegisterTabColor(tabName, tabColor);
if (!tabGroups.ContainsKey(tabName))
{
tabGroups[tabName] = new List<ValueInfo>();
}
tabGroups[tabName].Add(new ValueInfo
{
Method = method,
Attribute = attribute,
TabName = tabName
});
}
}
}
}
catch (Exception e)
{
// 某些程序集可能无法访问,跳过
Debug.LogWarning($"[CustomValuesModule] 无法访问程序集 {assembly.FullName}: {e.Message}");
}
}
Debug.Log($"[CustomValuesModule] 共找到 {tabGroups.Count} 个Tab");
foreach (var tab in tabGroups)
{
Debug.Log($" - Tab: {tab.Key}, 数值数: {tab.Value.Count}");
}
valueDefinitionsDirty = false;
}
private string ResolveTabName(DebugValueAttribute methodAttribute, DebugValueClassAttribute classAttribute)
{
if (!string.IsNullOrEmpty(methodAttribute.TabName))
{
return methodAttribute.TabName;
}
if (classAttribute != null && !string.IsNullOrEmpty(classAttribute.TabName))
{
return classAttribute.TabName;
}
return "默认";
}
private Color ResolveTabColor(DebugValueClassAttribute classAttribute)
{
if (classAttribute != null)
{
return classAttribute.TabColor;
}
return new Color(0.2f, 0.6f, 1f, 1f);
}
private void RegisterTabColor(string tabName, Color tabColor)
{
if (!tabColors.ContainsKey(tabName))
{
tabColors[tabName] = tabColor;
return;
}
Color existing = tabColors[tabName];
if (!Mathf.Approximately(existing.r, tabColor.r) ||
!Mathf.Approximately(existing.g, tabColor.g) ||
!Mathf.Approximately(existing.b, tabColor.b) ||
!Mathf.Approximately(existing.a, tabColor.a))
{
Debug.LogWarning($"[CustomValuesModule] Tab '{tabName}' 检测到多个颜色定义,已保留第一个颜色配置");
}
}
private void BuildValueViewSkeleton()
{
InvalidateValueView();
SetupValueContainerLayout();
if (tabGroups.Count == 0)
{
GameObject emptyText = new GameObject("EmptyTip");
emptyText.transform.SetParent(valueContainer, false);
TMP_Text text = emptyText.AddComponent<TextMeshProUGUI>();
text.text = "未找到任何 DebugValue";
text.fontSize = 28;
text.fontStyle = FontStyles.Bold;
text.enableAutoSizing = true;
text.fontSizeMin = 12;
text.fontSizeMax = 72;
text.alignment = TextAlignmentOptions.Center;
text.color = new Color(1f, 1f, 1f, 0.8f);
if (savedFontAsset != null)
{
text.font = savedFontAsset;
}
LayoutElement element = emptyText.AddComponent<LayoutElement>();
element.preferredHeight = 80;
valueViewInitialized = true;
return;
}
subTabBarObject = CreateSubTabBar();
tabContentRootObject = new GameObject("SubTabContentRoot");
tabContentRootObject.transform.SetParent(valueContainer, false);
RectTransform contentRootRect = tabContentRootObject.AddComponent<RectTransform>();
contentRootRect.anchorMin = new Vector2(0, 1);
contentRootRect.anchorMax = new Vector2(1, 1);
contentRootRect.pivot = new Vector2(0.5f, 1f);
VerticalLayoutGroup contentRootLayout = tabContentRootObject.AddComponent<VerticalLayoutGroup>();
contentRootLayout.childForceExpandWidth = true;
contentRootLayout.childForceExpandHeight = false;
contentRootLayout.childControlWidth = true;
contentRootLayout.childControlHeight = false;
contentRootLayout.spacing = 0;
contentRootLayout.padding = new RectOffset(0, 0, 0, 0);
ContentSizeFitter contentRootFitter = tabContentRootObject.AddComponent<ContentSizeFitter>();
contentRootFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
contentRootFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
LayoutElement contentRootElement = tabContentRootObject.AddComponent<LayoutElement>();
contentRootElement.flexibleWidth = 1;
var sortedTabs = tabGroups.OrderBy(kvp => kvp.Key == "默认" ? string.Empty : kvp.Key);
bool firstTab = true;
foreach (var tab in sortedTabs)
{
string tabName = tab.Key;
CreateSubTabButton(tabName, subTabBarObject.transform);
GameObject tabPanel = CreateTabPanel(tabName, tabContentRootObject.transform);
tabPanels[tabName] = tabPanel;
tabPanel.SetActive(firstTab);
if (firstTab)
{
currentSubTab = tabName;
firstTab = false;
}
}
Canvas.ForceUpdateCanvases();
UpdateSubTabBarHeight(subTabBarObject);
UpdateSubTabButtonStyles();
if (valuesScrollRect != null)
{
Canvas.ForceUpdateCanvases();
valuesScrollRect.verticalNormalizedPosition = 1f;
}
valueViewInitialized = true;
}
private void EnsureSubTabContentBuilt(string tabName)
{
if (string.IsNullOrEmpty(tabName) || builtSubTabs.Contains(tabName))
{
return;
}
if (!tabPanels.TryGetValue(tabName, out GameObject tabPanel) || tabPanel == null)
{
return;
}
if (!tabGroups.TryGetValue(tabName, out List<ValueInfo> values))
{
return;
}
foreach (var valueInfo in values)
{
CreateCustomValue(valueInfo.Method, valueInfo.Attribute, tabPanel.transform);
}
builtSubTabs.Add(tabName);
Canvas.ForceUpdateCanvases();
}
private void InvalidateValueView()
{
if (valueContainer != null)
{
foreach (Transform child in valueContainer)
{
UnityEngine.Object.Destroy(child.gameObject);
}
}
valueControls.Clear();
tabPanels.Clear();
subTabButtons.Clear();
builtSubTabs.Clear();
currentSubTab = string.Empty;
subTabBarObject = null;
tabContentRootObject = null;
valueViewInitialized = false;
}
private GameObject CreateSubTabBar()
{
GameObject subTabBar = new GameObject("SubTabBar");
subTabBar.transform.SetParent(valueContainer, false);
RectTransform rectTransform = subTabBar.AddComponent<RectTransform>();
rectTransform.anchorMin = new Vector2(0, 1);
rectTransform.anchorMax = new Vector2(1, 1);
rectTransform.pivot = new Vector2(0.5f, 1f);
Image bg = subTabBar.AddComponent<Image>();
bg.color = new Color(0.15f, 0.15f, 0.15f, 0.8f);
GridLayoutGroup layout = subTabBar.AddComponent<GridLayoutGroup>();
layout.cellSize = new Vector2(SubTabCellWidth, SubTabCellHeight);
layout.spacing = new Vector2(SubTabSpacingX, SubTabSpacingY);
layout.padding = new RectOffset(10, 10, 10, 10);
layout.constraint = GridLayoutGroup.Constraint.Flexible;
layout.startAxis = GridLayoutGroup.Axis.Horizontal;
layout.startCorner = GridLayoutGroup.Corner.UpperLeft;
layout.childAlignment = TextAnchor.UpperLeft;
ContentSizeFitter fitter = subTabBar.AddComponent<ContentSizeFitter>();
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
LayoutElement element = subTabBar.AddComponent<LayoutElement>();
element.minHeight = 80;
element.preferredHeight = 80;
element.flexibleWidth = 1;
return subTabBar;
}
private void CreateSubTabButton(string tabName, Transform parent)
{
GameObject tabButtonObj = new GameObject($"SubTab_{tabName}");
tabButtonObj.transform.SetParent(parent, false);
RectTransform rect = tabButtonObj.AddComponent<RectTransform>();
rect.sizeDelta = new Vector2(200, 56);
Image image = tabButtonObj.AddComponent<Image>();
image.color = GetInactiveTabColor(tabName);
Button button = tabButtonObj.AddComponent<Button>();
GameObject textObj = new GameObject("Text");
textObj.transform.SetParent(tabButtonObj.transform, false);
RectTransform textRect = textObj.AddComponent<RectTransform>();
textRect.anchorMin = Vector2.zero;
textRect.anchorMax = Vector2.one;
textRect.offsetMin = Vector2.zero;
textRect.offsetMax = Vector2.zero;
TMP_Text text = textObj.AddComponent<TextMeshProUGUI>();
text.text = tabName;
text.fontSize = 26;
text.fontStyle = FontStyles.Bold;
text.enableAutoSizing = true;
text.fontSizeMin = 12;
text.fontSizeMax = 72;
text.alignment = TextAlignmentOptions.Center;
text.color = Color.white;
if (savedFontAsset != null)
{
text.font = savedFontAsset;
}
LayoutElement element = tabButtonObj.AddComponent<LayoutElement>();
element.preferredWidth = SubTabCellWidth;
element.minWidth = SubTabCellWidth;
element.preferredHeight = SubTabCellHeight;
button.onClick.AddListener(() => SelectSubTab(tabName));
subTabButtons.Add(button);
}
private void UpdateSubTabBarHeight(GameObject subTabBar)
{
if (subTabBar == null)
{
return;
}
RectTransform subTabRect = subTabBar.GetComponent<RectTransform>();
LayoutElement layoutElement = subTabBar.GetComponent<LayoutElement>();
if (subTabRect == null || layoutElement == null)
{
return;
}
float availableWidth = subTabRect.rect.width;
if (availableWidth <= 0f && valueContainer != null)
{
availableWidth = valueContainer.rect.width;
}
if (availableWidth <= 0f)
{
return;
}
float usableWidth = Mathf.Max(1f, availableWidth - SubTabPaddingHorizontal);
int columnCount = Mathf.Max(1, Mathf.FloorToInt((usableWidth + SubTabSpacingX) / (SubTabCellWidth + SubTabSpacingX)));
int rowCount = Mathf.Max(1, Mathf.CeilToInt((float)subTabButtons.Count / columnCount));
float preferredHeight = SubTabPaddingVertical + rowCount * SubTabCellHeight + Mathf.Max(0, rowCount - 1) * SubTabSpacingY;
layoutElement.minHeight = preferredHeight;
layoutElement.preferredHeight = preferredHeight;
LayoutRebuilder.ForceRebuildLayoutImmediate(subTabRect);
}
private GameObject CreateTabPanel(string tabName, Transform parent)
{
GameObject panel = new GameObject($"Panel_{tabName}");
panel.transform.SetParent(parent, false);
RectTransform rectTransform = panel.AddComponent<RectTransform>();
rectTransform.anchorMin = new Vector2(0, 1);
rectTransform.anchorMax = new Vector2(1, 1);
rectTransform.pivot = new Vector2(0.5f, 1f);
VerticalLayoutGroup layout = panel.AddComponent<VerticalLayoutGroup>();
layout.childForceExpandWidth = true;
layout.childForceExpandHeight = false;
layout.childControlWidth = true;
layout.childControlHeight = false;
layout.spacing = 20;
layout.padding = new RectOffset(0, 0, 10, 10);
ContentSizeFitter fitter = panel.AddComponent<ContentSizeFitter>();
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
LayoutElement element = panel.AddComponent<LayoutElement>();
element.flexibleWidth = 1;
return panel;
}
private void SelectSubTab(string tabName)
{
if (string.IsNullOrEmpty(tabName))
{
return;
}
EnsureSubTabContentBuilt(tabName);
if (currentSubTab == tabName)
{
return;
}
currentSubTab = tabName;
foreach (var kvp in tabPanels)
{
kvp.Value.SetActive(kvp.Key == tabName);
}
UpdateSubTabButtonStyles();
if (valuesScrollRect != null)
{
Canvas.ForceUpdateCanvases();
valuesScrollRect.verticalNormalizedPosition = 1f;
}
}
private void UpdateSubTabButtonStyles()
{
foreach (var button in subTabButtons)
{
if (button == null)
{
continue;
}
string tabName = button.gameObject.name.Replace("SubTab_", string.Empty);
Image image = button.GetComponent<Image>();
if (image != null)
{
image.color = tabName == currentSubTab
? GetActiveTabColor(tabName)
: GetInactiveTabColor(tabName);
}
}
}
private Color GetActiveTabColor(string tabName)
{
if (tabColors.TryGetValue(tabName, out var color))
{
color.a = 0.95f;
return color;
}
return new Color(0.2f, 0.6f, 1f, 0.95f);
}
private Color GetInactiveTabColor(string tabName)
{
Color activeColor = GetActiveTabColor(tabName);
Color inactive = Color.Lerp(new Color(0.18f, 0.18f, 0.18f, 0.9f), activeColor, 0.35f);
inactive.a = 0.9f;
return inactive;
}
private void SetupValueContainerLayout()
{
if (valueContainer == null)
{
Debug.LogError("[CustomValuesModule] valueContainer 为 null无法设置布局");
return;
}
GameObject containerObject = valueContainer.gameObject;
GridLayoutGroup gridLayout = containerObject.GetComponent<GridLayoutGroup>();
if (gridLayout != null)
{
UnityEngine.Object.DestroyImmediate(gridLayout);
}
HorizontalLayoutGroup horizontalLayout = containerObject.GetComponent<HorizontalLayoutGroup>();
if (horizontalLayout != null)
{
UnityEngine.Object.DestroyImmediate(horizontalLayout);
}
VerticalLayoutGroup verticalLayout = containerObject.GetComponent<VerticalLayoutGroup>();
if (verticalLayout == null)
{
verticalLayout = containerObject.AddComponent<VerticalLayoutGroup>();
}
verticalLayout.childForceExpandWidth = true;
verticalLayout.childForceExpandHeight = false;
verticalLayout.childControlWidth = true;
verticalLayout.childControlHeight = false;
verticalLayout.spacing = 20;
verticalLayout.padding = new RectOffset(20, 20, 20, 20);
ContentSizeFitter sizeFitter = containerObject.GetComponent<ContentSizeFitter>();
if (sizeFitter == null)
{
sizeFitter = containerObject.AddComponent<ContentSizeFitter>();
}
sizeFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
sizeFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
}
private void CreateCustomValue(MethodInfo method, DebugValueAttribute attribute, Transform parent)
{
// 验证方法签名:必须接受一个 int 参数
var parameters = method.GetParameters();
if (parameters.Length != 1 || parameters[0].ParameterType != typeof(int))
{
Debug.LogWarning($"[CustomValuesModule] 方法 {method.Name} 必须接受一个 int 参数");
return;
}
GameObject valueObj = UnityEngine.Object.Instantiate(valuePrefab, parent);
RectTransform valueRect = valueObj.GetComponent<RectTransform>();
if (valueRect != null)
{
LayoutElement layoutElement = valueObj.GetComponent<LayoutElement>();
if (layoutElement == null)
{
layoutElement = valueObj.AddComponent<LayoutElement>();
}
layoutElement.preferredHeight = valueRect.sizeDelta.y > 0 ? valueRect.sizeDelta.y : 140f;
layoutElement.flexibleWidth = 1f;
}
// 获取组件
TMP_Text labelText = valueObj.transform.Find("Label")?.GetComponent<TMP_Text>();
Slider slider = valueObj.transform.Find("Slider")?.GetComponent<Slider>();
TMP_InputField inputField = valueObj.transform.Find("InputField")?.GetComponent<TMP_InputField>();
Button confirmButton = valueObj.transform.Find("ConfirmButton")?.GetComponent<Button>();
Image backgroundImage = valueObj.GetComponent<Image>();
// 设置标签文本
if (labelText != null)
{
labelText.text = string.IsNullOrEmpty(attribute.DisplayName) ? method.Name : attribute.DisplayName;
// 应用保存的字体
if (savedFontAsset != null)
{
labelText.font = savedFontAsset;
}
}
// 设置背景颜色
if (backgroundImage != null && attribute.ValueColor != default(Color))
{
backgroundImage.color = attribute.ValueColor;
}
// 获取或初始化状态(使用默认值)
string methodKey = $"{method.DeclaringType?.FullName}.{method.Name}";
if (!valueStates.ContainsKey(methodKey))
{
valueStates[methodKey] = attribute.DefaultValue;
}
int currentValue = valueStates[methodKey];
// 保存控件引用
valueControls[methodKey] = new ValueControlData
{
slider = slider,
inputField = inputField,
method = method,
attribute = attribute
};
// 设置滑动条
if (slider != null)
{
slider.minValue = attribute.MinValue;
slider.maxValue = attribute.MaxValue;
slider.wholeNumbers = true;
slider.value = currentValue;
// 滑动条值改变时更新输入框和保存状态
slider.onValueChanged.AddListener((float value) =>
{
int intValue = Mathf.RoundToInt(value);
if (inputField != null)
{
inputField.text = intValue.ToString();
}
valueStates[methodKey] = intValue;
});
}
// 设置输入框
if (inputField != null)
{
inputField.text = currentValue.ToString();
// 根据最小值判断是否允许负数
// 如果最小值小于0使用Standard类型并限制只能输入数字和负号
inputField.contentType = attribute.MinValue < 0
? TMP_InputField.ContentType.Standard
: TMP_InputField.ContentType.IntegerNumber;
// 如果允许负数,需要设置字符验证来只允许数字和负号
if (attribute.MinValue < 0)
{
inputField.characterValidation = TMP_InputField.CharacterValidation.None;
inputField.inputType = TMP_InputField.InputType.Standard;
inputField.keyboardType = TouchScreenKeyboardType.NumbersAndPunctuation;
}
// 应用保存的字体
if (savedFontAsset != null)
{
inputField.textComponent.font = savedFontAsset;
}
// 输入框值改变时更新滑动条和保存状态
inputField.onValueChanged.AddListener((string text) =>
{
// 允许输入过程中暂时为空或只有负号
if (string.IsNullOrEmpty(text) || text == "-")
{
return;
}
if (int.TryParse(text, out int intValue))
{
// 限制范围
intValue = Mathf.Clamp(intValue, attribute.MinValue, attribute.MaxValue);
if (slider != null)
{
slider.value = intValue;
}
valueStates[methodKey] = intValue;
}
});
// 输入框失去焦点时确保值在范围内
inputField.onEndEdit.AddListener((string text) =>
{
if (int.TryParse(text, out int intValue))
{
intValue = Mathf.Clamp(intValue, attribute.MinValue, attribute.MaxValue);
inputField.text = intValue.ToString();
valueStates[methodKey] = intValue;
if (slider != null)
{
slider.value = intValue;
}
}
else
{
// 如果输入无效,恢复为当前保存的值
inputField.text = valueStates[methodKey].ToString();
}
});
}
// 设置确认按钮
if (confirmButton != null)
{
// 应用保存的字体到按钮文本
if (savedFontAsset != null)
{
TMP_Text btnText = confirmButton.GetComponentInChildren<TMP_Text>();
if (btnText != null)
{
btnText.font = savedFontAsset;
}
}
confirmButton.onClick.AddListener(() =>
{
try
{
int finalValue = valueStates[methodKey];
// 调用方法,传递数值
method.Invoke(null, new object[] { finalValue });
Debug.Log($"[CustomValuesModule] 执行调试方法: {method.Name}, 数值: {finalValue}");
// 点击确认按钮后关闭主窗口
onCloseWindowCallback?.Invoke(); }
catch (Exception e)
{
Debug.LogError($"[CustomValuesModule] 执行调试方法 {method.Name} 时出错: {e.Message}");
}
});
}
}
#endregion
}
}