2895 lines
101 KiB
C#
2895 lines
101 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using TMPro;
|
||
|
||
namespace MeowmentDebugTool
|
||
{
|
||
/// <summary>
|
||
/// 通用调试工具 - 支持多标签页的调试界面系统
|
||
/// 默认分辨率: 1080x2340
|
||
/// 使用方法:
|
||
/// 1. 场景中放置 UniversalDebugTool 预制件
|
||
/// 2. 在代码中调用 UniversalDebugTool.Init() 初始化
|
||
/// 3. 未调用 Init() 前不会显示任何UI
|
||
/// </summary>
|
||
public class UniversalDebugTool : MonoBehaviour
|
||
{
|
||
#region 序列化字段
|
||
[Header("主窗口设置")]
|
||
[SerializeField] private RectTransform mainWindow;
|
||
[SerializeField] private Canvas canvas;
|
||
|
||
[Header("标签页系统")]
|
||
[SerializeField] private RectTransform tabButtonContainer;
|
||
[SerializeField] private RectTransform contentContainer;
|
||
[SerializeField] private GameObject tabButtonPrefab;
|
||
[SerializeField] private Button closeButton;
|
||
[SerializeField] private Color activeTabColor = Color.green;
|
||
[SerializeField] private Color inactiveTabColor = Color.gray;
|
||
|
||
[Header("悬浮按钮")]
|
||
[SerializeField] private GameObject floatingButton;
|
||
[SerializeField] private DraggableFloatingButton draggableComponent;
|
||
|
||
[Header("参数查看页面")]
|
||
[SerializeField] private GameObject parametersPage;
|
||
[SerializeField] private TMP_Text deviceInfoText;
|
||
[SerializeField] private TMP_Text systemInfoText;
|
||
[SerializeField] private ScrollRect parametersScrollRect;
|
||
|
||
[Header("自定义按钮页面")]
|
||
[SerializeField] private GameObject customButtonsPage;
|
||
[SerializeField] private RectTransform buttonContainer;
|
||
[SerializeField] private GameObject buttonPrefab;
|
||
[SerializeField] private ScrollRect buttonsScrollRect;
|
||
|
||
[Header("自定义复选框页面")]
|
||
[SerializeField] private GameObject customCheckBoxesPage;
|
||
[SerializeField] private RectTransform checkBoxContainer;
|
||
[SerializeField] private GameObject checkBoxPrefab;
|
||
[SerializeField] private ScrollRect checkBoxesScrollRect;
|
||
|
||
[Header("数值页面")]
|
||
[SerializeField] private GameObject customValuesPage;
|
||
[SerializeField] private RectTransform valueContainer;
|
||
[SerializeField] private GameObject valuePrefab;
|
||
[SerializeField] private ScrollRect valuesScrollRect;
|
||
|
||
[Header("设置页面")]
|
||
[SerializeField] private GameObject settingsPage;
|
||
[SerializeField] private TMP_InputField widthInputField;
|
||
[SerializeField] private TMP_InputField heightInputField;
|
||
[SerializeField] private Button applyResolutionButton;
|
||
[SerializeField] private Button resetResolutionButton;
|
||
[SerializeField] private TMP_Text currentResolutionText;
|
||
[SerializeField] private TMP_InputField infoBufferInputField;
|
||
[SerializeField] private TMP_InputField warningBufferInputField;
|
||
[SerializeField] private TMP_InputField errorBufferInputField;
|
||
[SerializeField] private TMP_InputField fatalBufferInputField;
|
||
[SerializeField] private Button applyBufferButton;
|
||
[SerializeField] private Button resetBufferButton;
|
||
|
||
[Header("控制台页面")]
|
||
[SerializeField] private GameObject consolePage;
|
||
[SerializeField] private ScrollRect consoleLogScrollRect;
|
||
[SerializeField] private RectTransform consoleLogContent;
|
||
[SerializeField] private ScrollRect consoleDetailScrollRect;
|
||
[SerializeField] private TMP_Text consoleDetailText;
|
||
[SerializeField] private Button consoleClearButton;
|
||
[SerializeField] private Toggle consoleLockScrollToggle;
|
||
[SerializeField] private Toggle consoleInfoFilterToggle;
|
||
[SerializeField] private Toggle consoleWarningFilterToggle;
|
||
[SerializeField] private Toggle consoleErrorFilterToggle;
|
||
[SerializeField] private Toggle consoleFatalFilterToggle;
|
||
[SerializeField] private TMP_InputField consoleTextFilterInputField;
|
||
[SerializeField] private GameObject consoleLogItemPrefab;
|
||
|
||
[Header("输入对话框")]
|
||
[SerializeField] private GameObject inputDialog;
|
||
[SerializeField] private TMP_Text inputDialogTitle;
|
||
[SerializeField] private TMP_Text inputDialogDescription;
|
||
[SerializeField] private RectTransform inputDialogFieldContainer;
|
||
[SerializeField] private Button inputDialogConfirmBtn;
|
||
[SerializeField] private Button inputDialogCancelBtn;
|
||
#endregion
|
||
|
||
#region 私有变量
|
||
private static UniversalDebugTool instance;
|
||
private static bool isInitialized = false;
|
||
private Dictionary<string, GameObject> pages = new Dictionary<string, GameObject>();
|
||
private Dictionary<string, Button> tabButtons = new Dictionary<string, Button>();
|
||
private string currentPageKey = "";
|
||
|
||
// Canvas层级设置
|
||
private const int TOP_SORT_ORDER = 30000;
|
||
|
||
// 保存的SDF字体资源
|
||
private static TMP_FontAsset savedFontAsset = null;
|
||
|
||
// 模块实例
|
||
private ParametersModule parametersModule;
|
||
private CustomButtonsModule customButtonsModule;
|
||
private CustomCheckBoxesModule customCheckBoxesModule;
|
||
private CustomValuesModule customValuesModule;
|
||
private SettingsModule settingsModule;
|
||
private ConsoleModule consoleModule;
|
||
private List<IDebugModule> allModules = new List<IDebugModule>();
|
||
|
||
// 暂时隐藏状态标志
|
||
private bool isHiding = false;
|
||
|
||
// 四指点击检测
|
||
private bool wasFourFingerTouch = false;
|
||
private const float fourFingerTapTime = 0.3f; // 四指同时按下的时间阈值
|
||
private float fourFingerTouchStartTime = 0f;
|
||
|
||
// 键盘测试模式(用于测试,可以用F键代替四指点击)
|
||
private static bool useKeyboardTestMode = false; // 默认开启键盘测试模式 f
|
||
|
||
private readonly List<GameObject> inputDialogGeneratedObjects = new List<GameObject>();
|
||
#endregion
|
||
|
||
#region 单例
|
||
public static UniversalDebugTool Instance
|
||
{
|
||
get
|
||
{
|
||
if (instance == null)
|
||
{
|
||
instance = FindObjectOfType<UniversalDebugTool>();
|
||
}
|
||
return instance;
|
||
}
|
||
}
|
||
|
||
public static bool InstanceExists => instance != null;
|
||
#endregion
|
||
|
||
#region Unity生命周期
|
||
private void Awake()
|
||
{
|
||
// 如果未初始化,直接销毁GameObject,不执行任何操作
|
||
if (!isInitialized)
|
||
{
|
||
Destroy(gameObject);
|
||
return;
|
||
}
|
||
|
||
if (instance == null)
|
||
{
|
||
instance = this;
|
||
DontDestroyOnLoad(gameObject);
|
||
}
|
||
else if (instance != this)
|
||
{
|
||
Destroy(gameObject);
|
||
return;
|
||
}
|
||
|
||
// 设置Canvas为最上层
|
||
SetCanvasToTop();
|
||
|
||
// 隐藏所有UI
|
||
HideAllUI();
|
||
|
||
// 注意:如果isInitialized为true,不在Awake中调用InitializeDebugTool
|
||
// 因为此时反射设置的字段可能还没生效,延迟到Start中调用
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
// 如果未初始化,不执行任何操作
|
||
if (!isInitialized)
|
||
return;
|
||
|
||
// 只有在已初始化但还没调用过InitializeDebugTool时才执行
|
||
// 这是通过Init()创建的实例,需要在Start中完成初始化
|
||
// 此时反射设置的字段已经生效
|
||
if (allModules.Count == 0)
|
||
{
|
||
InitializeDebugTool();
|
||
InitializeAllModules();
|
||
ShowMainWindow();
|
||
}
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
// 只有初始化后才执行任何逻辑
|
||
if (!isInitialized)
|
||
return;
|
||
|
||
// 键盘测试模式:按F键触发
|
||
if (useKeyboardTestMode && Input.GetKeyDown(KeyCode.F))
|
||
{
|
||
OnFourFingerTap();
|
||
}
|
||
|
||
// 四指点击检测已禁用,如需使用请在主程序中通过公共API控制显示/隐藏
|
||
// if (!useKeyboardTestMode)
|
||
// {
|
||
// DetectFourFingerTap();
|
||
// }
|
||
|
||
// 仅在控制台页实际显示时更新控制台UI
|
||
if (consoleModule != null && currentPageKey == "控制台" && mainWindow != null && mainWindow.gameObject.activeInHierarchy)
|
||
{
|
||
consoleModule.Update();
|
||
}
|
||
|
||
// 参数页 Profiler 实时刷新
|
||
if (parametersModule != null && currentPageKey == "参数" && mainWindow != null && mainWindow.gameObject.activeInHierarchy)
|
||
{
|
||
parametersModule.Update();
|
||
}
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
// 如果未初始化,不执行任何操作
|
||
if (!isInitialized)
|
||
return;
|
||
|
||
if (instance == this)
|
||
{
|
||
instance = null;
|
||
|
||
// 销毁控制台模块
|
||
if (consoleModule != null)
|
||
{
|
||
consoleModule.Shutdown();
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 初始化
|
||
/// <summary>
|
||
/// 初始化调试工具(自动创建UI)
|
||
/// </summary>
|
||
public static void Init()
|
||
{
|
||
if (isInitialized)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 已经初始化过了");
|
||
return;
|
||
}
|
||
|
||
isInitialized = true;
|
||
Debug.Log("[MeowmentDebugTool] 开始初始化调试工具...");
|
||
|
||
// 如果场景中没有实例,运行时自动创建UI
|
||
if (!InstanceExists)
|
||
{
|
||
Debug.Log("[MeowmentDebugTool] 运行时自动创建UI...");
|
||
instance = RuntimeUIGenerator.CreateDebugToolUI();
|
||
}
|
||
|
||
if (InstanceExists)
|
||
{
|
||
Instance.InitializeDebugTool();
|
||
Instance.InitializeAllModules();
|
||
Instance.ShowMainWindow();
|
||
|
||
Debug.Log("[MeowmentDebugTool] 初始化完成!");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置所有TextMeshProUGUI的SDF字体
|
||
/// </summary>
|
||
/// <param name="fontAsset">TMP字体资源</param>
|
||
public static void SetSDFFont(TMP_FontAsset fontAsset)
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法设置字体");
|
||
return;
|
||
}
|
||
|
||
if (fontAsset == null)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 字体资源为空");
|
||
return;
|
||
}
|
||
|
||
int count = 0;
|
||
|
||
// 1. 获取调试工具Canvas下的所有TextMeshProUGUI组件(包括隐藏的)
|
||
TextMeshProUGUI[] allTexts = Instance.GetComponentsInChildren<TextMeshProUGUI>(true);
|
||
foreach (var text in allTexts)
|
||
{
|
||
text.font = fontAsset;
|
||
count++;
|
||
}
|
||
|
||
// 2. 查找预制件模板(_TempPrefabHolder)下的TextMeshProUGUI
|
||
GameObject tempPrefabHolder = GameObject.Find("_TempPrefabHolder");
|
||
if (tempPrefabHolder != null)
|
||
{
|
||
TextMeshProUGUI[] prefabTexts = tempPrefabHolder.GetComponentsInChildren<TextMeshProUGUI>(true);
|
||
foreach (var text in prefabTexts)
|
||
{
|
||
text.font = fontAsset;
|
||
count++;
|
||
}
|
||
Debug.Log($"[MeowmentDebugTool] 已设置预制件模板中的 {prefabTexts.Length} 个文本组件");
|
||
}
|
||
|
||
Debug.Log($"[MeowmentDebugTool] 共将 {count} 个文本组件的字体设置为: {fontAsset.name}");
|
||
|
||
// 3. 保存字体资源,供后续创建的按钮使用
|
||
savedFontAsset = fontAsset;
|
||
Debug.Log("[MeowmentDebugTool] 字体资源已保存,后续创建的按钮将自动应用此字体");
|
||
|
||
// 4. 更新按钮模块字体,不再整页重建
|
||
if (Instance.customButtonsModule != null)
|
||
{
|
||
Debug.Log("[MeowmentDebugTool] 应用自定义按钮字体...");
|
||
Instance.customButtonsModule.SetSDFFont(fontAsset);
|
||
}
|
||
|
||
// 4.5. 更新自定义复选框字体,不再整页重建
|
||
if (Instance.customCheckBoxesModule != null)
|
||
{
|
||
Debug.Log("[MeowmentDebugTool] 应用自定义复选框字体...");
|
||
Instance.customCheckBoxesModule.SetSDFFont(fontAsset);
|
||
}
|
||
|
||
// 4.6. 更新数值模块字体,不再整页重建
|
||
if (Instance.customValuesModule != null)
|
||
{
|
||
Debug.Log("[MeowmentDebugTool] 应用数值模块字体...");
|
||
Instance.customValuesModule.SetSDFFont(fontAsset);
|
||
}
|
||
|
||
// 5. 为控制台模块应用字体
|
||
if (Instance.consoleModule != null)
|
||
{
|
||
Instance.consoleModule.SetSDFFont(fontAsset);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置Canvas为最上层
|
||
/// </summary>
|
||
private void SetCanvasToTop()
|
||
{
|
||
if (canvas != null)
|
||
{
|
||
canvas.sortingOrder = TOP_SORT_ORDER;
|
||
canvas.overrideSorting = true;
|
||
Debug.Log($"[MeowmentDebugTool] Canvas层级设置为: {TOP_SORT_ORDER}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 隐藏所有UI
|
||
/// </summary>
|
||
private void HideAllUI()
|
||
{
|
||
if (mainWindow != null)
|
||
mainWindow.gameObject.SetActive(false);
|
||
if (floatingButton != null)
|
||
floatingButton.SetActive(false);
|
||
}
|
||
|
||
private void InitializeDebugTool()
|
||
{
|
||
Debug.Log("[MeowmentDebugTool] 初始化UniversalDebugTool...");
|
||
|
||
// 参数页按需初始化,避免影响启动速度
|
||
parametersModule = null;
|
||
|
||
if (parametersPage != null)
|
||
{
|
||
RegisterPage("参数", parametersPage);
|
||
}
|
||
|
||
customButtonsModule = new CustomButtonsModule(
|
||
customButtonsPage, buttonContainer, buttonPrefab, buttonsScrollRect, CloseDebugWindow);
|
||
|
||
customCheckBoxesModule = new CustomCheckBoxesModule(
|
||
customCheckBoxesPage, checkBoxContainer, checkBoxPrefab, checkBoxesScrollRect);
|
||
|
||
customValuesModule = new CustomValuesModule(
|
||
customValuesPage, valueContainer, valuePrefab, valuesScrollRect, CloseDebugWindow);
|
||
|
||
settingsModule = new SettingsModule(
|
||
settingsPage, widthInputField, heightInputField,
|
||
applyResolutionButton, resetResolutionButton, currentResolutionText,
|
||
infoBufferInputField, warningBufferInputField,
|
||
errorBufferInputField, fatalBufferInputField,
|
||
applyBufferButton, resetBufferButton,
|
||
mainWindow);
|
||
|
||
consoleModule = new ConsoleModule(
|
||
consolePage, consoleLogScrollRect, consoleLogContent,
|
||
consoleDetailScrollRect, consoleDetailText,
|
||
consoleClearButton, consoleLockScrollToggle,
|
||
consoleInfoFilterToggle, consoleWarningFilterToggle,
|
||
consoleErrorFilterToggle, consoleFatalFilterToggle,
|
||
consoleTextFilterInputField, consoleLogItemPrefab);
|
||
|
||
// 添加会显示在顶部标签栏的模块(顺序:控制台、按钮、开关、数值、设置)
|
||
allModules.Add(consoleModule);
|
||
allModules.Add(customButtonsModule);
|
||
allModules.Add(customCheckBoxesModule);
|
||
allModules.Add(customValuesModule);
|
||
allModules.Add(settingsModule);
|
||
|
||
// 注册所有页面
|
||
foreach (var module in allModules)
|
||
{
|
||
RegisterPage(module.GetModuleName(), module.GetPage());
|
||
}
|
||
|
||
Debug.Log($"[MeowmentDebugTool] 已注册{pages.Count}个页面");
|
||
|
||
// 创建标签按钮
|
||
CreateTabButtons();
|
||
|
||
// 设置按钮事件
|
||
if (closeButton != null)
|
||
closeButton.onClick.AddListener(CloseDebugWindow);
|
||
|
||
// 注意:浮窗点击事件现在由DraggableFloatingButton的OnPointerClick处理
|
||
|
||
// 默认页仅记录,不在初始化阶段真正构建页面内容
|
||
if (pages.Count > 0)
|
||
{
|
||
currentPageKey = pages.ContainsKey("按钮") ? "按钮" : pages.Keys.First();
|
||
}
|
||
|
||
Debug.Log("[OK] UniversalDebugTool初始化完成!");
|
||
Debug.Log("[MeowmentDebugTool] 提示: 点击窗口顶部的标签切换页面");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化所有模块
|
||
/// </summary>
|
||
private void InitializeAllModules()
|
||
{
|
||
Debug.Log("🔧 初始化所有模块...");
|
||
|
||
foreach (var module in allModules)
|
||
{
|
||
module.Initialize();
|
||
}
|
||
|
||
// 设置ConsoleModule引用到SettingsModule
|
||
if (settingsModule != null && consoleModule != null)
|
||
{
|
||
settingsModule.SetConsoleModule(consoleModule);
|
||
}
|
||
|
||
Debug.Log("[OK] 所有模块初始化完成!");
|
||
}
|
||
|
||
private void RegisterPage(string pageName, GameObject pageObject)
|
||
{
|
||
if (pageObject != null && !pages.ContainsKey(pageName))
|
||
{
|
||
pages[pageName] = pageObject;
|
||
pageObject.SetActive(false);
|
||
}
|
||
}
|
||
|
||
private void CreateTabButtons()
|
||
{
|
||
if (tabButtonPrefab == null || tabButtonContainer == null)
|
||
{
|
||
string errorDetails = $"tabButtonPrefab={(tabButtonPrefab == null ? "null" : "已设置")}, " +
|
||
$"tabButtonContainer={(tabButtonContainer == null ? "null" : "已设置")}";
|
||
Debug.LogWarning($"⚠ [MeowmentDebugTool] 标签按钮预制件或容器未完全初始化:{errorDetails}");
|
||
Debug.LogWarning("[!] 这可能是Unity初始化顺序问题,如果标签正常显示则可以忽略此警告");
|
||
return;
|
||
}
|
||
|
||
Debug.Log($"[MeowmentDebugTool] 开始创建{pages.Count}个标签按钮...");
|
||
|
||
foreach (var page in pages)
|
||
{
|
||
GameObject buttonObj = Instantiate(tabButtonPrefab, tabButtonContainer);
|
||
buttonObj.name = $"Tab_{page.Key}";
|
||
|
||
Button button = buttonObj.GetComponent<Button>();
|
||
TMP_Text buttonText = buttonObj.GetComponentInChildren<TMP_Text>();
|
||
|
||
if (buttonText != null)
|
||
{
|
||
buttonText.text = page.Key;
|
||
Debug.Log($"[OK] 创建标签: [{page.Key}]");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"⚠️ 标签按钮 [{page.Key}] 缺少文本组件");
|
||
}
|
||
|
||
string pageName = page.Key; // 闭包捕获
|
||
button.onClick.AddListener(() => ShowPage(pageName));
|
||
|
||
tabButtons[page.Key] = button;
|
||
}
|
||
|
||
Debug.Log("[OK] 标签按钮创建完成!");
|
||
}
|
||
#endregion
|
||
|
||
#region 页面管理
|
||
/// <summary>
|
||
/// 显示指定的页面
|
||
/// </summary>
|
||
public void ShowPage(string pageName)
|
||
{
|
||
if (!isInitialized)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法显示页面");
|
||
return;
|
||
}
|
||
|
||
if (!pages.ContainsKey(pageName))
|
||
{
|
||
Debug.LogWarning($"页面 '{pageName}' 不存在!");
|
||
return;
|
||
}
|
||
|
||
// 隐藏所有页面
|
||
foreach (var page in pages.Values)
|
||
{
|
||
page.SetActive(false);
|
||
}
|
||
|
||
// 显示目标页面
|
||
pages[pageName].SetActive(true);
|
||
currentPageKey = pageName;
|
||
|
||
if (pageName == "参数")
|
||
{
|
||
EnsureParametersModule()?.RefreshAllInfo();
|
||
}
|
||
else if (pageName == "按钮" && customButtonsModule != null)
|
||
{
|
||
customButtonsModule.EnsurePageViewInitialized();
|
||
}
|
||
else if (pageName == "开关" && customCheckBoxesModule != null)
|
||
{
|
||
customCheckBoxesModule.EnsurePageViewInitialized();
|
||
}
|
||
else if (pageName == "数值" && customValuesModule != null)
|
||
{
|
||
customValuesModule.EnsurePageViewInitialized();
|
||
}
|
||
else if (pageName == "控制台" && consoleModule != null)
|
||
{
|
||
consoleModule.EnsurePageViewInitialized();
|
||
}
|
||
|
||
// 更新标签按钮状态
|
||
UpdateTabButtonStates(pageName);
|
||
}
|
||
|
||
private void UpdateTabButtonStates(string activePageName)
|
||
{
|
||
foreach (var kvp in tabButtons)
|
||
{
|
||
Image buttonImage = kvp.Value.GetComponent<Image>();
|
||
if (buttonImage != null)
|
||
{
|
||
buttonImage.color = kvp.Key == activePageName ? activeTabColor : inactiveTabColor;
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 参数查看功能(通过ParametersModule)
|
||
private ParametersModule EnsureParametersModule()
|
||
{
|
||
if (parametersModule != null)
|
||
{
|
||
return parametersModule;
|
||
}
|
||
|
||
if (parametersPage == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
parametersModule = new ParametersModule(parametersPage);
|
||
parametersModule.Initialize();
|
||
return parametersModule;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 复制所有参数信息到剪贴板
|
||
/// </summary>
|
||
public void CopyDeviceInfoToClipboard()
|
||
{
|
||
if (!isInitialized)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化");
|
||
return;
|
||
}
|
||
|
||
EnsureParametersModule()?.CopyAllInfoToClipboard();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 复制所有参数信息到剪贴板
|
||
/// </summary>
|
||
public void CopySystemInfoToClipboard()
|
||
{
|
||
if (!isInitialized)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化");
|
||
return;
|
||
}
|
||
|
||
EnsureParametersModule()?.CopyAllInfoToClipboard();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新所有信息
|
||
/// </summary>
|
||
public void RefreshAllInfo()
|
||
{
|
||
if (!isInitialized)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化");
|
||
return;
|
||
}
|
||
|
||
EnsureParametersModule()?.RefreshAllInfo();
|
||
}
|
||
#endregion
|
||
|
||
#region 自定义按钮功能(通过CustomButtonsModule)
|
||
/// <summary>
|
||
/// 设置自定义按钮回调
|
||
/// </summary>
|
||
public static void SetCustomButtonCallback(Action<Button, TMP_Text> callback)
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法设置按钮回调");
|
||
return;
|
||
}
|
||
|
||
if (Instance.customButtonsModule != null)
|
||
{
|
||
Instance.customButtonsModule.SetCustomButtonCallback(callback);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 运行时动态注册一个按钮
|
||
/// </summary>
|
||
public static bool RegisterRuntimeButton(string buttonId, Action callback, string tabName = "默认",
|
||
string groupName = "默认", string displayName = "", Color? buttonColor = null, Color? tabColor = null,
|
||
bool reloadImmediately = true)
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法注册运行时按钮");
|
||
return false;
|
||
}
|
||
|
||
if (Instance.customButtonsModule == null)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 按钮模块未初始化");
|
||
return false;
|
||
}
|
||
|
||
return Instance.customButtonsModule.RegisterRuntimeButton(buttonId, callback, tabName, groupName, displayName, buttonColor, tabColor, reloadImmediately);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 运行时动态注册一个带输入面板的按钮
|
||
/// </summary>
|
||
public static bool RegisterRuntimeInputButton(RuntimeDebugInputButtonDefinition definition, bool reloadImmediately = true)
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法注册运行时输入按钮");
|
||
return false;
|
||
}
|
||
|
||
if (Instance.customButtonsModule == null)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 按钮模块未初始化");
|
||
return false;
|
||
}
|
||
|
||
return Instance.customButtonsModule.RegisterRuntimeInputButton(definition, reloadImmediately);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始批量更新运行时按钮,期间的注册/移除/清空不会立即重建页面。
|
||
/// </summary>
|
||
public static void BeginRuntimeButtonBatchUpdate()
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法开始批量更新运行时按钮");
|
||
return;
|
||
}
|
||
|
||
Instance.customButtonsModule?.BeginRuntimeButtonBatchUpdate();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 结束批量更新运行时按钮,并在需要时统一刷新一次。
|
||
/// </summary>
|
||
public static void EndRuntimeButtonBatchUpdate(bool reloadIfDirty = true)
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法结束批量更新运行时按钮");
|
||
return;
|
||
}
|
||
|
||
Instance.customButtonsModule?.EndRuntimeButtonBatchUpdate(reloadIfDirty);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动刷新运行时动态按钮显示。
|
||
/// </summary>
|
||
public static void ReloadRuntimeButtons()
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法刷新运行时按钮");
|
||
return;
|
||
}
|
||
|
||
Instance.customButtonsModule?.ReloadCustomButtons();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除一个运行时动态按钮
|
||
/// </summary>
|
||
public static bool UnregisterRuntimeButton(string buttonId, bool reloadImmediately = true)
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法移除运行时按钮");
|
||
return false;
|
||
}
|
||
|
||
if (Instance.customButtonsModule == null)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 按钮模块未初始化");
|
||
return false;
|
||
}
|
||
|
||
return Instance.customButtonsModule.UnregisterRuntimeButton(buttonId, reloadImmediately);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空所有运行时动态按钮
|
||
/// </summary>
|
||
public static void ClearRuntimeButtons(bool reloadImmediately = true)
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法清空运行时按钮");
|
||
return;
|
||
}
|
||
|
||
Instance.customButtonsModule?.ClearRuntimeButtons(reloadImmediately);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重新加载自定义按钮
|
||
/// </summary>
|
||
public void ReloadCustomButtons()
|
||
{
|
||
if (!isInitialized)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化");
|
||
return;
|
||
}
|
||
|
||
customButtonsModule?.ReloadCustomButtons();
|
||
}
|
||
#endregion
|
||
|
||
#region 自定义数值功能(通过CustomValuesModule)
|
||
/// <summary>
|
||
/// 更新指定方法的数值范围
|
||
/// </summary>
|
||
/// <param name="methodName">方法名称(完整路径如"ClassName.MethodName"或简单的"MethodName")</param>
|
||
/// <param name="minValue">新的最小值</param>
|
||
/// <param name="maxValue">新的最大值</param>
|
||
/// <returns>是否成功更新</returns>
|
||
public static bool UpdateDebugValueRange(string methodName, int minValue, int maxValue)
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法更新数值范围");
|
||
return false;
|
||
}
|
||
|
||
if (Instance.customValuesModule == null)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 数值模块未初始化");
|
||
return false;
|
||
}
|
||
|
||
return Instance.customValuesModule.UpdateValueRange(methodName, minValue, maxValue);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新指定方法的当前值
|
||
/// </summary>
|
||
/// <param name="methodName">方法名称(完整路径如"ClassName.MethodName"或简单的"MethodName")</param>
|
||
/// <param name="value">新的值</param>
|
||
/// <returns>是否成功更新</returns>
|
||
public static bool UpdateDebugValue(string methodName, int value)
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法更新数值");
|
||
return false;
|
||
}
|
||
|
||
if (Instance.customValuesModule == null)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 数值模块未初始化");
|
||
return false;
|
||
}
|
||
|
||
return Instance.customValuesModule.UpdateValue(methodName, value);
|
||
}
|
||
#endregion
|
||
|
||
|
||
|
||
|
||
|
||
#region 输入对话框
|
||
private class InputDialogValueResult
|
||
{
|
||
public bool Success;
|
||
public object Value;
|
||
public string Error;
|
||
}
|
||
|
||
private class InputDialogDynamicOption
|
||
{
|
||
public string Label;
|
||
public object Value;
|
||
}
|
||
|
||
internal static void ShowMethodInputDialog(MethodInfo method, DebugInputButtonAttribute inputButtonAttribute, Action onCompleted = null)
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法显示输入对话框");
|
||
return;
|
||
}
|
||
|
||
Instance.ShowMethodInputDialogInternal(method, inputButtonAttribute, onCompleted);
|
||
}
|
||
|
||
internal static void ShowRuntimeInputDialog(RuntimeDebugInputButtonDefinition definition, Action onCompleted = null)
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法显示运行时输入对话框");
|
||
return;
|
||
}
|
||
|
||
Instance.ShowRuntimeInputDialogInternal(definition, onCompleted);
|
||
}
|
||
|
||
public static void CloseInputDialog()
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Instance.CloseInputDialogInternal();
|
||
}
|
||
|
||
private void ShowMethodInputDialogInternal(MethodInfo method, DebugInputButtonAttribute inputButtonAttribute, Action onCompleted)
|
||
{
|
||
if (method == null)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 输入型按钮的方法为空");
|
||
return;
|
||
}
|
||
|
||
if (inputDialog == null || inputDialogTitle == null || inputDialogFieldContainer == null || inputDialogConfirmBtn == null || inputDialogCancelBtn == null)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 输入对话框组件未正确初始化");
|
||
return;
|
||
}
|
||
|
||
ClearInputDialogFields();
|
||
|
||
string buttonName = !string.IsNullOrEmpty(inputButtonAttribute?.DisplayName) ? inputButtonAttribute.DisplayName : method.Name;
|
||
string dialogTitle = !string.IsNullOrEmpty(inputButtonAttribute?.DialogTitle) ? inputButtonAttribute.DialogTitle : buttonName;
|
||
|
||
inputDialogTitle.text = dialogTitle;
|
||
if (inputDialogDescription != null)
|
||
{
|
||
inputDialogDescription.text = $"请填写 {buttonName} 所需的参数";
|
||
}
|
||
|
||
List<Func<InputDialogValueResult>> readers = new List<Func<InputDialogValueResult>>();
|
||
ParameterInfo[] parameters = method.GetParameters();
|
||
|
||
if (parameters.Length == 0)
|
||
{
|
||
readers.Add(() => new InputDialogValueResult { Success = true, Value = null });
|
||
}
|
||
else
|
||
{
|
||
foreach (ParameterInfo parameter in parameters)
|
||
{
|
||
Func<InputDialogValueResult> reader = CreateInputDialogField(parameter);
|
||
if (reader == null)
|
||
{
|
||
Debug.LogWarning($"[MeowmentDebugTool] 暂不支持参数类型: {parameter.ParameterType.Name}");
|
||
return;
|
||
}
|
||
|
||
readers.Add(reader);
|
||
}
|
||
}
|
||
|
||
inputDialogConfirmBtn.onClick.RemoveAllListeners();
|
||
inputDialogConfirmBtn.onClick.AddListener(() =>
|
||
{
|
||
try
|
||
{
|
||
object[] values = parameters.Length == 0 ? null : new object[parameters.Length];
|
||
|
||
for (int i = 0; i < readers.Count; i++)
|
||
{
|
||
InputDialogValueResult result = readers[i].Invoke();
|
||
if (!result.Success)
|
||
{
|
||
Debug.LogWarning($"[MeowmentDebugTool] 参数输入无效: {result.Error}");
|
||
return;
|
||
}
|
||
|
||
if (parameters.Length > 0)
|
||
{
|
||
values[i] = result.Value;
|
||
}
|
||
}
|
||
|
||
method.Invoke(null, values);
|
||
Debug.Log($"[MeowmentDebugTool] 已执行输入型按钮方法: {method.Name}");
|
||
|
||
CloseInputDialogInternal();
|
||
onCompleted?.Invoke();
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"[MeowmentDebugTool] 执行输入型按钮 {method.Name} 失败: {e.Message}");
|
||
}
|
||
});
|
||
|
||
inputDialogCancelBtn.onClick.RemoveAllListeners();
|
||
inputDialogCancelBtn.onClick.AddListener(CloseInputDialogInternal);
|
||
|
||
inputDialog.SetActive(true);
|
||
}
|
||
|
||
private void ShowRuntimeInputDialogInternal(RuntimeDebugInputButtonDefinition definition, Action onCompleted)
|
||
{
|
||
if (definition == null)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 运行时输入按钮定义为空");
|
||
return;
|
||
}
|
||
|
||
if (definition.Callback == null)
|
||
{
|
||
Debug.LogWarning($"[MeowmentDebugTool] 运行时输入按钮 {definition.Id} 缺少回调");
|
||
return;
|
||
}
|
||
|
||
if (inputDialog == null || inputDialogTitle == null || inputDialogFieldContainer == null || inputDialogConfirmBtn == null || inputDialogCancelBtn == null)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 输入对话框组件未正确初始化");
|
||
return;
|
||
}
|
||
|
||
ClearInputDialogFields();
|
||
|
||
string buttonName = !string.IsNullOrEmpty(definition.DisplayName) ? definition.DisplayName : definition.Id;
|
||
string dialogTitle = !string.IsNullOrEmpty(definition.DialogTitle) ? definition.DialogTitle : buttonName;
|
||
|
||
inputDialogTitle.text = dialogTitle;
|
||
if (inputDialogDescription != null)
|
||
{
|
||
inputDialogDescription.text = $"请填写 {buttonName} 所需的参数";
|
||
}
|
||
|
||
List<RuntimeDebugInputParameterDefinition> parameters = definition.Parameters ?? new List<RuntimeDebugInputParameterDefinition>();
|
||
List<Func<InputDialogValueResult>> readers = new List<Func<InputDialogValueResult>>();
|
||
|
||
foreach (RuntimeDebugInputParameterDefinition parameter in parameters)
|
||
{
|
||
Func<InputDialogValueResult> reader = CreateRuntimeInputDialogField(parameter);
|
||
if (reader == null)
|
||
{
|
||
Debug.LogWarning($"[MeowmentDebugTool] 暂不支持运行时输入参数类型: {parameter?.ParameterType?.Name}");
|
||
return;
|
||
}
|
||
|
||
readers.Add(reader);
|
||
}
|
||
|
||
inputDialogConfirmBtn.onClick.RemoveAllListeners();
|
||
inputDialogConfirmBtn.onClick.AddListener(() =>
|
||
{
|
||
try
|
||
{
|
||
Dictionary<string, object> values = new Dictionary<string, object>();
|
||
for (int i = 0; i < readers.Count; i++)
|
||
{
|
||
InputDialogValueResult result = readers[i].Invoke();
|
||
if (!result.Success)
|
||
{
|
||
Debug.LogWarning($"[MeowmentDebugTool] 参数输入无效: {result.Error}");
|
||
return;
|
||
}
|
||
|
||
RuntimeDebugInputParameterDefinition parameter = parameters[i];
|
||
string key = string.IsNullOrEmpty(parameter.Key) ? $"param{i}" : parameter.Key;
|
||
values[key] = result.Value;
|
||
}
|
||
|
||
definition.Callback.Invoke(new RuntimeDebugInputResult(values));
|
||
Debug.Log($"[MeowmentDebugTool] 已执行运行时输入按钮: {definition.Id}");
|
||
|
||
CloseInputDialogInternal();
|
||
onCompleted?.Invoke();
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"[MeowmentDebugTool] 执行运行时输入按钮 {definition.Id} 失败: {e.Message}");
|
||
}
|
||
});
|
||
|
||
inputDialogCancelBtn.onClick.RemoveAllListeners();
|
||
inputDialogCancelBtn.onClick.AddListener(CloseInputDialogInternal);
|
||
|
||
inputDialog.SetActive(true);
|
||
}
|
||
|
||
private void CloseInputDialogInternal()
|
||
{
|
||
if (inputDialog == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
inputDialog.SetActive(false);
|
||
inputDialogConfirmBtn?.onClick.RemoveAllListeners();
|
||
inputDialogCancelBtn?.onClick.RemoveAllListeners();
|
||
ClearInputDialogFields();
|
||
}
|
||
|
||
private void ClearInputDialogFields()
|
||
{
|
||
foreach (GameObject fieldObject in inputDialogGeneratedObjects)
|
||
{
|
||
if (fieldObject != null)
|
||
{
|
||
Destroy(fieldObject);
|
||
}
|
||
}
|
||
|
||
inputDialogGeneratedObjects.Clear();
|
||
}
|
||
|
||
private Func<InputDialogValueResult> CreateInputDialogField(ParameterInfo parameter)
|
||
{
|
||
Type parameterType = parameter.ParameterType;
|
||
DebugInputParameterAttribute parameterAttribute = parameter.GetCustomAttribute<DebugInputParameterAttribute>();
|
||
string label = parameterAttribute != null && !string.IsNullOrEmpty(parameterAttribute.Label)
|
||
? parameterAttribute.Label
|
||
: parameter.Name;
|
||
|
||
object defaultValue = GetParameterDefaultValue(parameter, parameterAttribute, out bool hasDefaultValue);
|
||
bool isRequired = parameterAttribute != null && parameterAttribute.Required;
|
||
|
||
GameObject fieldRoot = CreateInputFieldRoot(label, isRequired);
|
||
inputDialogGeneratedObjects.Add(fieldRoot);
|
||
|
||
if (parameterAttribute != null && !string.IsNullOrEmpty(parameterAttribute.OptionsProvider))
|
||
{
|
||
List<InputDialogDynamicOption> dynamicOptions = ResolveDynamicOptions(parameter, parameterAttribute);
|
||
if (dynamicOptions.Count == 0)
|
||
{
|
||
GameObject hint = CreateDialogHint(fieldRoot.transform, $"{label} 当前没有可选项");
|
||
inputDialogGeneratedObjects.Add(hint);
|
||
return () => CreateInputError(label, $"{label} 当前没有可选项");
|
||
}
|
||
|
||
return CreateDynamicOptionField(fieldRoot.transform, parameterType, label, parameterAttribute, dynamicOptions, defaultValue, hasDefaultValue);
|
||
}
|
||
|
||
if (parameterType == typeof(string))
|
||
{
|
||
TMP_InputField inputField = CreateDialogTextInput(fieldRoot.transform, parameterAttribute?.Placeholder ?? "请输入内容", defaultValue as string ?? string.Empty, TMP_InputField.ContentType.Standard);
|
||
return () =>
|
||
{
|
||
string text = inputField.text;
|
||
if (string.IsNullOrEmpty(text))
|
||
{
|
||
if (hasDefaultValue)
|
||
{
|
||
text = defaultValue as string ?? string.Empty;
|
||
}
|
||
else if (isRequired)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能为空");
|
||
}
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = text ?? string.Empty };
|
||
};
|
||
}
|
||
|
||
if (parameterType == typeof(int))
|
||
{
|
||
string initialText = hasDefaultValue ? Convert.ToInt32(defaultValue).ToString() : string.Empty;
|
||
TMP_InputField inputField = CreateDialogTextInput(fieldRoot.transform, parameterAttribute?.Placeholder ?? "请输入整数", initialText, TMP_InputField.ContentType.Standard);
|
||
return () => ParseNumericInput(parameter, label, inputField.text, parameterAttribute, hasDefaultValue ? Convert.ToInt32(defaultValue) : (int?)null);
|
||
}
|
||
|
||
if (parameterType == typeof(float))
|
||
{
|
||
string initialText = hasDefaultValue ? Convert.ToSingle(defaultValue).ToString(CultureInfo.InvariantCulture) : string.Empty;
|
||
TMP_InputField inputField = CreateDialogTextInput(fieldRoot.transform, parameterAttribute?.Placeholder ?? "请输入数字", initialText, TMP_InputField.ContentType.Standard);
|
||
return () => ParseFloatInput(parameter, label, inputField.text, parameterAttribute, hasDefaultValue ? Convert.ToSingle(defaultValue) : (float?)null);
|
||
}
|
||
|
||
if (parameterType == typeof(bool))
|
||
{
|
||
Toggle toggle = CreateDialogToggle(fieldRoot.transform, hasDefaultValue && Convert.ToBoolean(defaultValue));
|
||
return () => new InputDialogValueResult { Success = true, Value = toggle.isOn };
|
||
}
|
||
|
||
if (parameterType.IsEnum)
|
||
{
|
||
if (Attribute.IsDefined(parameterType, typeof(FlagsAttribute)))
|
||
{
|
||
return CreateFlagsEnumField(fieldRoot.transform, parameterType, label, parameterAttribute, defaultValue, hasDefaultValue);
|
||
}
|
||
|
||
return CreateEnumField(fieldRoot.transform, parameterType, label, parameterAttribute, defaultValue, hasDefaultValue);
|
||
}
|
||
|
||
GameObject unsupported = CreateDialogHint(fieldRoot.transform, $"暂不支持类型: {parameterType.Name}");
|
||
inputDialogGeneratedObjects.Add(unsupported);
|
||
return null;
|
||
}
|
||
|
||
private Func<InputDialogValueResult> CreateRuntimeInputDialogField(RuntimeDebugInputParameterDefinition parameterDefinition)
|
||
{
|
||
if (parameterDefinition == null || parameterDefinition.ParameterType == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
Type parameterType = parameterDefinition.ParameterType;
|
||
string label = string.IsNullOrEmpty(parameterDefinition.Label)
|
||
? (string.IsNullOrEmpty(parameterDefinition.Key) ? parameterType.Name : parameterDefinition.Key)
|
||
: parameterDefinition.Label;
|
||
|
||
object defaultValue = GetRuntimeParameterDefaultValue(parameterDefinition, out bool hasDefaultValue);
|
||
bool isRequired = parameterDefinition.Required;
|
||
|
||
GameObject fieldRoot = CreateInputFieldRoot(label, isRequired);
|
||
inputDialogGeneratedObjects.Add(fieldRoot);
|
||
|
||
if (parameterDefinition.OptionsProvider != null)
|
||
{
|
||
List<InputDialogDynamicOption> dynamicOptions = ResolveDynamicOptions(parameterDefinition);
|
||
if (dynamicOptions.Count == 0)
|
||
{
|
||
GameObject hint = CreateDialogHint(fieldRoot.transform, $"{label} 当前没有可选项");
|
||
inputDialogGeneratedObjects.Add(hint);
|
||
return () => CreateInputError(label, $"{label} 当前没有可选项");
|
||
}
|
||
|
||
return CreateDynamicOptionField(fieldRoot.transform, parameterType, label, parameterDefinition.Required, dynamicOptions, defaultValue, hasDefaultValue);
|
||
}
|
||
|
||
if (parameterType == typeof(string))
|
||
{
|
||
TMP_InputField inputField = CreateDialogTextInput(fieldRoot.transform, parameterDefinition.Placeholder ?? "请输入内容", defaultValue as string ?? string.Empty, TMP_InputField.ContentType.Standard);
|
||
return () =>
|
||
{
|
||
string text = inputField.text;
|
||
if (string.IsNullOrEmpty(text))
|
||
{
|
||
if (hasDefaultValue)
|
||
{
|
||
text = defaultValue as string ?? string.Empty;
|
||
}
|
||
else if (isRequired)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能为空");
|
||
}
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = text ?? string.Empty };
|
||
};
|
||
}
|
||
|
||
if (parameterType == typeof(int))
|
||
{
|
||
string initialText = hasDefaultValue ? Convert.ToInt32(defaultValue).ToString() : string.Empty;
|
||
TMP_InputField inputField = CreateDialogTextInput(fieldRoot.transform, parameterDefinition.Placeholder ?? "请输入整数", initialText, TMP_InputField.ContentType.Standard);
|
||
return () => ParseNumericInput(label, inputField.text, parameterDefinition, hasDefaultValue ? Convert.ToInt32(defaultValue) : (int?)null);
|
||
}
|
||
|
||
if (parameterType == typeof(float))
|
||
{
|
||
string initialText = hasDefaultValue ? Convert.ToSingle(defaultValue).ToString(CultureInfo.InvariantCulture) : string.Empty;
|
||
TMP_InputField inputField = CreateDialogTextInput(fieldRoot.transform, parameterDefinition.Placeholder ?? "请输入数字", initialText, TMP_InputField.ContentType.Standard);
|
||
return () => ParseFloatInput(label, inputField.text, parameterDefinition, hasDefaultValue ? Convert.ToSingle(defaultValue) : (float?)null);
|
||
}
|
||
|
||
if (parameterType == typeof(bool))
|
||
{
|
||
Toggle toggle = CreateDialogToggle(fieldRoot.transform, hasDefaultValue && Convert.ToBoolean(defaultValue));
|
||
return () => new InputDialogValueResult { Success = true, Value = toggle.isOn };
|
||
}
|
||
|
||
if (parameterType.IsEnum)
|
||
{
|
||
if (Attribute.IsDefined(parameterType, typeof(FlagsAttribute)))
|
||
{
|
||
return CreateFlagsEnumField(fieldRoot.transform, parameterType, label, parameterDefinition.Required, defaultValue, hasDefaultValue);
|
||
}
|
||
|
||
return CreateEnumField(fieldRoot.transform, parameterType, label, parameterDefinition.Required, defaultValue, hasDefaultValue);
|
||
}
|
||
|
||
GameObject unsupported = CreateDialogHint(fieldRoot.transform, $"暂不支持类型: {parameterType.Name}");
|
||
inputDialogGeneratedObjects.Add(unsupported);
|
||
return null;
|
||
}
|
||
|
||
private object GetParameterDefaultValue(ParameterInfo parameter, DebugInputParameterAttribute parameterAttribute, out bool hasDefaultValue)
|
||
{
|
||
hasDefaultValue = false;
|
||
|
||
if (parameterAttribute != null && !string.IsNullOrEmpty(parameterAttribute.DefaultValue))
|
||
{
|
||
if (TryConvertStringToParameterType(parameter.ParameterType, parameterAttribute.DefaultValue, out object attributeValue))
|
||
{
|
||
hasDefaultValue = true;
|
||
return attributeValue;
|
||
}
|
||
}
|
||
|
||
if (parameter.HasDefaultValue && parameter.DefaultValue != DBNull.Value)
|
||
{
|
||
hasDefaultValue = true;
|
||
return parameter.DefaultValue;
|
||
}
|
||
|
||
return parameter.ParameterType.IsValueType ? Activator.CreateInstance(parameter.ParameterType) : null;
|
||
}
|
||
|
||
private object GetRuntimeParameterDefaultValue(RuntimeDebugInputParameterDefinition parameterDefinition, out bool hasDefaultValue)
|
||
{
|
||
hasDefaultValue = false;
|
||
|
||
if (parameterDefinition != null && !string.IsNullOrEmpty(parameterDefinition.DefaultValue))
|
||
{
|
||
if (TryConvertStringToParameterType(parameterDefinition.ParameterType, parameterDefinition.DefaultValue, out object attributeValue))
|
||
{
|
||
hasDefaultValue = true;
|
||
return attributeValue;
|
||
}
|
||
}
|
||
|
||
return parameterDefinition != null && parameterDefinition.ParameterType != null && parameterDefinition.ParameterType.IsValueType
|
||
? Activator.CreateInstance(parameterDefinition.ParameterType)
|
||
: null;
|
||
}
|
||
|
||
private bool TryConvertStringToParameterType(Type targetType, string value, out object result)
|
||
{
|
||
result = null;
|
||
|
||
if (targetType == typeof(string))
|
||
{
|
||
result = value;
|
||
return true;
|
||
}
|
||
|
||
if (targetType == typeof(int))
|
||
{
|
||
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int intValue))
|
||
{
|
||
result = intValue;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
if (targetType == typeof(float))
|
||
{
|
||
if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float floatValue))
|
||
{
|
||
result = floatValue;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
if (targetType == typeof(bool))
|
||
{
|
||
if (bool.TryParse(value, out bool boolValue))
|
||
{
|
||
result = boolValue;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
if (targetType.IsEnum)
|
||
{
|
||
try
|
||
{
|
||
result = Enum.Parse(targetType, value, true);
|
||
return true;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private List<InputDialogDynamicOption> ResolveDynamicOptions(ParameterInfo parameter, DebugInputParameterAttribute parameterAttribute)
|
||
{
|
||
List<InputDialogDynamicOption> options = new List<InputDialogDynamicOption>();
|
||
if (parameterAttribute == null || string.IsNullOrEmpty(parameterAttribute.OptionsProvider))
|
||
{
|
||
return options;
|
||
}
|
||
|
||
Type declaringType = parameter.Member.DeclaringType;
|
||
if (declaringType == null)
|
||
{
|
||
return options;
|
||
}
|
||
|
||
MethodInfo providerMethod = declaringType.GetMethod(
|
||
parameterAttribute.OptionsProvider,
|
||
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
|
||
null,
|
||
Type.EmptyTypes,
|
||
null);
|
||
|
||
if (providerMethod == null)
|
||
{
|
||
Debug.LogWarning($"[MeowmentDebugTool] 未找到动态选项提供方法: {declaringType.Name}.{parameterAttribute.OptionsProvider}()");
|
||
return options;
|
||
}
|
||
|
||
if (!typeof(IEnumerable).IsAssignableFrom(providerMethod.ReturnType))
|
||
{
|
||
Debug.LogWarning($"[MeowmentDebugTool] 动态选项提供方法 {providerMethod.Name} 必须返回 IEnumerable");
|
||
return options;
|
||
}
|
||
|
||
try
|
||
{
|
||
IEnumerable result = providerMethod.Invoke(null, null) as IEnumerable;
|
||
if (result == null)
|
||
{
|
||
return options;
|
||
}
|
||
|
||
foreach (object item in result)
|
||
{
|
||
if (TryConvertDynamicOptionItem(parameter.ParameterType, item, out InputDialogDynamicOption option))
|
||
{
|
||
options.Add(option);
|
||
}
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"[MeowmentDebugTool] 获取动态选项失败: {e.Message}");
|
||
}
|
||
|
||
return options;
|
||
}
|
||
|
||
private List<InputDialogDynamicOption> ResolveDynamicOptions(RuntimeDebugInputParameterDefinition parameterDefinition)
|
||
{
|
||
List<InputDialogDynamicOption> options = new List<InputDialogDynamicOption>();
|
||
if (parameterDefinition == null || parameterDefinition.OptionsProvider == null || parameterDefinition.ParameterType == null)
|
||
{
|
||
return options;
|
||
}
|
||
|
||
try
|
||
{
|
||
IEnumerable result = parameterDefinition.OptionsProvider.Invoke();
|
||
if (result == null)
|
||
{
|
||
return options;
|
||
}
|
||
|
||
foreach (object item in result)
|
||
{
|
||
if (TryConvertDynamicOptionItem(parameterDefinition.ParameterType, item, out InputDialogDynamicOption option))
|
||
{
|
||
options.Add(option);
|
||
}
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"[MeowmentDebugTool] 获取运行时动态选项失败: {e.Message}");
|
||
}
|
||
|
||
return options;
|
||
}
|
||
|
||
private bool TryConvertDynamicOptionItem(Type parameterType, object item, out InputDialogDynamicOption option)
|
||
{
|
||
option = null;
|
||
if (item == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (item is DebugInputOption debugOption)
|
||
{
|
||
if (!TryConvertStringToParameterType(parameterType, debugOption.Value ?? string.Empty, out object convertedValue))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
option = new InputDialogDynamicOption
|
||
{
|
||
Label = string.IsNullOrEmpty(debugOption.Label) ? debugOption.Value : debugOption.Label,
|
||
Value = convertedValue
|
||
};
|
||
return true;
|
||
}
|
||
|
||
if (parameterType.IsInstanceOfType(item))
|
||
{
|
||
option = new InputDialogDynamicOption
|
||
{
|
||
Label = item.ToString(),
|
||
Value = item
|
||
};
|
||
return true;
|
||
}
|
||
|
||
if (TryConvertStringToParameterType(parameterType, item.ToString(), out object valueFromString))
|
||
{
|
||
option = new InputDialogDynamicOption
|
||
{
|
||
Label = item.ToString(),
|
||
Value = valueFromString
|
||
};
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private Func<InputDialogValueResult> CreateDynamicOptionField(Transform parent, Type parameterType, string label,
|
||
DebugInputParameterAttribute parameterAttribute, List<InputDialogDynamicOption> options, object defaultValue, bool hasDefaultValue)
|
||
{
|
||
GameObject optionContainer = new GameObject("DynamicOptions");
|
||
optionContainer.transform.SetParent(parent, false);
|
||
inputDialogGeneratedObjects.Add(optionContainer);
|
||
|
||
VerticalLayoutGroup layout = optionContainer.AddComponent<VerticalLayoutGroup>();
|
||
layout.spacing = 10;
|
||
layout.childControlWidth = true;
|
||
layout.childControlHeight = true;
|
||
layout.childForceExpandWidth = true;
|
||
layout.childForceExpandHeight = false;
|
||
|
||
List<Button> buttons = new List<Button>();
|
||
int selectedIndex = -1;
|
||
|
||
if (hasDefaultValue)
|
||
{
|
||
selectedIndex = options.FindIndex(option => AreInputOptionValuesEqual(option.Value, defaultValue, parameterType));
|
||
}
|
||
|
||
for (int i = 0; i < options.Count; i++)
|
||
{
|
||
InputDialogDynamicOption currentOption = options[i];
|
||
Button optionButton = CreateDialogOptionButton(optionContainer.transform, currentOption.Label);
|
||
int currentIndex = i;
|
||
optionButton.onClick.AddListener(() =>
|
||
{
|
||
selectedIndex = currentIndex;
|
||
UpdateEnumOptionButtonStyles(buttons, selectedIndex);
|
||
});
|
||
buttons.Add(optionButton);
|
||
}
|
||
|
||
UpdateEnumOptionButtonStyles(buttons, selectedIndex);
|
||
|
||
return () =>
|
||
{
|
||
if (selectedIndex < 0)
|
||
{
|
||
if (parameterAttribute != null && parameterAttribute.Required)
|
||
{
|
||
return CreateInputError(label, $"请为 {label} 选择一个值");
|
||
}
|
||
|
||
if (hasDefaultValue)
|
||
{
|
||
return new InputDialogValueResult { Success = true, Value = defaultValue };
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = options[0].Value };
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = options[selectedIndex].Value };
|
||
};
|
||
}
|
||
|
||
private Func<InputDialogValueResult> CreateDynamicOptionField(Transform parent, Type parameterType, string label,
|
||
bool required, List<InputDialogDynamicOption> options, object defaultValue, bool hasDefaultValue)
|
||
{
|
||
GameObject optionContainer = new GameObject("DynamicOptions");
|
||
optionContainer.transform.SetParent(parent, false);
|
||
inputDialogGeneratedObjects.Add(optionContainer);
|
||
|
||
VerticalLayoutGroup layout = optionContainer.AddComponent<VerticalLayoutGroup>();
|
||
layout.spacing = 10;
|
||
layout.childControlWidth = true;
|
||
layout.childControlHeight = true;
|
||
layout.childForceExpandWidth = true;
|
||
layout.childForceExpandHeight = false;
|
||
|
||
List<Button> buttons = new List<Button>();
|
||
int selectedIndex = -1;
|
||
|
||
if (hasDefaultValue)
|
||
{
|
||
selectedIndex = options.FindIndex(option => AreInputOptionValuesEqual(option.Value, defaultValue, parameterType));
|
||
}
|
||
|
||
for (int i = 0; i < options.Count; i++)
|
||
{
|
||
InputDialogDynamicOption currentOption = options[i];
|
||
Button optionButton = CreateDialogOptionButton(optionContainer.transform, currentOption.Label);
|
||
int currentIndex = i;
|
||
optionButton.onClick.AddListener(() =>
|
||
{
|
||
selectedIndex = currentIndex;
|
||
UpdateEnumOptionButtonStyles(buttons, selectedIndex);
|
||
});
|
||
buttons.Add(optionButton);
|
||
}
|
||
|
||
UpdateEnumOptionButtonStyles(buttons, selectedIndex);
|
||
|
||
return () =>
|
||
{
|
||
if (selectedIndex < 0)
|
||
{
|
||
if (required)
|
||
{
|
||
return CreateInputError(label, $"请为 {label} 选择一个值");
|
||
}
|
||
|
||
if (hasDefaultValue)
|
||
{
|
||
return new InputDialogValueResult { Success = true, Value = defaultValue };
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = options[0].Value };
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = options[selectedIndex].Value };
|
||
};
|
||
}
|
||
|
||
private bool AreInputOptionValuesEqual(object left, object right, Type parameterType)
|
||
{
|
||
if (left == null || right == null)
|
||
{
|
||
return left == right;
|
||
}
|
||
|
||
if (parameterType == typeof(float))
|
||
{
|
||
return Mathf.Approximately(Convert.ToSingle(left), Convert.ToSingle(right));
|
||
}
|
||
|
||
return left.Equals(right);
|
||
}
|
||
|
||
private Func<InputDialogValueResult> CreateEnumField(Transform parent, Type enumType, string label,
|
||
DebugInputParameterAttribute parameterAttribute, object defaultValue, bool hasDefaultValue)
|
||
{
|
||
GameObject optionContainer = new GameObject("Options");
|
||
optionContainer.transform.SetParent(parent, false);
|
||
inputDialogGeneratedObjects.Add(optionContainer);
|
||
|
||
VerticalLayoutGroup layout = optionContainer.AddComponent<VerticalLayoutGroup>();
|
||
layout.spacing = 10;
|
||
layout.childControlWidth = true;
|
||
layout.childControlHeight = true;
|
||
layout.childForceExpandWidth = true;
|
||
layout.childForceExpandHeight = false;
|
||
|
||
Array enumValues = Enum.GetValues(enumType);
|
||
List<Button> buttons = new List<Button>();
|
||
int selectedIndex = hasDefaultValue ? Array.IndexOf(enumValues, defaultValue) : -1;
|
||
|
||
for (int i = 0; i < enumValues.Length; i++)
|
||
{
|
||
object optionValue = enumValues.GetValue(i);
|
||
string optionName = optionValue.ToString();
|
||
Button optionButton = CreateDialogOptionButton(optionContainer.transform, optionName);
|
||
int currentIndex = i;
|
||
optionButton.onClick.AddListener(() =>
|
||
{
|
||
selectedIndex = currentIndex;
|
||
UpdateEnumOptionButtonStyles(buttons, selectedIndex);
|
||
});
|
||
buttons.Add(optionButton);
|
||
}
|
||
|
||
UpdateEnumOptionButtonStyles(buttons, selectedIndex);
|
||
|
||
return () =>
|
||
{
|
||
if (selectedIndex < 0)
|
||
{
|
||
if (parameterAttribute != null && parameterAttribute.Required)
|
||
{
|
||
return CreateInputError(label, $"请为 {label} 选择一个值");
|
||
}
|
||
|
||
return new InputDialogValueResult
|
||
{
|
||
Success = true,
|
||
Value = hasDefaultValue ? defaultValue : enumValues.GetValue(0)
|
||
};
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = enumValues.GetValue(selectedIndex) };
|
||
};
|
||
}
|
||
|
||
private Func<InputDialogValueResult> CreateEnumField(Transform parent, Type enumType, string label,
|
||
bool required, object defaultValue, bool hasDefaultValue)
|
||
{
|
||
GameObject optionContainer = new GameObject("Options");
|
||
optionContainer.transform.SetParent(parent, false);
|
||
inputDialogGeneratedObjects.Add(optionContainer);
|
||
|
||
VerticalLayoutGroup layout = optionContainer.AddComponent<VerticalLayoutGroup>();
|
||
layout.spacing = 10;
|
||
layout.childControlWidth = true;
|
||
layout.childControlHeight = true;
|
||
layout.childForceExpandWidth = true;
|
||
layout.childForceExpandHeight = false;
|
||
|
||
Array enumValues = Enum.GetValues(enumType);
|
||
List<Button> buttons = new List<Button>();
|
||
int selectedIndex = hasDefaultValue ? Array.IndexOf(enumValues, defaultValue) : -1;
|
||
|
||
for (int i = 0; i < enumValues.Length; i++)
|
||
{
|
||
object optionValue = enumValues.GetValue(i);
|
||
string optionName = optionValue.ToString();
|
||
Button optionButton = CreateDialogOptionButton(optionContainer.transform, optionName);
|
||
int currentIndex = i;
|
||
optionButton.onClick.AddListener(() =>
|
||
{
|
||
selectedIndex = currentIndex;
|
||
UpdateEnumOptionButtonStyles(buttons, selectedIndex);
|
||
});
|
||
buttons.Add(optionButton);
|
||
}
|
||
|
||
UpdateEnumOptionButtonStyles(buttons, selectedIndex);
|
||
|
||
return () =>
|
||
{
|
||
if (selectedIndex < 0)
|
||
{
|
||
if (required)
|
||
{
|
||
return CreateInputError(label, $"请为 {label} 选择一个值");
|
||
}
|
||
|
||
return new InputDialogValueResult
|
||
{
|
||
Success = true,
|
||
Value = hasDefaultValue ? defaultValue : enumValues.GetValue(0)
|
||
};
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = enumValues.GetValue(selectedIndex) };
|
||
};
|
||
}
|
||
|
||
private Func<InputDialogValueResult> CreateFlagsEnumField(Transform parent, Type enumType, string label,
|
||
DebugInputParameterAttribute parameterAttribute, object defaultValue, bool hasDefaultValue)
|
||
{
|
||
GameObject optionContainer = new GameObject("FlagsOptions");
|
||
optionContainer.transform.SetParent(parent, false);
|
||
inputDialogGeneratedObjects.Add(optionContainer);
|
||
|
||
VerticalLayoutGroup layout = optionContainer.AddComponent<VerticalLayoutGroup>();
|
||
layout.spacing = 10;
|
||
layout.childControlWidth = true;
|
||
layout.childControlHeight = true;
|
||
layout.childForceExpandWidth = true;
|
||
layout.childForceExpandHeight = false;
|
||
|
||
List<KeyValuePair<object, Toggle>> toggles = new List<KeyValuePair<object, Toggle>>();
|
||
Array enumValues = Enum.GetValues(enumType);
|
||
long defaultMask = hasDefaultValue ? Convert.ToInt64(defaultValue) : 0L;
|
||
|
||
foreach (object enumValue in enumValues)
|
||
{
|
||
long numericValue = Convert.ToInt64(enumValue);
|
||
if (numericValue == 0 && enumValues.Length > 1)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
Toggle toggle = CreateDialogToggle(optionContainer.transform, (defaultMask & numericValue) == numericValue && numericValue != 0L, enumValue.ToString());
|
||
toggles.Add(new KeyValuePair<object, Toggle>(enumValue, toggle));
|
||
}
|
||
|
||
return () =>
|
||
{
|
||
long resultMask = 0L;
|
||
foreach (var toggleInfo in toggles)
|
||
{
|
||
if (toggleInfo.Value.isOn)
|
||
{
|
||
resultMask |= Convert.ToInt64(toggleInfo.Key);
|
||
}
|
||
}
|
||
|
||
if (parameterAttribute != null && parameterAttribute.Required && resultMask == 0L)
|
||
{
|
||
return CreateInputError(label, $"请至少为 {label} 选择一项");
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = Enum.ToObject(enumType, resultMask) };
|
||
};
|
||
}
|
||
|
||
private Func<InputDialogValueResult> CreateFlagsEnumField(Transform parent, Type enumType, string label,
|
||
bool required, object defaultValue, bool hasDefaultValue)
|
||
{
|
||
GameObject optionContainer = new GameObject("FlagsOptions");
|
||
optionContainer.transform.SetParent(parent, false);
|
||
inputDialogGeneratedObjects.Add(optionContainer);
|
||
|
||
VerticalLayoutGroup layout = optionContainer.AddComponent<VerticalLayoutGroup>();
|
||
layout.spacing = 10;
|
||
layout.childControlWidth = true;
|
||
layout.childControlHeight = true;
|
||
layout.childForceExpandWidth = true;
|
||
layout.childForceExpandHeight = false;
|
||
|
||
List<KeyValuePair<object, Toggle>> toggles = new List<KeyValuePair<object, Toggle>>();
|
||
Array enumValues = Enum.GetValues(enumType);
|
||
long defaultMask = hasDefaultValue ? Convert.ToInt64(defaultValue) : 0L;
|
||
|
||
foreach (object enumValue in enumValues)
|
||
{
|
||
long numericValue = Convert.ToInt64(enumValue);
|
||
if (numericValue == 0 && enumValues.Length > 1)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
Toggle toggle = CreateDialogToggle(optionContainer.transform, (defaultMask & numericValue) == numericValue && numericValue != 0L, enumValue.ToString());
|
||
toggles.Add(new KeyValuePair<object, Toggle>(enumValue, toggle));
|
||
}
|
||
|
||
return () =>
|
||
{
|
||
long resultMask = 0L;
|
||
foreach (var toggleInfo in toggles)
|
||
{
|
||
if (toggleInfo.Value.isOn)
|
||
{
|
||
resultMask |= Convert.ToInt64(toggleInfo.Key);
|
||
}
|
||
}
|
||
|
||
if (required && resultMask == 0L)
|
||
{
|
||
return CreateInputError(label, $"请至少为 {label} 选择一项");
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = Enum.ToObject(enumType, resultMask) };
|
||
};
|
||
}
|
||
|
||
private InputDialogValueResult ParseNumericInput(ParameterInfo parameter, string label, string text,
|
||
DebugInputParameterAttribute attribute, int? defaultValue)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(text))
|
||
{
|
||
if (defaultValue.HasValue)
|
||
{
|
||
return new InputDialogValueResult { Success = true, Value = defaultValue.Value };
|
||
}
|
||
|
||
if (attribute != null && attribute.Required)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能为空");
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = 0 };
|
||
}
|
||
|
||
if (!int.TryParse(text, out int value))
|
||
{
|
||
return CreateInputError(label, $"{label} 必须是整数");
|
||
}
|
||
|
||
if (attribute != null)
|
||
{
|
||
if (!float.IsNaN(attribute.Min) && value < attribute.Min)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能小于 {attribute.Min}");
|
||
}
|
||
|
||
if (!float.IsNaN(attribute.Max) && value > attribute.Max)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能大于 {attribute.Max}");
|
||
}
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = value };
|
||
}
|
||
|
||
private InputDialogValueResult ParseNumericInput(string label, string text,
|
||
RuntimeDebugInputParameterDefinition definition, int? defaultValue)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(text))
|
||
{
|
||
if (defaultValue.HasValue)
|
||
{
|
||
return new InputDialogValueResult { Success = true, Value = defaultValue.Value };
|
||
}
|
||
|
||
if (definition != null && definition.Required)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能为空");
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = 0 };
|
||
}
|
||
|
||
if (!int.TryParse(text, out int value))
|
||
{
|
||
return CreateInputError(label, $"{label} 必须是整数");
|
||
}
|
||
|
||
if (definition != null)
|
||
{
|
||
if (!float.IsNaN(definition.Min) && value < definition.Min)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能小于 {definition.Min}");
|
||
}
|
||
|
||
if (!float.IsNaN(definition.Max) && value > definition.Max)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能大于 {definition.Max}");
|
||
}
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = value };
|
||
}
|
||
|
||
private InputDialogValueResult ParseFloatInput(ParameterInfo parameter, string label, string text,
|
||
DebugInputParameterAttribute attribute, float? defaultValue)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(text))
|
||
{
|
||
if (defaultValue.HasValue)
|
||
{
|
||
return new InputDialogValueResult { Success = true, Value = defaultValue.Value };
|
||
}
|
||
|
||
if (attribute != null && attribute.Required)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能为空");
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = 0f };
|
||
}
|
||
|
||
if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out float value) && !float.TryParse(text, out value))
|
||
{
|
||
return CreateInputError(label, $"{label} 必须是数字");
|
||
}
|
||
|
||
if (attribute != null)
|
||
{
|
||
if (!float.IsNaN(attribute.Min) && value < attribute.Min)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能小于 {attribute.Min}");
|
||
}
|
||
|
||
if (!float.IsNaN(attribute.Max) && value > attribute.Max)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能大于 {attribute.Max}");
|
||
}
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = value };
|
||
}
|
||
|
||
private InputDialogValueResult ParseFloatInput(string label, string text,
|
||
RuntimeDebugInputParameterDefinition definition, float? defaultValue)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(text))
|
||
{
|
||
if (defaultValue.HasValue)
|
||
{
|
||
return new InputDialogValueResult { Success = true, Value = defaultValue.Value };
|
||
}
|
||
|
||
if (definition != null && definition.Required)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能为空");
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = 0f };
|
||
}
|
||
|
||
if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out float value) && !float.TryParse(text, out value))
|
||
{
|
||
return CreateInputError(label, $"{label} 必须是数字");
|
||
}
|
||
|
||
if (definition != null)
|
||
{
|
||
if (!float.IsNaN(definition.Min) && value < definition.Min)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能小于 {definition.Min}");
|
||
}
|
||
|
||
if (!float.IsNaN(definition.Max) && value > definition.Max)
|
||
{
|
||
return CreateInputError(label, $"{label} 不能大于 {definition.Max}");
|
||
}
|
||
}
|
||
|
||
return new InputDialogValueResult { Success = true, Value = value };
|
||
}
|
||
|
||
private InputDialogValueResult CreateInputError(string label, string error)
|
||
{
|
||
return new InputDialogValueResult
|
||
{
|
||
Success = false,
|
||
Error = error,
|
||
Value = null
|
||
};
|
||
}
|
||
|
||
private GameObject CreateInputFieldRoot(string label, bool required)
|
||
{
|
||
GameObject fieldRoot = new GameObject($"Field_{label}");
|
||
fieldRoot.transform.SetParent(inputDialogFieldContainer, false);
|
||
|
||
VerticalLayoutGroup layout = fieldRoot.AddComponent<VerticalLayoutGroup>();
|
||
layout.spacing = 10;
|
||
layout.childControlWidth = true;
|
||
layout.childControlHeight = true;
|
||
layout.childForceExpandWidth = true;
|
||
layout.childForceExpandHeight = false;
|
||
layout.padding = new RectOffset(12, 12, 12, 12);
|
||
|
||
Image bg = fieldRoot.AddComponent<Image>();
|
||
bg.color = new Color(0.16f, 0.16f, 0.16f, 0.95f);
|
||
|
||
ContentSizeFitter fitter = fieldRoot.AddComponent<ContentSizeFitter>();
|
||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||
fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
||
|
||
LayoutElement element = fieldRoot.AddComponent<LayoutElement>();
|
||
element.flexibleWidth = 1f;
|
||
|
||
TMP_Text labelText = CreateDialogText(label + (required ? " *" : string.Empty), 28, TextAlignmentOptions.Left);
|
||
labelText.transform.SetParent(fieldRoot.transform, false);
|
||
|
||
return fieldRoot;
|
||
}
|
||
|
||
private TMP_InputField CreateDialogTextInput(Transform parent, string placeholder, string initialValue, TMP_InputField.ContentType contentType)
|
||
{
|
||
GameObject inputObj = new GameObject("InputField");
|
||
inputObj.transform.SetParent(parent, false);
|
||
Image bg = inputObj.AddComponent<Image>();
|
||
bg.color = new Color(0.24f, 0.24f, 0.24f, 1f);
|
||
|
||
LayoutElement layoutElement = inputObj.AddComponent<LayoutElement>();
|
||
layoutElement.preferredHeight = 80f;
|
||
layoutElement.flexibleWidth = 1f;
|
||
|
||
TMP_InputField inputField = inputObj.AddComponent<TMP_InputField>();
|
||
inputField.contentType = contentType;
|
||
|
||
GameObject textViewport = new GameObject("Text Area");
|
||
textViewport.transform.SetParent(inputObj.transform, false);
|
||
RectTransform viewportRect = textViewport.AddComponent<RectTransform>();
|
||
viewportRect.anchorMin = Vector2.zero;
|
||
viewportRect.anchorMax = Vector2.one;
|
||
viewportRect.offsetMin = new Vector2(20, 12);
|
||
viewportRect.offsetMax = new Vector2(-20, -12);
|
||
|
||
GameObject placeholderObj = new GameObject("Placeholder");
|
||
placeholderObj.transform.SetParent(textViewport.transform, false);
|
||
RectTransform placeholderRect = placeholderObj.AddComponent<RectTransform>();
|
||
placeholderRect.anchorMin = Vector2.zero;
|
||
placeholderRect.anchorMax = Vector2.one;
|
||
placeholderRect.offsetMin = Vector2.zero;
|
||
placeholderRect.offsetMax = Vector2.zero;
|
||
TMP_Text placeholderText = placeholderObj.AddComponent<TextMeshProUGUI>();
|
||
placeholderText.text = placeholder;
|
||
placeholderText.fontSize = 26;
|
||
placeholderText.color = new Color(1f, 1f, 1f, 0.35f);
|
||
placeholderText.alignment = TextAlignmentOptions.Left;
|
||
ApplySavedFont(placeholderText);
|
||
|
||
GameObject textObj = new GameObject("Text");
|
||
textObj.transform.SetParent(textViewport.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 = initialValue;
|
||
text.fontSize = 28;
|
||
text.color = Color.white;
|
||
text.alignment = TextAlignmentOptions.Left;
|
||
ApplySavedFont(text);
|
||
|
||
inputField.textViewport = viewportRect;
|
||
inputField.textComponent = text as TextMeshProUGUI;
|
||
inputField.placeholder = placeholderText;
|
||
inputField.text = initialValue;
|
||
|
||
return inputField;
|
||
}
|
||
|
||
private Button CreateDialogOptionButton(Transform parent, string text)
|
||
{
|
||
GameObject buttonObj = new GameObject($"Option_{text}");
|
||
buttonObj.transform.SetParent(parent, false);
|
||
Image image = buttonObj.AddComponent<Image>();
|
||
image.color = new Color(0.25f, 0.25f, 0.25f, 1f);
|
||
|
||
LayoutElement layoutElement = buttonObj.AddComponent<LayoutElement>();
|
||
layoutElement.preferredHeight = 68f;
|
||
layoutElement.flexibleWidth = 1f;
|
||
|
||
Button button = buttonObj.AddComponent<Button>();
|
||
|
||
TMP_Text textLabel = CreateDialogText(text, 26, TextAlignmentOptions.Center);
|
||
textLabel.transform.SetParent(buttonObj.transform, false);
|
||
RectTransform textRect = textLabel.GetComponent<RectTransform>();
|
||
textRect.anchorMin = Vector2.zero;
|
||
textRect.anchorMax = Vector2.one;
|
||
textRect.offsetMin = Vector2.zero;
|
||
textRect.offsetMax = Vector2.zero;
|
||
|
||
return button;
|
||
}
|
||
|
||
private void UpdateEnumOptionButtonStyles(List<Button> buttons, int selectedIndex)
|
||
{
|
||
for (int i = 0; i < buttons.Count; i++)
|
||
{
|
||
Image image = buttons[i].GetComponent<Image>();
|
||
if (image != null)
|
||
{
|
||
image.color = i == selectedIndex
|
||
? new Color(0.2f, 0.65f, 1f, 1f)
|
||
: new Color(0.25f, 0.25f, 0.25f, 1f);
|
||
}
|
||
}
|
||
}
|
||
|
||
private Toggle CreateDialogToggle(Transform parent, bool isOn, string labelText = "启用")
|
||
{
|
||
GameObject toggleObj = new GameObject($"Toggle_{labelText}");
|
||
toggleObj.transform.SetParent(parent, false);
|
||
|
||
Image rootImage = toggleObj.AddComponent<Image>();
|
||
rootImage.color = new Color(1f, 1f, 1f, 0.001f);
|
||
|
||
HorizontalLayoutGroup layout = toggleObj.AddComponent<HorizontalLayoutGroup>();
|
||
layout.spacing = 12;
|
||
layout.childAlignment = TextAnchor.MiddleLeft;
|
||
layout.childControlWidth = false;
|
||
layout.childControlHeight = true;
|
||
layout.childForceExpandWidth = false;
|
||
layout.childForceExpandHeight = false;
|
||
|
||
LayoutElement layoutElement = toggleObj.AddComponent<LayoutElement>();
|
||
layoutElement.preferredHeight = 64f;
|
||
layoutElement.flexibleWidth = 1f;
|
||
|
||
GameObject background = new GameObject("Background");
|
||
background.transform.SetParent(toggleObj.transform, false);
|
||
Image backgroundImage = background.AddComponent<Image>();
|
||
backgroundImage.color = new Color(0.8f, 0.8f, 0.8f, 1f);
|
||
RectTransform bgRect = background.GetComponent<RectTransform>();
|
||
bgRect.sizeDelta = new Vector2(42, 42);
|
||
LayoutElement bgLayout = background.AddComponent<LayoutElement>();
|
||
bgLayout.preferredWidth = 42f;
|
||
bgLayout.preferredHeight = 42f;
|
||
bgLayout.minWidth = 42f;
|
||
bgLayout.minHeight = 42f;
|
||
|
||
GameObject checkmark = new GameObject("Checkmark");
|
||
checkmark.transform.SetParent(background.transform, false);
|
||
Image checkmarkImage = checkmark.AddComponent<Image>();
|
||
checkmarkImage.color = new Color(0.2f, 0.8f, 0.2f, 1f);
|
||
RectTransform checkRect = checkmark.GetComponent<RectTransform>();
|
||
checkRect.anchorMin = Vector2.zero;
|
||
checkRect.anchorMax = Vector2.one;
|
||
checkRect.offsetMin = new Vector2(6, 6);
|
||
checkRect.offsetMax = new Vector2(-6, -6);
|
||
|
||
TMP_Text text = CreateDialogText(labelText, 26, TextAlignmentOptions.Left);
|
||
text.transform.SetParent(toggleObj.transform, false);
|
||
LayoutElement textElement = text.gameObject.AddComponent<LayoutElement>();
|
||
textElement.flexibleWidth = 1f;
|
||
|
||
Toggle toggle = toggleObj.AddComponent<Toggle>();
|
||
toggle.transition = Selectable.Transition.ColorTint;
|
||
toggle.targetGraphic = rootImage;
|
||
toggle.targetGraphic = backgroundImage;
|
||
toggle.graphic = checkmarkImage;
|
||
toggle.isOn = isOn;
|
||
|
||
return toggle;
|
||
}
|
||
|
||
private GameObject CreateDialogHint(Transform parent, string text)
|
||
{
|
||
TMP_Text hint = CreateDialogText(text, 24, TextAlignmentOptions.Left);
|
||
hint.color = new Color(1f, 0.6f, 0.4f, 1f);
|
||
hint.transform.SetParent(parent, false);
|
||
return hint.gameObject;
|
||
}
|
||
|
||
private TMP_Text CreateDialogText(string content, float fontSize, TextAlignmentOptions alignment)
|
||
{
|
||
GameObject textObj = new GameObject("Text");
|
||
TMP_Text text = textObj.AddComponent<TextMeshProUGUI>();
|
||
text.text = content;
|
||
text.fontSize = fontSize;
|
||
text.alignment = alignment;
|
||
text.color = Color.white;
|
||
ApplySavedFont(text);
|
||
return text;
|
||
}
|
||
|
||
private void ApplySavedFont(TMP_Text text)
|
||
{
|
||
if (text == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (savedFontAsset != null)
|
||
{
|
||
text.font = savedFontAsset;
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 公共API
|
||
/// <summary>
|
||
/// 设置键盘测试模式(用F键代替四指点击)
|
||
/// </summary>
|
||
/// <param name="enable">true=使用F键测试, false=使用四指点击</param>
|
||
public static void SetKeyboardTestMode(bool enable)
|
||
{
|
||
useKeyboardTestMode = enable;
|
||
Debug.Log($"[MeowmentDebugTool] 键盘测试模式: {(enable ? "开启 (按F键触发)" : "关闭 (使用四指点击)")})");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前是否为键盘测试模式
|
||
/// </summary>
|
||
public static bool IsKeyboardTestMode()
|
||
{
|
||
return useKeyboardTestMode;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取Debugger是否正在显示
|
||
/// </summary>
|
||
/// <returns>true=Debugger正在显示(主窗口或悬浮按钮),false=完全隐藏或未初始化</returns>
|
||
public static bool IsDebuggerVisible()
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// 首先检查整个GameObject是否激活
|
||
if (!Instance.gameObject.activeSelf)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// 如果GameObject激活,再检查主窗口或悬浮按钮是否可见
|
||
// 注意:使用 activeInHierarchy 而不是 activeSelf,因为需要考虑父对象的状态
|
||
bool mainWindowVisible = Instance.mainWindow != null && Instance.mainWindow.gameObject.activeInHierarchy;
|
||
bool floatingButtonVisible = Instance.floatingButton != null && Instance.floatingButton.activeInHierarchy;
|
||
|
||
return mainWindowVisible || floatingButtonVisible;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示调试工具
|
||
/// </summary>
|
||
public static void Show()
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法显示");
|
||
return;
|
||
}
|
||
|
||
Instance.gameObject.SetActive(true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 隐藏调试工具
|
||
/// </summary>
|
||
public static void Hide()
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法隐藏");
|
||
return;
|
||
}
|
||
|
||
Instance.gameObject.SetActive(false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换调试工具显示状态
|
||
/// </summary>
|
||
public static void Toggle()
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法切换状态");
|
||
return;
|
||
}
|
||
|
||
if (Instance.mainWindow != null && Instance.mainWindow.gameObject.activeSelf)
|
||
{
|
||
Instance.CloseDebugWindow();
|
||
}
|
||
else
|
||
{
|
||
Instance.OpenDebugWindow();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭调试窗口,显示悬浮按钮
|
||
/// </summary>
|
||
public void CloseDebugWindow()
|
||
{
|
||
if (mainWindow != null)
|
||
mainWindow.gameObject.SetActive(false);
|
||
|
||
if (floatingButton != null)
|
||
floatingButton.SetActive(true);
|
||
|
||
Debug.Log("[关闭] 调试窗口已关闭");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开调试窗口,隐藏悬浮按钮
|
||
/// </summary>
|
||
public void OpenDebugWindow()
|
||
{
|
||
if (floatingButton != null)
|
||
floatingButton.SetActive(false);
|
||
|
||
if (settingsModule != null)
|
||
settingsModule.RefreshAutoResolution();
|
||
|
||
if (mainWindow != null)
|
||
mainWindow.gameObject.SetActive(true);
|
||
|
||
if (!string.IsNullOrEmpty(currentPageKey) && pages.ContainsKey(currentPageKey))
|
||
{
|
||
ShowPage(currentPageKey);
|
||
}
|
||
|
||
Debug.Log("[打开] 调试窗口已打开");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示主窗口(初始化时调用)
|
||
/// </summary>
|
||
private void ShowMainWindow()
|
||
{
|
||
if (mainWindow != null)
|
||
mainWindow.gameObject.SetActive(false);
|
||
|
||
if (floatingButton != null)
|
||
floatingButton.SetActive(true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 暂时隐藏调试工具UI(用于截图等场景)
|
||
/// </summary>
|
||
/// <param name="seconds">隐藏的秒数,默认5秒</param>
|
||
public static void HideTemporarily(float seconds = 5f)
|
||
{
|
||
if (!isInitialized || !InstanceExists)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法隐藏");
|
||
return;
|
||
}
|
||
|
||
if (Instance.isHiding)
|
||
{
|
||
Debug.LogWarning("[MeowmentDebugTool] 已经在隐藏状态中,请等待恢复");
|
||
return;
|
||
}
|
||
|
||
Instance.StartCoroutine(Instance.HideTemporarilyCoroutine(seconds));
|
||
}
|
||
|
||
private IEnumerator HideTemporarilyCoroutine(float seconds)
|
||
{
|
||
isHiding = true;
|
||
Debug.Log($"[MeowmentDebugTool] 开始暂时隐藏流程,时长: {seconds}秒");
|
||
|
||
// 保存Canvas初始状态
|
||
if (canvas == null)
|
||
{
|
||
Debug.LogError("[MeowmentDebugTool] Canvas为空!");
|
||
isHiding = false;
|
||
yield break;
|
||
}
|
||
|
||
bool canvasWasEnabled = canvas.enabled;
|
||
Debug.Log($"[MeowmentDebugTool] Canvas初始状态: {(canvasWasEnabled ? "启用" : "禁用")}");
|
||
|
||
// 1. 先关闭主窗口(如果是打开状态)
|
||
bool wasMainWindowOpen = mainWindow != null && mainWindow.gameObject.activeSelf;
|
||
Debug.Log($"[MeowmentDebugTool] 主窗口初始状态: {(wasMainWindowOpen ? "打开" : "关闭")}");
|
||
|
||
if (wasMainWindowOpen)
|
||
{
|
||
CloseDebugWindow();
|
||
yield return null;
|
||
Debug.Log("[MeowmentDebugTool] 主窗口已关闭");
|
||
}
|
||
|
||
// 2. 隐藏Canvas(包括浮窗)
|
||
canvas.enabled = false;
|
||
Debug.Log($"[MeowmentDebugTool] Canvas已禁用,UI完全隐藏");
|
||
|
||
// 3. 等待指定时间
|
||
yield return new WaitForSeconds(seconds);
|
||
|
||
// 4. 恢复Canvas
|
||
canvas.enabled = true;
|
||
Debug.Log($"[MeowmentDebugTool] Canvas已启用,UI恢复显示");
|
||
|
||
// 5. 验证恢复结果
|
||
if (canvas.enabled)
|
||
{
|
||
Debug.Log("[MeowmentDebugTool] [OK] UI已成功恢复(浮窗状态)");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("[MeowmentDebugTool] ✗ Canvas恢复失败!");
|
||
}
|
||
|
||
isHiding = false;
|
||
Debug.Log("[MeowmentDebugTool] 暂时隐藏流程结束");
|
||
}
|
||
#endregion
|
||
|
||
// #region 四指点击检测(已禁用)
|
||
// /// <summary>
|
||
// /// 检测四指点击屏幕
|
||
// /// </summary>
|
||
// private void DetectFourFingerTap()
|
||
// {
|
||
// // 只在移动设备或Unity编辑器(模拟触摸)上检测
|
||
// #if UNITY_ANDROID || UNITY_IOS || UNITY_EDITOR
|
||
//
|
||
// // 检测四指同时按下
|
||
// if (Input.touchCount == 4)
|
||
// {
|
||
// // 检查是否所有触摸都刚开始
|
||
// bool allBegan = true;
|
||
// foreach (Touch touch in Input.touches)
|
||
// {
|
||
// if (touch.phase != TouchPhase.Began)
|
||
// {
|
||
// allBegan = false;
|
||
// break;
|
||
// }
|
||
// }
|
||
//
|
||
// if (allBegan && !wasFourFingerTouch)
|
||
// {
|
||
// wasFourFingerTouch = true;
|
||
// fourFingerTouchStartTime = Time.time;
|
||
// }
|
||
// }
|
||
//
|
||
// // 检测四指抬起(点击完成)
|
||
// if (wasFourFingerTouch && Input.touchCount == 0)
|
||
// {
|
||
// float touchDuration = Time.time - fourFingerTouchStartTime;
|
||
//
|
||
// // 如果触摸时间小于阈值,认为是点击(而非长按)
|
||
// if (touchDuration < fourFingerTapTime)
|
||
// {
|
||
// OnFourFingerTap();
|
||
// }
|
||
//
|
||
// wasFourFingerTouch = false;
|
||
// }
|
||
//
|
||
// // 如果触摸数量变化,重置状态
|
||
// if (wasFourFingerTouch && Input.touchCount != 4)
|
||
// {
|
||
// wasFourFingerTouch = false;
|
||
// }
|
||
//
|
||
// #endif
|
||
// }
|
||
|
||
/// <summary>
|
||
/// 四指点击触发的事件 - 切换所有UI的显示/隐藏(包括主窗口和浮窗)
|
||
/// 注意:此方法保留用于键盘测试模式(按F键),四指点击检测已禁用
|
||
/// </summary>
|
||
private void OnFourFingerTap()
|
||
{
|
||
Debug.Log("[MeowmentDebugTool] 检测到F键触发(测试模式)!");
|
||
|
||
// 检查主窗口或浮窗是否有任意一个显示
|
||
bool anyUIVisible = (mainWindow != null && mainWindow.gameObject.activeSelf) ||
|
||
(floatingButton != null && floatingButton.activeSelf);
|
||
|
||
if (anyUIVisible)
|
||
{
|
||
// 隐藏所有UI
|
||
if (mainWindow != null)
|
||
mainWindow.gameObject.SetActive(false);
|
||
if (floatingButton != null)
|
||
floatingButton.SetActive(false);
|
||
|
||
Debug.Log("[MeowmentDebugTool] 所有UI已隐藏");
|
||
}
|
||
else
|
||
{
|
||
// 显示浮窗
|
||
if (mainWindow != null)
|
||
mainWindow.gameObject.SetActive(false);
|
||
if (floatingButton != null)
|
||
floatingButton.SetActive(true);
|
||
|
||
Debug.Log("[MeowmentDebugTool] 已显示浮窗");
|
||
}
|
||
}
|
||
// #endregion
|
||
}
|
||
|
||
#region 自定义特性
|
||
/// <summary>
|
||
/// 调试按钮特性 - 标记方法为调试按钮
|
||
///
|
||
/// 使用说明:
|
||
/// 1. 在主项目中使用此特性时,请用条件编译包裹:
|
||
///
|
||
/// #if MEOWMENT_DEBUG_TOOL
|
||
/// [DebugButton("默认", "我的调试按钮")]
|
||
/// public static void MyDebugMethod()
|
||
/// {
|
||
/// Debug.Log("调试方法被执行");
|
||
/// }
|
||
/// #endif
|
||
///
|
||
/// 2. 这样当包被卸载时,代码不会报错,也不会影响主工程的正常运行
|
||
/// 3. MEOWMENT_DEBUG_TOOL 宏会在包安装时自动定义,卸载时自动移除
|
||
/// 4. 组名参数用于对按钮进行分组显示,未指定组名的按钮会归入"默认"组
|
||
/// </summary>
|
||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||
public class DebugButtonAttribute : Attribute
|
||
{
|
||
public string TabName { get; set; }
|
||
public string GroupName { get; set; }
|
||
public string DisplayName { get; set; }
|
||
public Color ButtonColor { get; set; }
|
||
|
||
public DebugButtonAttribute(string groupName = "默认", string displayName = "", float r = 0.8f, float g = 0.8f, float b = 0.8f)
|
||
{
|
||
TabName = string.Empty;
|
||
GroupName = string.IsNullOrEmpty(groupName) ? "默认" : groupName;
|
||
DisplayName = displayName;
|
||
ButtonColor = new Color(r, g, b, 1f);
|
||
}
|
||
|
||
public DebugButtonAttribute(string tabName, string groupName, string displayName = "", float r = 0.8f, float g = 0.8f, float b = 0.8f)
|
||
{
|
||
TabName = string.IsNullOrEmpty(tabName) ? "默认" : tabName;
|
||
GroupName = string.IsNullOrEmpty(groupName) ? "默认" : groupName;
|
||
DisplayName = displayName;
|
||
ButtonColor = new Color(r, g, b, 1f);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调试按钮类级别分组特性(可选)
|
||
///
|
||
/// 使用说明:
|
||
/// 1. 可以标记在 class 上,为该类下所有 DebugButton 提供默认 Tab 和默认组
|
||
/// 2. 方法上的 DebugButton 若明确指定了 Tab/组,则优先使用方法上的配置
|
||
/// </summary>
|
||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||
public class DebugButtonClassAttribute : Attribute
|
||
{
|
||
public string TabName { get; private set; }
|
||
public string GroupName { get; private set; }
|
||
public Color TabColor { get; private set; }
|
||
|
||
public DebugButtonClassAttribute(string tabName = "默认", string groupName = "默认", float r = 0.2f, float g = 0.6f, float b = 1f)
|
||
{
|
||
TabName = string.IsNullOrEmpty(tabName) ? "默认" : tabName;
|
||
GroupName = string.IsNullOrEmpty(groupName) ? "默认" : groupName;
|
||
TabColor = new Color(r, g, b, 1f);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 输入型调试按钮特性 - 点击按钮后弹出输入面板,再执行目标方法
|
||
/// </summary>
|
||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||
public class DebugInputButtonAttribute : Attribute
|
||
{
|
||
public string TabName { get; set; }
|
||
public string GroupName { get; set; }
|
||
public string DisplayName { get; set; }
|
||
public string DialogTitle { get; set; }
|
||
public Color ButtonColor { get; set; }
|
||
|
||
public DebugInputButtonAttribute(string groupName = "默认", string displayName = "", string dialogTitle = "", float r = 0.8f, float g = 0.8f, float b = 0.8f)
|
||
{
|
||
TabName = string.Empty;
|
||
GroupName = string.IsNullOrEmpty(groupName) ? "默认" : groupName;
|
||
DisplayName = displayName;
|
||
DialogTitle = dialogTitle;
|
||
ButtonColor = new Color(r, g, b, 1f);
|
||
}
|
||
|
||
public DebugInputButtonAttribute(string tabName, string groupName, string displayName = "", string dialogTitle = "", float r = 0.8f, float g = 0.8f, float b = 0.8f)
|
||
{
|
||
TabName = string.IsNullOrEmpty(tabName) ? "默认" : tabName;
|
||
GroupName = string.IsNullOrEmpty(groupName) ? "默认" : groupName;
|
||
DisplayName = displayName;
|
||
DialogTitle = dialogTitle;
|
||
ButtonColor = new Color(r, g, b, 1f);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 输入型调试按钮的参数显示配置
|
||
/// </summary>
|
||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
|
||
public class DebugInputParameterAttribute : Attribute
|
||
{
|
||
public string Label { get; private set; }
|
||
public string Placeholder { get; set; }
|
||
public string DefaultValue { get; set; }
|
||
public string OptionsProvider { get; set; }
|
||
public bool Required { get; set; }
|
||
public float Min { get; set; } = float.NaN;
|
||
public float Max { get; set; } = float.NaN;
|
||
|
||
public DebugInputParameterAttribute(string label = "")
|
||
{
|
||
Label = label;
|
||
Placeholder = string.Empty;
|
||
DefaultValue = string.Empty;
|
||
OptionsProvider = string.Empty;
|
||
Required = false;
|
||
}
|
||
}
|
||
|
||
public class DebugInputOption
|
||
{
|
||
public string Label { get; private set; }
|
||
public string Value { get; private set; }
|
||
|
||
public DebugInputOption(string label, string value)
|
||
{
|
||
Label = label;
|
||
Value = value;
|
||
}
|
||
}
|
||
|
||
public class RuntimeDebugInputButtonDefinition
|
||
{
|
||
public string Id { get; set; }
|
||
public string TabName { get; set; }
|
||
public string GroupName { get; set; }
|
||
public string DisplayName { get; set; }
|
||
public string DialogTitle { get; set; }
|
||
public Color ButtonColor { get; set; }
|
||
public Color TabColor { get; set; }
|
||
public List<RuntimeDebugInputParameterDefinition> Parameters { get; set; }
|
||
public Action<RuntimeDebugInputResult> Callback { get; set; }
|
||
|
||
public RuntimeDebugInputButtonDefinition(string id, Action<RuntimeDebugInputResult> callback)
|
||
{
|
||
Id = id;
|
||
Callback = callback;
|
||
TabName = "默认";
|
||
GroupName = "默认";
|
||
DisplayName = string.Empty;
|
||
DialogTitle = string.Empty;
|
||
ButtonColor = new Color(0.8f, 0.8f, 0.8f, 1f);
|
||
TabColor = new Color(0.2f, 0.6f, 1f, 1f);
|
||
Parameters = new List<RuntimeDebugInputParameterDefinition>();
|
||
}
|
||
}
|
||
|
||
public class RuntimeDebugInputParameterDefinition
|
||
{
|
||
public string Key { get; set; }
|
||
public Type ParameterType { get; set; }
|
||
public string Label { get; set; }
|
||
public string Placeholder { get; set; }
|
||
public string DefaultValue { get; set; }
|
||
public bool Required { get; set; }
|
||
public float Min { get; set; } = float.NaN;
|
||
public float Max { get; set; } = float.NaN;
|
||
public Func<IEnumerable> OptionsProvider { get; set; }
|
||
|
||
public RuntimeDebugInputParameterDefinition(string key, Type parameterType, string label = "")
|
||
{
|
||
Key = key;
|
||
ParameterType = parameterType;
|
||
Label = label;
|
||
Placeholder = string.Empty;
|
||
DefaultValue = string.Empty;
|
||
Required = false;
|
||
}
|
||
}
|
||
|
||
public class RuntimeDebugInputResult
|
||
{
|
||
private readonly Dictionary<string, object> values;
|
||
|
||
internal RuntimeDebugInputResult(Dictionary<string, object> values)
|
||
{
|
||
this.values = values ?? new Dictionary<string, object>();
|
||
}
|
||
|
||
public object this[string key] => values.TryGetValue(key, out object value) ? value : null;
|
||
|
||
public T Get<T>(string key)
|
||
{
|
||
if (!values.TryGetValue(key, out object value))
|
||
{
|
||
throw new KeyNotFoundException($"未找到参数: {key}");
|
||
}
|
||
|
||
if (value is T typedValue)
|
||
{
|
||
return typedValue;
|
||
}
|
||
|
||
if (value == null)
|
||
{
|
||
return default;
|
||
}
|
||
|
||
Type targetType = typeof(T);
|
||
if (targetType.IsEnum)
|
||
{
|
||
return (T)Enum.Parse(targetType, value.ToString(), true);
|
||
}
|
||
|
||
return (T)Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture);
|
||
}
|
||
|
||
public bool TryGet<T>(string key, out T value)
|
||
{
|
||
try
|
||
{
|
||
value = Get<T>(key);
|
||
return true;
|
||
}
|
||
catch
|
||
{
|
||
value = default;
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调试复选框特性 - 标记方法为调试复选框开关
|
||
///
|
||
/// 使用说明:
|
||
/// 1. 在主项目中使用此特性时,请用条件编译包裹:
|
||
///
|
||
/// #if MEOWMENT_DEBUG_TOOL
|
||
/// [DebugCheckBox("开关名称")]
|
||
/// public static void MyToggleMethod(bool isOn)
|
||
/// {
|
||
/// Debug.Log($"开关状态: {isOn}");
|
||
/// // 在这里设置某些参数的值
|
||
/// }
|
||
/// #endif
|
||
///
|
||
/// 2. 方法必须接受一个 bool 参数,表示复选框的状态
|
||
/// 3. 这样当包被卸载时,代码不会报错,也不会影响主工程的正常运行
|
||
/// 4. MEOWMENT_DEBUG_TOOL 宏会在包安装时自动定义,卸载时自动移除
|
||
/// </summary>
|
||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||
public class DebugCheckBoxAttribute : Attribute
|
||
{
|
||
public string TabName { get; set; }
|
||
public string DisplayName { get; set; }
|
||
public Color CheckBoxColor { get; set; }
|
||
|
||
public DebugCheckBoxAttribute(string displayName = "", float r = 0.8f, float g = 0.8f, float b = 0.8f)
|
||
{
|
||
TabName = string.Empty;
|
||
DisplayName = displayName;
|
||
CheckBoxColor = new Color(r, g, b, 1f);
|
||
}
|
||
|
||
public DebugCheckBoxAttribute(string tabName, string displayName, float r = 0.8f, float g = 0.8f, float b = 0.8f)
|
||
{
|
||
TabName = string.IsNullOrEmpty(tabName) ? "默认" : tabName;
|
||
DisplayName = displayName;
|
||
CheckBoxColor = new Color(r, g, b, 1f);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调试复选框类级别Tab特性(可选)
|
||
///
|
||
/// 使用说明:
|
||
/// 1. 可以标记在 class 上,为该类下所有 DebugCheckBox 提供默认 Tab 和 Tab 颜色
|
||
/// 2. 方法上的 DebugCheckBox 若明确指定了 Tab,则优先使用方法上的配置
|
||
/// </summary>
|
||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||
public class DebugCheckBoxClassAttribute : Attribute
|
||
{
|
||
public string TabName { get; private set; }
|
||
public Color TabColor { get; private set; }
|
||
|
||
public DebugCheckBoxClassAttribute(string tabName = "默认", float r = 0.2f, float g = 0.6f, float b = 1f)
|
||
{
|
||
TabName = string.IsNullOrEmpty(tabName) ? "默认" : tabName;
|
||
TabColor = new Color(r, g, b, 1f);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调试数值特性 - 标记方法为调试数值调整器
|
||
///
|
||
/// 使用说明:
|
||
/// 1. 在主项目中使用此特性时,请用条件编译包裹:
|
||
///
|
||
/// #if MEOWMENT_DEBUG_TOOL
|
||
/// [DebugValue("数值名称", 最小值, 最大值)]
|
||
/// public static void MyValueMethod(int value)
|
||
/// {
|
||
/// Debug.Log($"设置数值: {value}");
|
||
/// // 在这里使用value来设置游戏参数
|
||
/// }
|
||
/// #endif
|
||
///
|
||
/// 2. 方法必须接受一个 int 参数,表示调整后的数值
|
||
/// 3. 可以指定最小值和最大值来限制范围
|
||
/// 4. 可以设置自定义颜色
|
||
/// 5. MEOWMENT_DEBUG_TOOL 宏会在包安装时自动定义,卸载时自动移除
|
||
/// </summary>
|
||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||
public class DebugValueAttribute : Attribute
|
||
{
|
||
public string TabName { get; set; }
|
||
public string DisplayName { get; set; }
|
||
public Color ValueColor { get; set; }
|
||
public int MinValue { get; set; }
|
||
public int MaxValue { get; set; }
|
||
public int DefaultValue { get; set; }
|
||
|
||
public DebugValueAttribute(string displayName = "", int minValue = 0, int maxValue = 100, int defaultValue = -1, float r = 0.8f, float g = 0.8f, float b = 0.8f)
|
||
{
|
||
TabName = string.Empty;
|
||
DisplayName = displayName;
|
||
MinValue = minValue;
|
||
MaxValue = maxValue;
|
||
// 如果defaultValue为-1(未设置),则使用minValue作为默认值
|
||
DefaultValue = defaultValue == -1 ? minValue : defaultValue;
|
||
ValueColor = new Color(r, g, b, 1f);
|
||
}
|
||
|
||
public DebugValueAttribute(string tabName, string displayName, int minValue, int maxValue, int defaultValue = -1, float r = 0.8f, float g = 0.8f, float b = 0.8f)
|
||
{
|
||
TabName = string.IsNullOrEmpty(tabName) ? "默认" : tabName;
|
||
DisplayName = displayName;
|
||
MinValue = minValue;
|
||
MaxValue = maxValue;
|
||
DefaultValue = defaultValue == -1 ? minValue : defaultValue;
|
||
ValueColor = new Color(r, g, b, 1f);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调试数值类级别Tab特性(可选)
|
||
///
|
||
/// 使用说明:
|
||
/// 1. 可以标记在 class 上,为该类下所有 DebugValue 提供默认 Tab 和 Tab 颜色
|
||
/// 2. 方法上的 DebugValue 若明确指定了 Tab,则优先使用方法上的配置
|
||
/// </summary>
|
||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||
public class DebugValueClassAttribute : Attribute
|
||
{
|
||
public string TabName { get; private set; }
|
||
public Color TabColor { get; private set; }
|
||
|
||
public DebugValueClassAttribute(string tabName = "默认", float r = 0.2f, float g = 0.6f, float b = 1f)
|
||
{
|
||
TabName = string.IsNullOrEmpty(tabName) ? "默认" : tabName;
|
||
TabColor = new Color(r, g, b, 1f);
|
||
}
|
||
}
|
||
#endregion
|
||
}
|