728 lines
26 KiB
C#
728 lines
26 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using TMPro;
|
||
|
||
namespace MeowmentDebugTool
|
||
{
|
||
/// <summary>
|
||
/// 自定义复选框模块 - 通过反射加载标记为DebugCheckBox的方法
|
||
/// </summary>
|
||
public class CustomCheckBoxesModule : 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 customCheckBoxesPage;
|
||
private RectTransform checkBoxContainer;
|
||
private GameObject checkBoxPrefab;
|
||
private ScrollRect checkBoxesScrollRect;
|
||
|
||
// 保存的SDF字体资源
|
||
private TMP_FontAsset savedFontAsset = null;
|
||
|
||
// 保存每个复选框的状态
|
||
private Dictionary<string, bool> checkBoxStates = new Dictionary<string, bool>();
|
||
|
||
// Tab相关
|
||
private Dictionary<string, List<CheckBoxInfo>> tabGroups = new Dictionary<string, List<CheckBoxInfo>>();
|
||
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 checkBoxDefinitionsDirty = true;
|
||
private bool checkBoxViewInitialized = false;
|
||
private HashSet<string> builtSubTabs = new HashSet<string>();
|
||
private GameObject subTabBarObject;
|
||
private GameObject tabContentRootObject;
|
||
#endregion
|
||
|
||
#region 内部类
|
||
private class CheckBoxInfo
|
||
{
|
||
public MethodInfo Method;
|
||
public DebugCheckBoxAttribute Attribute;
|
||
public string TabName;
|
||
}
|
||
#endregion
|
||
|
||
#region 构造函数
|
||
public CustomCheckBoxesModule(GameObject page, RectTransform container, GameObject prefab,
|
||
ScrollRect scrollRect)
|
||
{
|
||
customCheckBoxesPage = page;
|
||
checkBoxContainer = container;
|
||
checkBoxPrefab = prefab;
|
||
checkBoxesScrollRect = scrollRect;
|
||
}
|
||
#endregion
|
||
|
||
#region IDebugModule 实现
|
||
public void Initialize()
|
||
{
|
||
Debug.Log("[CustomCheckBoxesModule] 初始化自定义复选框模块...");
|
||
checkBoxDefinitionsDirty = true;
|
||
checkBoxViewInitialized = false;
|
||
}
|
||
|
||
public GameObject GetPage()
|
||
{
|
||
return customCheckBoxesPage;
|
||
}
|
||
|
||
public string GetModuleName()
|
||
{
|
||
return "开关";
|
||
}
|
||
#endregion
|
||
|
||
#region 公共方法
|
||
/// <summary>
|
||
/// 设置SDF字体
|
||
/// </summary>
|
||
public void SetSDFFont(TMP_FontAsset fontAsset)
|
||
{
|
||
savedFontAsset = fontAsset;
|
||
ApplyFontToExistingUI();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重新加载自定义复选框
|
||
/// </summary>
|
||
public void ReloadCustomCheckBoxes()
|
||
{
|
||
ReloadCheckBoxDefinitions();
|
||
InvalidateCheckBoxView();
|
||
|
||
if (customCheckBoxesPage != null && customCheckBoxesPage.activeInHierarchy)
|
||
{
|
||
EnsurePageViewInitialized();
|
||
}
|
||
}
|
||
|
||
public void EnsurePageViewInitialized()
|
||
{
|
||
if (checkBoxDefinitionsDirty)
|
||
{
|
||
ReloadCheckBoxDefinitions();
|
||
}
|
||
|
||
if (!checkBoxViewInitialized)
|
||
{
|
||
BuildCheckBoxViewSkeleton();
|
||
}
|
||
|
||
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 || customCheckBoxesPage == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
TMP_Text[] texts = customCheckBoxesPage.GetComponentsInChildren<TMP_Text>(true);
|
||
foreach (TMP_Text text in texts)
|
||
{
|
||
text.font = savedFontAsset;
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 私有方法
|
||
/// <summary>
|
||
/// 加载所有自定义复选框(使用反射)
|
||
/// </summary>
|
||
private void ReloadCheckBoxDefinitions()
|
||
{
|
||
if (checkBoxContainer == null || checkBoxPrefab == null)
|
||
{
|
||
Debug.LogWarning("[CustomCheckBoxesModule] 复选框容器或复选框预制件未设置");
|
||
return;
|
||
}
|
||
|
||
tabGroups.Clear();
|
||
tabColors.Clear();
|
||
|
||
// 查找所有标记为DebugCheckBox的方法
|
||
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<DebugCheckBoxAttribute>();
|
||
if (attribute != null)
|
||
{
|
||
var classAttribute = type.GetCustomAttribute<DebugCheckBoxClassAttribute>(true);
|
||
string tabName = ResolveTabName(attribute, classAttribute);
|
||
Color tabColor = ResolveTabColor(classAttribute);
|
||
|
||
RegisterTabColor(tabName, tabColor);
|
||
|
||
if (!tabGroups.ContainsKey(tabName))
|
||
{
|
||
tabGroups[tabName] = new List<CheckBoxInfo>();
|
||
}
|
||
|
||
tabGroups[tabName].Add(new CheckBoxInfo
|
||
{
|
||
Method = method,
|
||
Attribute = attribute,
|
||
TabName = tabName
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
// 某些程序集可能无法访问,跳过
|
||
Debug.LogWarning($"[CustomCheckBoxesModule] 无法访问程序集 {assembly.FullName}: {e.Message}");
|
||
}
|
||
}
|
||
|
||
Debug.Log($"[CustomCheckBoxesModule] 共找到 {tabGroups.Count} 个Tab");
|
||
foreach (var tab in tabGroups)
|
||
{
|
||
Debug.Log($" - Tab: {tab.Key}, 开关数: {tab.Value.Count}");
|
||
}
|
||
|
||
checkBoxDefinitionsDirty = false;
|
||
}
|
||
|
||
private string ResolveTabName(DebugCheckBoxAttribute methodAttribute, DebugCheckBoxClassAttribute classAttribute)
|
||
{
|
||
if (!string.IsNullOrEmpty(methodAttribute.TabName))
|
||
{
|
||
return methodAttribute.TabName;
|
||
}
|
||
|
||
if (classAttribute != null && !string.IsNullOrEmpty(classAttribute.TabName))
|
||
{
|
||
return classAttribute.TabName;
|
||
}
|
||
|
||
return "默认";
|
||
}
|
||
|
||
private Color ResolveTabColor(DebugCheckBoxClassAttribute 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($"[CustomCheckBoxesModule] Tab '{tabName}' 检测到多个颜色定义,已保留第一个颜色配置");
|
||
}
|
||
}
|
||
|
||
private void BuildCheckBoxViewSkeleton()
|
||
{
|
||
InvalidateCheckBoxView();
|
||
SetupCheckBoxContainerLayout();
|
||
|
||
if (tabGroups.Count == 0)
|
||
{
|
||
GameObject emptyText = new GameObject("EmptyTip");
|
||
emptyText.transform.SetParent(checkBoxContainer, false);
|
||
TMP_Text text = emptyText.AddComponent<TextMeshProUGUI>();
|
||
text.text = "未找到任何 DebugCheckBox";
|
||
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;
|
||
checkBoxViewInitialized = true;
|
||
return;
|
||
}
|
||
|
||
subTabBarObject = CreateSubTabBar();
|
||
|
||
tabContentRootObject = new GameObject("SubTabContentRoot");
|
||
tabContentRootObject.transform.SetParent(checkBoxContainer, 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 (checkBoxesScrollRect != null)
|
||
{
|
||
Canvas.ForceUpdateCanvases();
|
||
checkBoxesScrollRect.verticalNormalizedPosition = 1f;
|
||
}
|
||
|
||
checkBoxViewInitialized = 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<CheckBoxInfo> checkBoxes))
|
||
{
|
||
return;
|
||
}
|
||
|
||
foreach (var checkBoxInfo in checkBoxes)
|
||
{
|
||
CreateCustomCheckBox(checkBoxInfo.Method, checkBoxInfo.Attribute, tabPanel.transform);
|
||
}
|
||
|
||
builtSubTabs.Add(tabName);
|
||
Canvas.ForceUpdateCanvases();
|
||
}
|
||
|
||
private void InvalidateCheckBoxView()
|
||
{
|
||
if (checkBoxContainer != null)
|
||
{
|
||
foreach (Transform child in checkBoxContainer)
|
||
{
|
||
UnityEngine.Object.Destroy(child.gameObject);
|
||
}
|
||
}
|
||
|
||
tabPanels.Clear();
|
||
subTabButtons.Clear();
|
||
builtSubTabs.Clear();
|
||
currentSubTab = string.Empty;
|
||
subTabBarObject = null;
|
||
tabContentRootObject = null;
|
||
checkBoxViewInitialized = false;
|
||
}
|
||
|
||
private GameObject CreateSubTabBar()
|
||
{
|
||
GameObject subTabBar = new GameObject("SubTabBar");
|
||
subTabBar.transform.SetParent(checkBoxContainer, 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 && checkBoxContainer != null)
|
||
{
|
||
availableWidth = checkBoxContainer.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 = 15;
|
||
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 (checkBoxesScrollRect != null)
|
||
{
|
||
Canvas.ForceUpdateCanvases();
|
||
checkBoxesScrollRect.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 SetupCheckBoxContainerLayout()
|
||
{
|
||
if (checkBoxContainer == null)
|
||
{
|
||
Debug.LogError("[CustomCheckBoxesModule] checkBoxContainer 为 null,无法设置布局");
|
||
return;
|
||
}
|
||
|
||
GameObject containerObject = checkBoxContainer.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 = 15;
|
||
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 CreateCustomCheckBox(MethodInfo method, DebugCheckBoxAttribute attribute, Transform parent)
|
||
{
|
||
// 验证方法签名:必须接受一个 bool 参数
|
||
var parameters = method.GetParameters();
|
||
if (parameters.Length != 1 || parameters[0].ParameterType != typeof(bool))
|
||
{
|
||
Debug.LogWarning($"[CustomCheckBoxesModule] 方法 {method.Name} 必须接受一个 bool 参数");
|
||
return;
|
||
}
|
||
|
||
GameObject checkBoxObj = UnityEngine.Object.Instantiate(checkBoxPrefab, parent);
|
||
RectTransform checkBoxRect = checkBoxObj.GetComponent<RectTransform>();
|
||
if (checkBoxRect != null)
|
||
{
|
||
LayoutElement layoutElement = checkBoxObj.GetComponent<LayoutElement>();
|
||
if (layoutElement == null)
|
||
{
|
||
layoutElement = checkBoxObj.AddComponent<LayoutElement>();
|
||
}
|
||
|
||
layoutElement.preferredHeight = checkBoxRect.sizeDelta.y > 0 ? checkBoxRect.sizeDelta.y : 80f;
|
||
layoutElement.flexibleWidth = 1f;
|
||
}
|
||
|
||
Toggle toggle = checkBoxObj.GetComponent<Toggle>();
|
||
TMP_Text labelText = checkBoxObj.GetComponentInChildren<TMP_Text>();
|
||
Image backgroundImage = checkBoxObj.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.CheckBoxColor != default(Color))
|
||
{
|
||
backgroundImage.color = attribute.CheckBoxColor;
|
||
}
|
||
|
||
// 获取或初始化状态(默认为 false)
|
||
string methodKey = $"{method.DeclaringType?.FullName}.{method.Name}";
|
||
if (!checkBoxStates.ContainsKey(methodKey))
|
||
{
|
||
checkBoxStates[methodKey] = false;
|
||
}
|
||
|
||
// 设置初始状态
|
||
if (toggle != null)
|
||
{
|
||
toggle.isOn = checkBoxStates[methodKey];
|
||
toggle.onValueChanged.AddListener((bool isOn) =>
|
||
{
|
||
try
|
||
{
|
||
// 保存状态
|
||
checkBoxStates[methodKey] = isOn;
|
||
|
||
// 调用方法,传递状态
|
||
method.Invoke(null, new object[] { isOn });
|
||
Debug.Log($"[CustomCheckBoxesModule] 执行调试方法: {method.Name}, 状态: {isOn}");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"[CustomCheckBoxesModule] 执行调试方法 {method.Name} 时出错: {e.Message}");
|
||
}
|
||
});
|
||
}
|
||
}
|
||
#endregion
|
||
}
|
||
}
|