930 lines
30 KiB
C#
930 lines
30 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
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 toolbarPage;
|
||
[SerializeField] private Slider timeAdjustSlider;
|
||
[SerializeField] private TMP_Text timeAdjustValueText;
|
||
[SerializeField] private Button timeIncreaseButton;
|
||
[SerializeField] private Button timeDecreaseButton;
|
||
|
||
[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;
|
||
|
||
[Header("输入对话框")]
|
||
[SerializeField] private GameObject inputDialog;
|
||
[SerializeField] private TMP_Text inputDialogTitle;
|
||
[SerializeField] private TMP_InputField inputDialogInputField;
|
||
[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 = "";
|
||
|
||
// 默认分辨率
|
||
private Vector2 defaultResolution = new Vector2(1080, 2340);
|
||
private Vector2 currentCustomResolution;
|
||
|
||
// 自定义按钮回调
|
||
private static Action<Button, TMP_Text> customButtonCallback;
|
||
|
||
// Canvas层级设置
|
||
private const int TOP_SORT_ORDER = 30000;
|
||
|
||
// 保存的SDF字体资源
|
||
private static TMP_FontAsset savedFontAsset = null;
|
||
#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()
|
||
{
|
||
if (instance == null)
|
||
{
|
||
instance = this;
|
||
DontDestroyOnLoad(gameObject);
|
||
}
|
||
else if (instance != this)
|
||
{
|
||
Destroy(gameObject);
|
||
return;
|
||
}
|
||
|
||
currentCustomResolution = defaultResolution;
|
||
|
||
// 设置Canvas为最上层
|
||
SetCanvasToTop();
|
||
|
||
// 未初始化前隐藏所有UI
|
||
if (!isInitialized)
|
||
{
|
||
HideAllUI();
|
||
}
|
||
// 注意:如果isInitialized为true,不在Awake中调用InitializeDebugTool
|
||
// 因为此时反射设置的字段可能还没生效,延迟到Start中调用
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
// 只有在已初始化但还没调用过InitializeDebugTool时才执行
|
||
// 这避免了Awake和Init()的重复调用问题
|
||
if (isInitialized && pages.Count == 0)
|
||
{
|
||
// 这是通过Init()创建的实例,需要在Start中完成初始化
|
||
// 此时反射设置的字段已经生效
|
||
InitializeDebugTool();
|
||
UpdateDeviceInfo();
|
||
UpdateSystemInfo();
|
||
LoadCustomButtons();
|
||
ShowMainWindow();
|
||
}
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (instance == this)
|
||
{
|
||
instance = null;
|
||
}
|
||
}
|
||
#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.UpdateDeviceInfo();
|
||
Instance.UpdateSystemInfo();
|
||
Instance.LoadCustomButtons();
|
||
Instance.ShowMainWindow();
|
||
|
||
Debug.Log("[MeowmentDebugTool] ✅ 初始化完成!");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置所有TextMeshProUGUI的SDF字体
|
||
/// </summary>
|
||
/// <param name="fontAsset">TMP字体资源</param>
|
||
public static void SetSDFFont(TMP_FontAsset fontAsset)
|
||
{
|
||
if (!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. 重新加载自定义按钮,使新字体应用到已存在的CustomButton
|
||
Debug.Log("[MeowmentDebugTool] 重新加载自定义按钮以应用新字体...");
|
||
Instance.ReloadCustomButtons();
|
||
}
|
||
|
||
/// <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("🚀 初始化UniversalDebugTool...");
|
||
|
||
// 注册所有页面
|
||
RegisterPage("参数", parametersPage);
|
||
RegisterPage("自定义按钮", customButtonsPage);
|
||
RegisterPage("工具栏", toolbarPage);
|
||
RegisterPage("设置", settingsPage);
|
||
|
||
Debug.Log($"📄 已注册{pages.Count}个页面");
|
||
|
||
// 创建标签按钮
|
||
CreateTabButtons();
|
||
|
||
// 设置按钮事件
|
||
if (closeButton != null)
|
||
closeButton.onClick.AddListener(CloseDebugWindow);
|
||
|
||
if (applyResolutionButton != null)
|
||
applyResolutionButton.onClick.AddListener(ApplyCustomResolution);
|
||
|
||
if (resetResolutionButton != null)
|
||
resetResolutionButton.onClick.AddListener(ResetToDefaultResolution);
|
||
|
||
// 设置时间调整工具
|
||
if (timeAdjustSlider != null)
|
||
{
|
||
timeAdjustSlider.onValueChanged.AddListener(OnTimeAdjustValueChanged);
|
||
// 初始化显示
|
||
UpdateTimeAdjustDisplay(timeAdjustSlider.value);
|
||
}
|
||
|
||
if (timeIncreaseButton != null)
|
||
timeIncreaseButton.onClick.AddListener(OnIncreaseTime);
|
||
|
||
if (timeDecreaseButton != null)
|
||
timeDecreaseButton.onClick.AddListener(OnDecreaseTime);
|
||
|
||
// 设置悬浮按钮点击事件
|
||
if (floatingButton != null)
|
||
{
|
||
Button floatBtn = floatingButton.GetComponent<Button>();
|
||
if (floatBtn != null)
|
||
{
|
||
floatBtn.onClick.AddListener(OnFloatingButtonClick);
|
||
}
|
||
}
|
||
|
||
// 默认显示第一个页面
|
||
if (pages.Count > 0)
|
||
{
|
||
ShowPage("参数");
|
||
}
|
||
|
||
// 应用默认分辨率
|
||
ApplyResolution(defaultResolution);
|
||
|
||
Debug.Log("✅ UniversalDebugTool初始化完成!");
|
||
Debug.Log("💡 提示: 点击窗口顶部的标签切换页面,或按F1-F4使用快捷键");
|
||
}
|
||
|
||
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($"📋 开始创建{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($"✅ 创建标签: [{page.Key}]");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"⚠️ 标签按钮 [{page.Key}] 缺少文本组件");
|
||
}
|
||
|
||
string pageName = page.Key; // 闭包捕获
|
||
button.onClick.AddListener(() => ShowPage(pageName));
|
||
|
||
tabButtons[page.Key] = button;
|
||
}
|
||
|
||
Debug.Log("✅ 标签按钮创建完成!");
|
||
}
|
||
#endregion
|
||
|
||
#region 页面管理
|
||
/// <summary>
|
||
/// 显示指定的页面
|
||
/// </summary>
|
||
public void ShowPage(string pageName)
|
||
{
|
||
if (!pages.ContainsKey(pageName))
|
||
{
|
||
Debug.LogWarning($"页面 '{pageName}' 不存在!");
|
||
return;
|
||
}
|
||
|
||
// 隐藏所有页面
|
||
foreach (var page in pages.Values)
|
||
{
|
||
page.SetActive(false);
|
||
}
|
||
|
||
// 显示目标页面
|
||
pages[pageName].SetActive(true);
|
||
currentPageKey = pageName;
|
||
|
||
// 更新标签按钮状态
|
||
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 参数查看功能
|
||
private void UpdateDeviceInfo()
|
||
{
|
||
if (deviceInfoText == null) return;
|
||
|
||
string info = "===== 设备信息 =====\n";
|
||
info += $"设备名称: {SystemInfo.deviceName}\n";
|
||
info += $"设备型号: {SystemInfo.deviceModel}\n";
|
||
info += $"设备类型: {SystemInfo.deviceType}\n";
|
||
info += $"操作系统: {SystemInfo.operatingSystem}\n";
|
||
info += $"处理器: {SystemInfo.processorType}\n";
|
||
info += $"处理器核心数: {SystemInfo.processorCount}\n";
|
||
info += $"系统内存: {SystemInfo.systemMemorySize} MB\n";
|
||
info += $"显存: {SystemInfo.graphicsMemorySize} MB\n";
|
||
info += $"设备标识符: {SystemInfo.deviceUniqueIdentifier}\n";
|
||
|
||
deviceInfoText.text = info;
|
||
}
|
||
|
||
private void UpdateSystemInfo()
|
||
{
|
||
if (systemInfoText == null) return;
|
||
|
||
string info = "===== 系统信息 =====\n";
|
||
info += $"Unity版本: {Application.unityVersion}\n";
|
||
info += $"平台: {Application.platform}\n";
|
||
info += $"产品名称: {Application.productName}\n";
|
||
info += $"公司名称: {Application.companyName}\n";
|
||
info += $"版本: {Application.version}\n";
|
||
info += $"数据路径: {Application.dataPath}\n";
|
||
info += $"持久化数据路径: {Application.persistentDataPath}\n";
|
||
info += $"临时缓存路径: {Application.temporaryCachePath}\n";
|
||
info += $"屏幕分辨率: {Screen.width} x {Screen.height} @ {Screen.currentResolution.refreshRateRatio.value:F2}Hz\n";
|
||
info += $"屏幕DPI: {Screen.dpi}\n";
|
||
info += $"是否全屏: {Screen.fullScreen}\n";
|
||
info += $"目标帧率: {Application.targetFrameRate}\n";
|
||
info += $"当前帧率: {(int)(1f / Time.smoothDeltaTime)}\n";
|
||
info += $"\n===== 图形信息 =====\n";
|
||
info += $"图形设备名称: {SystemInfo.graphicsDeviceName}\n";
|
||
info += $"图形设备供应商: {SystemInfo.graphicsDeviceVendor}\n";
|
||
info += $"图形设备类型: {SystemInfo.graphicsDeviceType}\n";
|
||
info += $"图形设备版本: {SystemInfo.graphicsDeviceVersion}\n";
|
||
info += $"着色器等级: {SystemInfo.graphicsShaderLevel}\n";
|
||
info += $"多线程渲染: {SystemInfo.graphicsMultiThreaded}\n";
|
||
|
||
systemInfoText.text = info;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 复制设备信息到剪贴板
|
||
/// </summary>
|
||
public void CopyDeviceInfoToClipboard()
|
||
{
|
||
if (deviceInfoText != null)
|
||
{
|
||
GUIUtility.systemCopyBuffer = deviceInfoText.text;
|
||
Debug.Log("设备信息已复制到剪贴板");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 复制系统信息到剪贴板
|
||
/// </summary>
|
||
public void CopySystemInfoToClipboard()
|
||
{
|
||
if (systemInfoText != null)
|
||
{
|
||
GUIUtility.systemCopyBuffer = systemInfoText.text;
|
||
Debug.Log("系统信息已复制到剪贴板");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新所有信息
|
||
/// </summary>
|
||
public void RefreshAllInfo()
|
||
{
|
||
UpdateDeviceInfo();
|
||
UpdateSystemInfo();
|
||
}
|
||
#endregion
|
||
|
||
#region 自定义按钮功能
|
||
/// <summary>
|
||
/// 加载所有自定义按钮(使用反射)
|
||
/// </summary>
|
||
private void LoadCustomButtons()
|
||
{
|
||
if (buttonContainer == null || buttonPrefab == null)
|
||
{
|
||
Debug.LogWarning("按钮容器或按钮预制件未设置");
|
||
return;
|
||
}
|
||
|
||
// 清空现有按钮
|
||
foreach (Transform child in buttonContainer)
|
||
{
|
||
Destroy(child.gameObject);
|
||
}
|
||
|
||
// 查找所有标记为DebugButton的方法
|
||
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<DebugButtonAttribute>();
|
||
if (attribute != null)
|
||
{
|
||
CreateCustomButton(method, attribute);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
// 某些程序集可能无法访问,跳过
|
||
Debug.LogWarning($"无法访问程序集 {assembly.FullName}: {e.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
private void CreateCustomButton(MethodInfo method, DebugButtonAttribute attribute)
|
||
{
|
||
GameObject buttonObj = Instantiate(buttonPrefab, buttonContainer);
|
||
Button button = buttonObj.GetComponent<Button>();
|
||
TMP_Text buttonText = buttonObj.GetComponentInChildren<TMP_Text>();
|
||
Image buttonImage = buttonObj.GetComponent<Image>();
|
||
|
||
// 设置按钮文本
|
||
if (buttonText != null)
|
||
{
|
||
buttonText.text = string.IsNullOrEmpty(attribute.DisplayName) ? method.Name : attribute.DisplayName;
|
||
|
||
// 应用保存的字体
|
||
if (savedFontAsset != null)
|
||
{
|
||
buttonText.font = savedFontAsset;
|
||
}
|
||
}
|
||
|
||
// 设置按钮颜色
|
||
if (buttonImage != null && attribute.ButtonColor != default(Color))
|
||
{
|
||
buttonImage.color = attribute.ButtonColor;
|
||
}
|
||
|
||
// 设置按钮点击事件
|
||
button.onClick.AddListener(() =>
|
||
{
|
||
try
|
||
{
|
||
method.Invoke(null, null);
|
||
Debug.Log($"执行调试方法: {method.Name}");
|
||
|
||
// 调用回调
|
||
customButtonCallback?.Invoke(button, buttonText);
|
||
customButtonCallback = null;
|
||
|
||
// 点击按钮后关闭主窗口
|
||
CloseDebugWindow();
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"执行调试方法 {method.Name} 时出错: {e.Message}");
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置自定义按钮回调
|
||
/// </summary>
|
||
public static void SetCustomButtonCallback(Action<Button, TMP_Text> callback)
|
||
{
|
||
customButtonCallback = callback;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重新加载自定义按钮
|
||
/// </summary>
|
||
public void ReloadCustomButtons()
|
||
{
|
||
LoadCustomButtons();
|
||
}
|
||
#endregion
|
||
|
||
#region 工具栏功能
|
||
private float timeAdjustValue = 60f; // 当前设置的时间调整值(秒)
|
||
|
||
/// <summary>
|
||
/// 时间调整Slider值改变回调(只更新显示,不改变时间)
|
||
/// </summary>
|
||
private void OnTimeAdjustValueChanged(float value)
|
||
{
|
||
timeAdjustValue = value;
|
||
UpdateTimeAdjustDisplay(value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新时间调整显示
|
||
/// </summary>
|
||
private void UpdateTimeAdjustDisplay(float seconds)
|
||
{
|
||
if (timeAdjustValueText != null)
|
||
{
|
||
if (seconds < 60)
|
||
{
|
||
timeAdjustValueText.text = $"{seconds:F0}秒";
|
||
}
|
||
else
|
||
{
|
||
float minutes = seconds / 60f;
|
||
timeAdjustValueText.text = $"{minutes:F1}分钟";
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 增加时间
|
||
/// </summary>
|
||
private void OnIncreaseTime()
|
||
{
|
||
// 修改系统时间(这里演示修改Time.time的偏移)
|
||
float adjustSeconds = timeAdjustValue;
|
||
|
||
// 注意:Time.time本身无法直接修改,这里提供一个可以被重写的方法
|
||
// 实际项目中,你应该修改游戏内的时间变量
|
||
AdjustGameTime(adjustSeconds);
|
||
|
||
Debug.Log($"⏰ 增加时间: +{adjustSeconds}秒");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 减少时间
|
||
/// </summary>
|
||
private void OnDecreaseTime()
|
||
{
|
||
float adjustSeconds = -timeAdjustValue;
|
||
|
||
AdjustGameTime(adjustSeconds);
|
||
|
||
Debug.Log($"⏰ 减少时间: {adjustSeconds}秒");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调整游戏时间(示例方法,需要根据实际项目修改)
|
||
/// </summary>
|
||
private void AdjustGameTime(float seconds)
|
||
{
|
||
// 方案1: 如果你的项目有全局时间管理器
|
||
// TimeManager.Instance?.AddTime(seconds);
|
||
|
||
// 方案2: 如果你使用DateTime
|
||
// GameTime.Current = GameTime.Current.AddSeconds(seconds);
|
||
|
||
// 方案3: 临时演示 - 修改Time.timeScale来模拟时间变化
|
||
// 实际项目中应该修改你自己的时间变量
|
||
Debug.Log($"💡 提示: 请在AdjustGameTime方法中实现你的时间调整逻辑");
|
||
Debug.Log($"💡 建议: 修改游戏内的时间变量,例如 GameTime.Current.AddSeconds({seconds})");
|
||
|
||
// 示例:如果你有一个静态的游戏时间偏移量
|
||
// GameTimeOffset += seconds;
|
||
}
|
||
#endregion
|
||
|
||
#region 分辨率设置
|
||
private void ApplyCustomResolution()
|
||
{
|
||
if (widthInputField == null || heightInputField == null) return;
|
||
|
||
if (float.TryParse(widthInputField.text, out float width) &&
|
||
float.TryParse(heightInputField.text, out float height))
|
||
{
|
||
if (width > 0 && height > 0)
|
||
{
|
||
currentCustomResolution = new Vector2(width, height);
|
||
ApplyResolution(currentCustomResolution);
|
||
Debug.Log($"已应用自定义分辨率: {width} x {height}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("分辨率值必须大于0");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("无效的分辨率值");
|
||
}
|
||
}
|
||
|
||
private void ResetToDefaultResolution()
|
||
{
|
||
currentCustomResolution = defaultResolution;
|
||
ApplyResolution(defaultResolution);
|
||
|
||
if (widthInputField != null)
|
||
widthInputField.text = defaultResolution.x.ToString();
|
||
if (heightInputField != null)
|
||
heightInputField.text = defaultResolution.y.ToString();
|
||
|
||
Debug.Log($"已重置为默认分辨率: {defaultResolution.x} x {defaultResolution.y}");
|
||
}
|
||
|
||
private void ApplyResolution(Vector2 resolution)
|
||
{
|
||
if (mainWindow != null)
|
||
{
|
||
mainWindow.sizeDelta = resolution;
|
||
}
|
||
|
||
if (currentResolutionText != null)
|
||
{
|
||
currentResolutionText.text = $"当前窗口尺寸: {resolution.x} x {resolution.y}";
|
||
}
|
||
|
||
// 强制刷新布局
|
||
if (canvas != null)
|
||
{
|
||
Canvas.ForceUpdateCanvases();
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 输入对话框
|
||
/// <summary>
|
||
/// 显示输入对话框
|
||
/// </summary>
|
||
public static void ShowInputDialog(string title, Action<string> onConfirmAction,
|
||
string initialValue = "", TMP_InputField.ContentType contentType = TMP_InputField.ContentType.Standard)
|
||
{
|
||
if (!InstanceExists) return;
|
||
|
||
var inst = Instance;
|
||
if (inst.inputDialog == null) return;
|
||
|
||
inst.inputDialogInputField.contentType = contentType;
|
||
inst.inputDialog.SetActive(true);
|
||
inst.inputDialogTitle.text = title;
|
||
inst.inputDialogInputField.text = initialValue;
|
||
|
||
inst.inputDialogConfirmBtn.onClick.RemoveAllListeners();
|
||
inst.inputDialogConfirmBtn.onClick.AddListener(() =>
|
||
{
|
||
onConfirmAction?.Invoke(inst.inputDialogInputField.text);
|
||
CloseInputDialog();
|
||
});
|
||
|
||
inst.inputDialogCancelBtn.onClick.RemoveAllListeners();
|
||
inst.inputDialogCancelBtn.onClick.AddListener(CloseInputDialog);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭输入对话框
|
||
/// </summary>
|
||
public static void CloseInputDialog()
|
||
{
|
||
if (!InstanceExists) return;
|
||
|
||
var inst = Instance;
|
||
if (inst.inputDialog == null) return;
|
||
|
||
inst.inputDialog.SetActive(false);
|
||
inst.inputDialogInputField.text = "";
|
||
inst.inputDialogConfirmBtn.onClick.RemoveAllListeners();
|
||
inst.inputDialogCancelBtn.onClick.RemoveAllListeners();
|
||
}
|
||
#endregion
|
||
|
||
#region 公共API
|
||
/// <summary>
|
||
/// 显示调试工具
|
||
/// </summary>
|
||
public static void Show()
|
||
{
|
||
if (InstanceExists)
|
||
{
|
||
Instance.gameObject.SetActive(true);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 隐藏调试工具
|
||
/// </summary>
|
||
public static void Hide()
|
||
{
|
||
if (InstanceExists)
|
||
{
|
||
Instance.gameObject.SetActive(false);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换调试工具显示状态
|
||
/// </summary>
|
||
public static void Toggle()
|
||
{
|
||
if (InstanceExists)
|
||
{
|
||
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>
|
||
private void OnFloatingButtonClick()
|
||
{
|
||
// 检查是否刚拖动过
|
||
if (draggableComponent != null && draggableComponent.GetWasDragging())
|
||
{
|
||
// 如果刚拖动过,不打开窗口
|
||
Debug.Log("🚫 拖动操作,不打开窗口");
|
||
return;
|
||
}
|
||
|
||
// 否则打开窗口
|
||
OpenDebugWindow();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开调试窗口,隐藏悬浮按钮
|
||
/// </summary>
|
||
public void OpenDebugWindow()
|
||
{
|
||
if (floatingButton != null)
|
||
floatingButton.SetActive(false);
|
||
|
||
if (mainWindow != null)
|
||
mainWindow.gameObject.SetActive(true);
|
||
|
||
Debug.Log("🔼 调试窗口已打开");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示主窗口(初始化时调用)
|
||
/// </summary>
|
||
private void ShowMainWindow()
|
||
{
|
||
if (mainWindow != null)
|
||
mainWindow.gameObject.SetActive(false);
|
||
|
||
if (floatingButton != null)
|
||
floatingButton.SetActive(true);
|
||
}
|
||
#endregion
|
||
}
|
||
|
||
#region 自定义特性
|
||
/// <summary>
|
||
/// 调试按钮特性 - 标记方法为调试按钮
|
||
///
|
||
/// 使用说明:
|
||
/// 1. 在主项目中使用此特性时,请用条件编译包裹:
|
||
///
|
||
/// #if MEOWMENT_DEBUG_TOOL
|
||
/// [DebugButton("我的调试按钮")]
|
||
/// public static void MyDebugMethod()
|
||
/// {
|
||
/// Debug.Log("调试方法被执行");
|
||
/// }
|
||
/// #endif
|
||
///
|
||
/// 2. 这样当包被卸载时,代码不会报错,也不会影响主工程的正常运行
|
||
/// 3. MEOWMENT_DEBUG_TOOL 宏会在包安装时自动定义,卸载时自动移除
|
||
/// </summary>
|
||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||
public class DebugButtonAttribute : Attribute
|
||
{
|
||
public string DisplayName { get; set; }
|
||
public Color ButtonColor { get; set; }
|
||
|
||
public DebugButtonAttribute(string displayName = "", float r = 0.8f, float g = 0.8f, float b = 0.8f)
|
||
{
|
||
DisplayName = displayName;
|
||
ButtonColor = new Color(r, g, b, 1f);
|
||
}
|
||
}
|
||
#endregion
|
||
}
|