更新meowmentdebugtool 增加开关,数值功能,去掉工具栏功能

This commit is contained in:
zhang hongbo 2025-12-24 19:51:03 +08:00
parent 62f990f702
commit bdb24b05d1
12 changed files with 1220 additions and 542 deletions

View File

@ -5,6 +5,73 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)
版本号遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
## [0.3.7] - 2025-12-24
### Added
- **数值特性增强**
- `[DebugValue]` 特性新增 `defaultValue` 参数,用于设置初始默认值
- 参数顺序:`displayName`, `minValue`, `maxValue`, `defaultValue`, `r`, `g`, `b`
- 若未指定 `defaultValue`(或传入-1则自动使用 `minValue` 作为默认值
### Changed
- **模块命名优化**
- "自定义数值"页面重命名为"数值",简化用户界面
- 统一更新相关日志消息和UI文本
### Improved
- **用户体验提升**
- 数值页面的确认按钮点击后自动关闭主窗口(与自定义按钮保持一致)
- 优化操作流程,调整数值后自动返回游戏界面
## [0.3.6] - 2025-12-24
### Added
- **自定义复选框页面 (CheckBoxes)**
- 新增 `[DebugCheckBox]` 特性,用于创建开关功能
- 支持自定义显示名称和复选框颜色RGB参数
- 自动通过反射扫描并加载所有带该特性的静态方法
- 方法必须接收一个 `bool` 参数作为开关状态
- 支持状态保存,切换页面后保持开关状态
- 使用 ScrollView 支持大量复选框滚动显示
- 单行布局,每个复选框占据一行
- **自定义数值页面 (Values)**
- 新增 `[DebugValue]` 特性,用于创建整数值调整功能
- 支持自定义显示名称、最小值、最大值、颜色RGB参数
- 复合UI组件标题 + 滑块 + 输入框 + 确认按钮
- 滑块和输入框双向绑定,实时同步数值
- 数值自动限制在指定范围内(最小值-最大值)
- 方法必须接收一个 `int` 参数
- 支持状态保存,切换页面后保持当前值
- 使用 ScrollView 支持大量数值项滚动显示
- 两行布局:标题行 + 控制行滑块55% + 输入框17% + 按钮26%
### Changed
- 移除工具栏页面及相关功能(时间调整功能已不再需要)
- 优化UI布局尺寸
- 复选框项宽度800px高度80px
- 数值调整项宽度900px高度140px
- 改进标题可见性,采用两行布局设计
### Fixed
- 修复运行时UI生成时的 NullReferenceException 问题
- 修复 TMP_InputField 的 textComponent 必须为子对象的问题
- 修复非激活父对象下创建UI组件的问题
## [0.3.5] - 2025-12-22
- 增加四指隐藏Debugger显示效果
- 性能优化update代码优化
## [0.3.0] - 2025-12-22
### Added

View File

@ -5,15 +5,16 @@ namespace MeowmentDebugTool.Editor
{
/// <summary>
/// 自动添加 MEOWMENT_DEBUG_TOOL 宏定义
/// 注意:已禁用自动添加功能,如需启用请取消注释 [InitializeOnLoad] 特性
/// </summary>
[InitializeOnLoad]
// [InitializeOnLoad] // 已注释,不再自动添加宏定义
public static class DefineSymbolsManager
{
private const string DEFINE_SYMBOL = "MEOWMENT_DEBUG_TOOL";
static DefineSymbolsManager()
{
AddDefineSymbol();
// AddDefineSymbol(); // 已注释,不再自动添加
}
private static void AddDefineSymbol()

View File

@ -1,547 +1,85 @@
# MeowMent Debug Tool(喵刻调试工具)
# MeowMent Debug Tool
一个强大的 Unity 运行时调试工具,提供多标签页调试面板、控制台日志、可拖拽悬浮按钮、系统信息查看以及自定义调试按钮等功能
Unity 运行时调试工具。
**版本:** 0.3.0
**适用平台:** 全平台Windows、Android、iOS等
**Unity版本** 2021.3 及以上
---
## 📋 目录
## 目录
- [功能特性](#功能特性)
- [给策划使用 - 基础功能](#给策划使用---基础功能)
- [给客户端使用 - 初始化配置](#给客户端使用---初始化配置)
- [给后端使用 - 添加自定义按钮](#给后端使用---添加自定义按钮)
- [常见问题](#常见问题)
- [给策划](#给策划)
- [给客户端](#给客户端)
- [给服务端](#给服务端)
---
## 给策划
## 🎯 功能特性
点击屏幕左侧蓝色浮窗打开调试窗口。
### 核心功能模块
- **参数查看**:实时查看设备信息、系统信息,支持一键复制
- **自定义按钮**:通过特性自动生成调试按钮,无需手动添加
- **工具栏**:时间调整工具、截图隐藏功能
- **控制台**实时捕获Unity日志支持过滤、搜索、查看堆栈
- **设置**:运行时修改分辨率
**参数查看** - 查看设备信息、系统信息,可复制
**自定义按钮** - 执行调试功能(清空数据、添加道具等)
**开关** - 功能开关控制(开启/关闭某些功能)
**数值** - 数值调整(通过滑块和输入框调整整数值)
**控制台** - 查看日志、过滤、堆栈信息
**设置** - 调整分辨率
### UI特性
- 可拖拽悬浮按钮,自动吸附边缘
- 多标签页设计,清晰分类
- Canvas层级30000始终在最上层
- 支持自定义TextMeshPro字体
四指点击屏幕是隐藏和现实Debugger工具
---
## 给客户端
## 👥 给策划使用 - 基础功能
### 1. 如何打开调试工具
游戏运行后,屏幕左侧会显示一个**蓝色圆形浮窗按钮**
- **点击浮窗** → 打开调试主窗口
- **拖动浮窗** → 改变位置(会自动吸附到屏幕边缘)
- **点击左上角X** → 关闭主窗口,回到浮窗状态
### 2. 功能页面说明
调试窗口包含5个标签页
#### 📊 参数查看
- **设备信息**:设备型号、系统版本、处理器、内存等
- **系统信息**Unity版本、图形API、显卡信息等
- **复制按钮**点击可复制信息到剪贴板方便粘贴到bug报告
#### 🎮 自定义按钮
- 显示后端程序员添加的调试功能按钮
- 点击按钮即可执行对应功能(如清理存档、添加道具等)
- 不同按钮有不同颜色,方便区分
**常见按钮示例:**
- "清空玩家数据" - 重置游戏进度
- "添加金币" - 增加游戏货币
- "解锁所有关卡" - 开启所有内容
- "重置今日任务" - 刷新每日任务
#### 🛠️ 工具栏
- **时间调整**:模拟时间流逝(用于测试时间相关功能)
- **暂时隐藏(5秒)**截图前点击此按钮调试工具会隐藏5秒方便截取干净的游戏画面
**使用场景:**
1. 需要截图游戏画面时
2. 点击"暂时隐藏(5秒)"
3. 调试工具消失,快速截图
4. 5秒后自动恢复继续调试
#### 📝 控制台
实时显示游戏日志,帮助定位问题:
- **Info白色**:普通信息日志
- **Warning黄色**:警告信息
- **Error红色**:错误信息
- **Fatal深红**:严重错误/异常
**功能按钮:**
- **清空**:清除所有日志
- **锁定滚动**:勾选后自动滚动到最新日志
- **过滤器**点击Info/Warning/Error切换显示对应类型日志
**查看详情:**
- 点击任意日志条目 → 下方显示完整堆栈信息
- 方便向程序员反馈详细错误
#### ⚙️ 设置
- **分辨率调整**:修改游戏窗口大小(仅编辑器/Windows有效
- **当前分辨率显示**:显示当前屏幕分辨率
### 3. 日常测试工作流
**测试新功能:**
1. 打开调试工具
2. 切换到"自定义按钮"页面
3. 点击相关测试按钮(如"开启活动"
4. 观察游戏效果
5. 如有问题,切换到"控制台"查看错误
**截图报bug**
1. 复现问题
2. 点击"工具栏" → "暂时隐藏(5秒)"
3. 截取游戏画面
4. 切换回"控制台"截取错误日志
5. 一起提交给程序
**收集设备信息:**
1. 切换到"参数查看"
2. 点击"复制设备信息"或"复制系统信息"
3. 粘贴到bug报告中
---
## 💻 给客户端使用 - 初始化配置
### 1. 基础初始化
在游戏启动时(通常在启动场景的初始化脚本中)调用:
在游戏启动时初始化:
```csharp
using UnityEngine;
using MeowmentDebugTool;
using TMPro;
public class GameInitializer : MonoBehaviour
{
[SerializeField] private TMP_FontAsset customFont; // 拖入自定义字体
void Start()
{
// 初始化调试工具
UniversalDebugTool.Init();
// 设置自定义字体(可选)
if (customFont != null)
{
UniversalDebugTool.SetSDFFont(customFont);
}
}
}
```
### 2. API说明
#### `UniversalDebugTool.Init()`
初始化调试工具,自动创建:
- Canvas排序顺序30000
- 主调试窗口
- 悬浮按钮
- EventSystem如果没有
**注意:**
- 只需调用一次
- 工具会自动DontDestroyOnLoad场景切换不销毁
- 未调用Init()前不显示任何UI
#### `UniversalDebugTool.SetSDFFont(TMP_FontAsset fontAsset)`
设置所有UI文本的字体
- 包括已创建的UI和后续创建的按钮
- 支持中文、特殊字符
- 建议使用支持中文的SDF字体
**字体推荐:**
- 思源黑体 SDF
- 阿里巴巴普惠体 SDF
- 或项目现有的中文字体
### 3. 条件编译(推荐)
为了确保打包时不包含调试代码:
```csharp
void Start()
{
#if MEOWMENT_DEBUG_TOOL
UniversalDebugTool.Init();
UniversalDebugTool.SetSDFFont(customFont);
UniversalDebugTool.SetSDFFont(customFont); // 可选,设置中文字体
#endif
}
```
**说明:**
- `MEOWMENT_DEBUG_TOOL` 宏在包安装时自动定义
- 移除包时自动移除,不影响项目编译
- 正式包不会包含调试工具代码
## 给服务端
### 4. 完整示例
`[DebugButton]` 特性标记静态方法自动生成按钮:
```csharp
using UnityEngine;
using MeowmentDebugTool;
using TMPro;
public class Test : MonoBehaviour
public static class DebugFunctions
{
public TMP_FontAsset fontAsset;
void Start()
{
#if MEOWMENT_DEBUG_TOOL
// 初始化调试工具
UniversalDebugTool.Init();
// 设置字体
if (fontAsset != null)
{
UniversalDebugTool.SetSDFFont(fontAsset);
}
#endif
}
}
```
---
## 🔧 给后端使用 - 添加自定义按钮
### 1. 基础用法
使用 `[DebugButton]` 特性标记静态方法,调试工具会自动生成按钮:
```csharp
using UnityEngine;
public static class PlayerDebugFunctions
{
[DebugButton("清空玩家数据")]
private static void ClearPlayerData()
[DebugButton("清空数据")]
private static void ClearData()
{
PlayerPrefs.DeleteAll();
Debug.Log("玩家数据已清空");
}
[DebugButton("添加1000金币")]
// 设置按钮颜色 (r, g, b)
[DebugButton("添加金币", 0.2f, 0.8f, 0.2f)]
private static void AddCoins()
{
PlayerData.Coins += 1000;
Debug.Log($"当前金币:{PlayerData.Coins}");
}
// 复选框开关方法必须接收bool参数
[DebugCheckBox("无敌模式", 1f, 0.5f, 0f)]
private static void ToggleGodMode(bool isOn)
{
Player.IsInvincible = isOn;
}
// 数值调整方法必须接收int参数
// 参数:显示名称, 最小值, 最大值, 默认值, r, g, b
[DebugValue("玩家等级", 1, 100, 50, 0f, 0.8f, 1f)]
private static void SetPlayerLevel(int level)
{
Player.Level = level;
}
}
```
**运行效果:**
- 在"自定义按钮"页面会出现两个按钮
- 点击按钮执行对应方法
## 常见问题
### 2. 特性参数详解
**字体显示方块?** 调用 `SetSDFFont()` 设置中文字体
**按钮不显示?** 检查方法是否为 `static` 且有 `[DebugButton]` 特性
**如何移除?** 删除 Packages 下的工具包文件夹
```csharp
[DebugButton(string displayName = "", float r = 0.8f, float g = 0.8f, float b = 0.8f)]
```
#### 参数说明:
- **displayName**:按钮显示文字(不填则使用方法名)
- **r, g, b**按钮背景颜色RGB值范围0-1
#### 示例:不同颜色按钮
```csharp
public static class GameDebugFunctions
{
// 绿色按钮 - 安全操作
[DebugButton("保存游戏", 0.2f, 0.6f, 0.2f)]
private static void SaveGame()
{
GameManager.Save();
}
// 红色按钮 - 危险操作
[DebugButton("删除存档", 0.9f, 0.2f, 0.2f)]
private static void DeleteSave()
{
GameManager.DeleteSave();
}
// 蓝色按钮 - 功能测试
[DebugButton("跳到第10关", 0.2f, 0.5f, 0.9f)]
private static void JumpToLevel10()
{
LevelManager.LoadLevel(10);
}
// 默认颜色(灰色)
[DebugButton("打印游戏状态")]
private static void PrintGameState()
{
Debug.Log($"Level: {GameManager.CurrentLevel}");
}
}
```
### 3. 使用输入对话框
需要用户输入参数时,使用 `ShowInputDialog`
```csharp
using TMPro;
public static class AdvancedDebugFunctions
{
[DebugButton("设置玩家等级")]
private static void SetPlayerLevel()
{
UniversalDebugTool.ShowInputDialog(
"输入等级",
onConfirmAction: text =>
{
if (int.TryParse(text, out int level))
{
PlayerData.Level = level;
Debug.Log($"等级已设置为:{level}");
}
else
{
Debug.LogWarning("请输入有效数字");
}
},
initialValue: "1",
contentType: TMP_InputField.ContentType.IntegerNumber
);
}
[DebugButton("设置玩家名称")]
private static void SetPlayerName()
{
UniversalDebugTool.ShowInputDialog(
"输入名称",
onConfirmAction: name =>
{
PlayerData.Name = name;
Debug.Log($"名称已设置为:{name}");
},
initialValue: PlayerData.Name,
contentType: TMP_InputField.ContentType.Standard
);
}
}
```
### 4. 条件编译(推荐)
为了确保正式版不包含调试代码:
```csharp
public static class ItemDebugFunctions
{
#if MEOWMENT_DEBUG_TOOL
[DebugButton("添加道具")]
private static void AddItem()
{
ItemManager.AddItem(1001, 10);
}
#endif
}
```
### 5. 高级技巧
#### 技巧1分类管理
按功能模块创建不同的类:
```csharp
// 玩家相关
public static class PlayerDebug { ... }
// 关卡相关
public static class LevelDebug { ... }
// 道具相关
public static class ItemDebug { ... }
```
#### 技巧2快速测试流程
```csharp
[DebugButton("快速进入战斗")]
private static void QuickEnterBattle()
{
// 1. 设置测试数据
PlayerData.Level = 10;
PlayerData.Coins = 9999;
// 2. 解锁功能
FeatureManager.UnlockAll();
// 3. 跳转场景
SceneManager.LoadScene("Battle");
}
```
#### 技巧3开发辅助
```csharp
[DebugButton("开启所有调试选项")]
private static void EnableAllDebug()
{
GameConfig.ShowFPS = true;
GameConfig.GodMode = true;
GameConfig.UnlimitedEnergy = true;
Debug.Log("所有调试选项已开启");
}
```
### 6. 实战示例
```csharp
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;
public static class SampleDebugFunctions
{
#region 玩家数据
[DebugButton("重置游戏", 0.9f, 0.3f, 0.3f)]
private static void ResetGame()
{
PlayerPrefs.DeleteAll();
SceneManager.LoadScene(0);
Debug.Log("游戏已重置");
}
[DebugButton("满级满资源", 0.2f, 0.8f, 0.2f)]
private static void MaxEverything()
{
PlayerData.Level = 99;
PlayerData.Coins = 999999;
PlayerData.Gems = 99999;
Debug.Log("已设置为满级满资源");
}
#endregion
#region 关卡测试
[DebugButton("跳关", 0.3f, 0.6f, 0.9f)]
private static void JumpToLevel()
{
UniversalDebugTool.ShowInputDialog(
"输入关卡号",
text =>
{
if (int.TryParse(text, out int level))
{
LevelManager.LoadLevel(level);
}
},
"1",
TMP_InputField.ContentType.IntegerNumber
);
}
[DebugButton("解锁所有关卡", 0.2f, 0.8f, 0.8f)]
private static void UnlockAllLevels()
{
for (int i = 0; i < 100; i++)
{
LevelManager.UnlockLevel(i);
}
Debug.Log("已解锁所有关卡");
}
#endregion
#region 系统测试
[DebugButton("清理未使用资源", 0.9f, 0.5f, 0.3f)]
private static void UnloadUnusedAssets()
{
Resources.UnloadUnusedAssets();
Debug.Log("已清理未使用的资源");
}
[DebugButton("触发GC")]
private static void ForceGC()
{
System.GC.Collect();
Debug.Log("已触发垃圾回收");
}
#endregion
}
```
---
## ❓ 常见问题
### Q1: 字体显示方块/乱码怎么办?
**A:** 调用 `UniversalDebugTool.SetSDFFont()` 设置支持中文的SDF字体。
### Q2: 自定义按钮不显示?
**A:** 检查:
- 方法是否为 `static`
- 是否添加了 `[DebugButton]` 特性
- 是否在 `#if MEOWMENT_DEBUG_TOOL`
### Q3: 第二次点击"暂时隐藏"无效?
**A:** 等待上一次隐藏结束,或查看控制台是否有"已经在隐藏状态中"的警告。
### Q4: 如何在正式版移除调试工具?
**A:** 删除Packages目录下的工具包文件夹即可`#if MEOWMENT_DEBUG_TOOL` 包裹的代码会自动失效。
### Q5: 控制台日志太多怎么办?
**A:**
- 点击"清空"按钮清除日志
- 使用过滤器只显示Error/Warning
- 工具默认只保留最新100条日志
### Q6: 可以在多个场景使用吗?
**A:** 可以工具使用DontDestroyOnLoad场景切换不会销毁。
---
## 📝 更新日志
### [0.3.0] - 2025-12-22
- ✨ 新增控制台模块实时显示Unity日志
- ✨ 新增暂时隐藏功能,方便截图
- 🔨 重构代码架构,模块化设计
- 🐛 修复多个UI布局问题
### [0.2.2] - 2025-12-19
- 🔨 默认显示浮窗而非主窗口
- 🐛 优化初始化逻辑
详见 [CHANGELOG.md](CHANGELOG.md)
---
## 📦 依赖
- Unity 2021.3+
- TextMesh Pro (com.unity.textmeshpro)
- Unity UI (com.unity.ugui)
---
## 📧 联系方式
如有问题或建议,请联系开发团队。
---
**祝调试顺利!** 🐱

View File

@ -53,7 +53,7 @@ namespace MeowmentDebugTool
public string GetModuleName()
{
return "自定义按钮";
return "按钮";
}
#endregion

View File

@ -0,0 +1,187 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace MeowmentDebugTool
{
/// <summary>
/// 自定义复选框模块 - 通过反射加载标记为DebugCheckBox的方法
/// </summary>
public class CustomCheckBoxesModule : IDebugModule
{
#region
private GameObject customCheckBoxesPage;
private RectTransform checkBoxContainer;
private GameObject checkBoxPrefab;
private ScrollRect checkBoxesScrollRect;
// 保存的SDF字体资源
private TMP_FontAsset savedFontAsset = null;
// 保存每个复选框的状态
private Dictionary<string, bool> checkBoxStates = new Dictionary<string, bool>();
#endregion
#region
public CustomCheckBoxesModule(GameObject page, RectTransform container, GameObject prefab,
ScrollRect scrollRect)
{
customCheckBoxesPage = page;
checkBoxContainer = container;
checkBoxPrefab = prefab;
checkBoxesScrollRect = scrollRect;
}
#endregion
#region IDebugModule
public void Initialize()
{
Debug.Log("[CustomCheckBoxesModule] 初始化自定义复选框模块...");
LoadCustomCheckBoxes();
}
public GameObject GetPage()
{
return customCheckBoxesPage;
}
public string GetModuleName()
{
return "开关";
}
#endregion
#region
/// <summary>
/// 设置SDF字体
/// </summary>
public void SetSDFFont(TMP_FontAsset fontAsset)
{
savedFontAsset = fontAsset;
}
/// <summary>
/// 重新加载自定义复选框
/// </summary>
public void ReloadCustomCheckBoxes()
{
LoadCustomCheckBoxes();
}
#endregion
#region
/// <summary>
/// 加载所有自定义复选框(使用反射)
/// </summary>
private void LoadCustomCheckBoxes()
{
if (checkBoxContainer == null || checkBoxPrefab == null)
{
Debug.LogWarning("[CustomCheckBoxesModule] 复选框容器或复选框预制件未设置");
return;
}
// 清空现有复选框
foreach (Transform child in checkBoxContainer)
{
UnityEngine.Object.Destroy(child.gameObject);
}
// 查找所有标记为DebugCheckBox的方法
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
try
{
var types = assembly.GetTypes();
foreach (var type in types)
{
var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var method in methods)
{
var attribute = method.GetCustomAttribute<DebugCheckBoxAttribute>();
if (attribute != null)
{
CreateCustomCheckBox(method, attribute);
}
}
}
}
catch (Exception e)
{
// 某些程序集可能无法访问,跳过
Debug.LogWarning($"[CustomCheckBoxesModule] 无法访问程序集 {assembly.FullName}: {e.Message}");
}
}
}
private void CreateCustomCheckBox(MethodInfo method, DebugCheckBoxAttribute attribute)
{
// 验证方法签名:必须接受一个 bool 参数
var parameters = method.GetParameters();
if (parameters.Length != 1 || parameters[0].ParameterType != typeof(bool))
{
Debug.LogWarning($"[CustomCheckBoxesModule] 方法 {method.Name} 必须接受一个 bool 参数");
return;
}
GameObject checkBoxObj = UnityEngine.Object.Instantiate(checkBoxPrefab, checkBoxContainer);
Toggle toggle = checkBoxObj.GetComponent<Toggle>();
TMP_Text labelText = checkBoxObj.GetComponentInChildren<TMP_Text>();
Image backgroundImage = checkBoxObj.GetComponent<Image>();
// 设置复选框文本
if (labelText != null)
{
labelText.text = string.IsNullOrEmpty(attribute.DisplayName) ? method.Name : attribute.DisplayName;
// 应用保存的字体
if (savedFontAsset != null)
{
labelText.font = savedFontAsset;
}
}
// 设置背景颜色
if (backgroundImage != null && attribute.CheckBoxColor != default(Color))
{
backgroundImage.color = attribute.CheckBoxColor;
}
// 获取或初始化状态(默认为 false
string methodKey = $"{method.DeclaringType?.FullName}.{method.Name}";
if (!checkBoxStates.ContainsKey(methodKey))
{
checkBoxStates[methodKey] = false;
}
// 设置初始状态
if (toggle != null)
{
toggle.isOn = checkBoxStates[methodKey];
}
// 设置复选框状态改变事件
toggle.onValueChanged.AddListener((bool isOn) =>
{
try
{
// 保存状态
checkBoxStates[methodKey] = isOn;
// 调用方法,传递状态
method.Invoke(null, new object[] { isOn });
Debug.Log($"[CustomCheckBoxesModule] 执行调试方法: {method.Name}, 状态: {isOn}");
}
catch (Exception e)
{
Debug.LogError($"[CustomCheckBoxesModule] 执行调试方法 {method.Name} 时出错: {e.Message}");
}
});
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8d7e036c6d4b2d4083addffa8c87eb8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,274 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace MeowmentDebugTool
{
/// <summary>
/// 自定义数值模块 - 通过反射加载标记为DebugValue的方法
/// </summary>
public class CustomValuesModule : IDebugModule
{
#region
private GameObject customValuesPage;
private RectTransform valueContainer;
private GameObject valuePrefab;
private ScrollRect valuesScrollRect;
// 保存的SDF字体资源
private TMP_FontAsset savedFontAsset = null;
// 保存每个数值的当前值
private Dictionary<string, int> valueStates = new Dictionary<string, int>();
// 关闭窗口回调
private Action onCloseWindowCallback;
#endregion
#region
public CustomValuesModule(GameObject page, RectTransform container, GameObject prefab,
ScrollRect scrollRect, Action closeWindowCallback)
{
customValuesPage = page;
valueContainer = container;
valuePrefab = prefab;
valuesScrollRect = scrollRect;
onCloseWindowCallback = closeWindowCallback;
}
#endregion
#region IDebugModule
public void Initialize()
{
Debug.Log("[CustomValuesModule] 初始化自定义数值模块...");
LoadCustomValues();
}
public GameObject GetPage()
{
return customValuesPage;
}
public string GetModuleName()
{
return "数值";
}
#endregion
#region
/// <summary>
/// 设置SDF字体
/// </summary>
public void SetSDFFont(TMP_FontAsset fontAsset)
{
savedFontAsset = fontAsset;
}
/// <summary>
/// 重新加载自定义数值
/// </summary>
public void ReloadCustomValues()
{
LoadCustomValues();
}
#endregion
#region
/// <summary>
/// 加载所有自定义数值(使用反射)
/// </summary>
private void LoadCustomValues()
{
if (valueContainer == null || valuePrefab == null)
{
Debug.LogWarning("[CustomValuesModule] 数值容器或数值预制件未设置");
return;
}
// 清空现有数值
foreach (Transform child in valueContainer)
{
UnityEngine.Object.Destroy(child.gameObject);
}
// 查找所有标记为DebugValue的方法
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
try
{
var types = assembly.GetTypes();
foreach (var type in types)
{
var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var method in methods)
{
var attribute = method.GetCustomAttribute<DebugValueAttribute>();
if (attribute != null)
{
CreateCustomValue(method, attribute);
}
}
}
}
catch (Exception e)
{
// 某些程序集可能无法访问,跳过
Debug.LogWarning($"[CustomValuesModule] 无法访问程序集 {assembly.FullName}: {e.Message}");
}
}
}
private void CreateCustomValue(MethodInfo method, DebugValueAttribute attribute)
{
// 验证方法签名:必须接受一个 int 参数
var parameters = method.GetParameters();
if (parameters.Length != 1 || parameters[0].ParameterType != typeof(int))
{
Debug.LogWarning($"[CustomValuesModule] 方法 {method.Name} 必须接受一个 int 参数");
return;
}
GameObject valueObj = UnityEngine.Object.Instantiate(valuePrefab, valueContainer);
// 获取组件
TMP_Text labelText = valueObj.transform.Find("Label")?.GetComponent<TMP_Text>();
Slider slider = valueObj.transform.Find("Slider")?.GetComponent<Slider>();
TMP_InputField inputField = valueObj.transform.Find("InputField")?.GetComponent<TMP_InputField>();
Button confirmButton = valueObj.transform.Find("ConfirmButton")?.GetComponent<Button>();
Image backgroundImage = valueObj.GetComponent<Image>();
// 设置标签文本
if (labelText != null)
{
labelText.text = string.IsNullOrEmpty(attribute.DisplayName) ? method.Name : attribute.DisplayName;
// 应用保存的字体
if (savedFontAsset != null)
{
labelText.font = savedFontAsset;
}
}
// 设置背景颜色
if (backgroundImage != null && attribute.ValueColor != default(Color))
{
backgroundImage.color = attribute.ValueColor;
}
// 获取或初始化状态(使用默认值)
string methodKey = $"{method.DeclaringType?.FullName}.{method.Name}";
if (!valueStates.ContainsKey(methodKey))
{
valueStates[methodKey] = attribute.DefaultValue;
}
int currentValue = valueStates[methodKey];
// 设置滑动条
if (slider != null)
{
slider.minValue = attribute.MinValue;
slider.maxValue = attribute.MaxValue;
slider.wholeNumbers = true;
slider.value = currentValue;
// 滑动条值改变时更新输入框和保存状态
slider.onValueChanged.AddListener((float value) =>
{
int intValue = Mathf.RoundToInt(value);
if (inputField != null)
{
inputField.text = intValue.ToString();
}
valueStates[methodKey] = intValue;
});
}
// 设置输入框
if (inputField != null)
{
inputField.text = currentValue.ToString();
inputField.contentType = TMP_InputField.ContentType.IntegerNumber;
// 应用保存的字体
if (savedFontAsset != null)
{
inputField.textComponent.font = savedFontAsset;
}
// 输入框值改变时更新滑动条和保存状态
inputField.onValueChanged.AddListener((string text) =>
{
if (int.TryParse(text, out int intValue))
{
// 限制范围
intValue = Mathf.Clamp(intValue, attribute.MinValue, attribute.MaxValue);
if (slider != null)
{
slider.value = intValue;
}
valueStates[methodKey] = intValue;
}
});
// 输入框失去焦点时确保值在范围内
inputField.onEndEdit.AddListener((string text) =>
{
if (int.TryParse(text, out int intValue))
{
intValue = Mathf.Clamp(intValue, attribute.MinValue, attribute.MaxValue);
inputField.text = intValue.ToString();
valueStates[methodKey] = intValue;
if (slider != null)
{
slider.value = intValue;
}
}
else
{
// 如果输入无效,恢复为当前保存的值
inputField.text = valueStates[methodKey].ToString();
}
});
}
// 设置确认按钮
if (confirmButton != null)
{
// 应用保存的字体到按钮文本
if (savedFontAsset != null)
{
TMP_Text btnText = confirmButton.GetComponentInChildren<TMP_Text>();
if (btnText != null)
{
btnText.font = savedFontAsset;
}
}
confirmButton.onClick.AddListener(() =>
{
try
{
int finalValue = valueStates[methodKey];
// 调用方法,传递数值
method.Invoke(null, new object[] { finalValue });
Debug.Log($"[CustomValuesModule] 执行调试方法: {method.Name}, 数值: {finalValue}");
// 点击确认按钮后关闭主窗口
onCloseWindowCallback?.Invoke(); }
catch (Exception e)
{
Debug.LogError($"[CustomValuesModule] 执行调试方法 {method.Name} 时出错: {e.Message}");
}
});
}
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6e9a59724c256d243a97df1857fdc89e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -51,7 +51,8 @@ namespace MeowmentDebugTool
GameObject parametersPage = CreateParametersPage(contentContainer.transform);
GameObject customButtonsPage = CreateCustomButtonsPage(contentContainer.transform);
GameObject toolbarPage = CreateToolbarPage(contentContainer.transform);
GameObject customCheckBoxesPage = CreateCustomCheckBoxesPage(contentContainer.transform);
GameObject customValuesPage = CreateCustomValuesPage(contentContainer.transform);
GameObject settingsPage = CreateSettingsPage(contentContainer.transform);
GameObject consolePage = CreateConsolePage(contentContainer.transform);
@ -60,7 +61,7 @@ namespace MeowmentDebugTool
// 设置引用
SetupReferences(tool, canvas, mainWindow, floatingButton,
parametersPage, customButtonsPage, toolbarPage, settingsPage, consolePage, inputDialog);
parametersPage, customButtonsPage, customCheckBoxesPage, customValuesPage, settingsPage, consolePage, inputDialog);
return tool;
}
@ -253,6 +254,92 @@ namespace MeowmentDebugTool
return page;
}
private static GameObject CreateCustomCheckBoxesPage(Transform parent)
{
GameObject page = new GameObject("CustomCheckBoxesPage");
page.transform.SetParent(parent, false);
RectTransform rect = page.AddComponent<RectTransform>();
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.sizeDelta = Vector2.zero;
ScrollRect scroll = page.AddComponent<ScrollRect>();
GameObject viewport = new GameObject("Viewport");
viewport.transform.SetParent(page.transform, false);
RectTransform vpRect = viewport.AddComponent<RectTransform>();
vpRect.anchorMin = Vector2.zero;
vpRect.anchorMax = Vector2.one;
vpRect.sizeDelta = Vector2.zero;
viewport.AddComponent<Mask>().showMaskGraphic = false;
GameObject checkBoxContainer = new GameObject("CheckBoxContainer");
checkBoxContainer.transform.SetParent(viewport.transform, false);
RectTransform containerRect = checkBoxContainer.AddComponent<RectTransform>();
containerRect.anchorMin = new Vector2(0, 1);
containerRect.anchorMax = new Vector2(1, 1);
containerRect.pivot = new Vector2(0.5f, 1);
// 使用垂直布局,一行一个复选框
VerticalLayoutGroup vertLayout = checkBoxContainer.AddComponent<VerticalLayoutGroup>();
vertLayout.spacing = 15;
vertLayout.padding = new RectOffset(20, 20, 20, 20);
vertLayout.childControlWidth = true;
vertLayout.childControlHeight = false;
vertLayout.childForceExpandWidth = true;
ContentSizeFitter fitter = checkBoxContainer.AddComponent<ContentSizeFitter>();
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
scroll.viewport = vpRect;
scroll.content = containerRect;
return page;
}
private static GameObject CreateCustomValuesPage(Transform parent)
{
GameObject page = new GameObject("CustomValuesPage");
page.transform.SetParent(parent, false);
RectTransform rect = page.AddComponent<RectTransform>();
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.sizeDelta = Vector2.zero;
ScrollRect scroll = page.AddComponent<ScrollRect>();
GameObject viewport = new GameObject("Viewport");
viewport.transform.SetParent(page.transform, false);
RectTransform vpRect = viewport.AddComponent<RectTransform>();
vpRect.anchorMin = Vector2.zero;
vpRect.anchorMax = Vector2.one;
vpRect.sizeDelta = Vector2.zero;
viewport.AddComponent<Mask>().showMaskGraphic = false;
GameObject valueContainer = new GameObject("ValueContainer");
valueContainer.transform.SetParent(viewport.transform, false);
RectTransform containerRect = valueContainer.AddComponent<RectTransform>();
containerRect.anchorMin = new Vector2(0, 1);
containerRect.anchorMax = new Vector2(1, 1);
containerRect.pivot = new Vector2(0.5f, 1);
// 使用垂直布局,一行一个数值项
VerticalLayoutGroup vertLayout = valueContainer.AddComponent<VerticalLayoutGroup>();
vertLayout.spacing = 20;
vertLayout.padding = new RectOffset(20, 20, 20, 20);
vertLayout.childControlWidth = true;
vertLayout.childControlHeight = false;
vertLayout.childForceExpandWidth = true;
ContentSizeFitter fitter = valueContainer.AddComponent<ContentSizeFitter>();
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
scroll.viewport = vpRect;
scroll.content = containerRect;
return page;
}
private static GameObject CreateToolbarPage(Transform parent)
{
GameObject page = new GameObject("ToolbarPage");
@ -1042,6 +1129,230 @@ namespace MeowmentDebugTool
return btn;
}
private static GameObject CreateCustomCheckBoxPrefab()
{
// 使用同一个临时父对象
GameObject tempParent = GameObject.Find("_TempPrefabHolder");
if (tempParent == null)
{
tempParent = new GameObject("_TempPrefabHolder");
tempParent.SetActive(false);
Object.DontDestroyOnLoad(tempParent);
}
GameObject toggleObj = new GameObject("CustomCheckBox", typeof(RectTransform));
toggleObj.transform.SetParent(tempParent.transform, false);
RectTransform rect = toggleObj.GetComponent<RectTransform>();
rect.sizeDelta = new Vector2(800, 80);
// 添加背景图片
Image bg = toggleObj.AddComponent<Image>();
bg.color = new Color(0.15f, 0.15f, 0.15f, 1f);
// 创建复选框背景(左侧)
GameObject checkboxBg = new GameObject("Background");
checkboxBg.transform.SetParent(toggleObj.transform, false);
RectTransform checkboxBgRect = checkboxBg.AddComponent<RectTransform>();
checkboxBgRect.anchorMin = new Vector2(0, 0.5f);
checkboxBgRect.anchorMax = new Vector2(0, 0.5f);
checkboxBgRect.pivot = new Vector2(0, 0.5f);
checkboxBgRect.anchoredPosition = new Vector2(20, 0);
checkboxBgRect.sizeDelta = new Vector2(60, 60);
Image checkboxBgImage = checkboxBg.AddComponent<Image>();
checkboxBgImage.color = new Color(0.8f, 0.8f, 0.8f, 1f);
// 创建勾选标记
GameObject checkmark = new GameObject("Checkmark");
checkmark.transform.SetParent(checkboxBg.transform, false);
RectTransform checkmarkRect = checkmark.AddComponent<RectTransform>();
checkmarkRect.anchorMin = Vector2.zero;
checkmarkRect.anchorMax = Vector2.one;
checkmarkRect.sizeDelta = Vector2.zero;
Image checkmarkImage = checkmark.AddComponent<Image>();
checkmarkImage.color = new Color(0.2f, 0.8f, 0.2f, 1f);
// 创建文本标签(右侧)
GameObject label = new GameObject("Label");
label.transform.SetParent(toggleObj.transform, false);
RectTransform labelRect = label.AddComponent<RectTransform>();
labelRect.anchorMin = new Vector2(0, 0);
labelRect.anchorMax = new Vector2(1, 1);
labelRect.pivot = new Vector2(0, 0.5f);
labelRect.offsetMin = new Vector2(100, 0);
labelRect.offsetMax = new Vector2(-20, 0);
TextMeshProUGUI labelText = label.AddComponent<TextMeshProUGUI>();
labelText.text = "复选框";
labelText.fontSize = 32;
labelText.alignment = TextAlignmentOptions.Left;
labelText.color = Color.white;
// 添加Toggle组件
Toggle toggle = toggleObj.AddComponent<Toggle>();
toggle.targetGraphic = checkboxBgImage;
toggle.graphic = checkmarkImage;
toggle.isOn = false;
return toggleObj;
}
private static GameObject CreateCustomValuePrefab()
{
// 使用同一个临时父对象
GameObject tempParent = GameObject.Find("_TempPrefabHolder");
if (tempParent == null)
{
tempParent = new GameObject("_TempPrefabHolder");
tempParent.SetActive(false);
Object.DontDestroyOnLoad(tempParent);
}
GameObject valueObj = new GameObject("CustomValue", typeof(RectTransform));
valueObj.transform.SetParent(tempParent.transform, false);
RectTransform rect = valueObj.GetComponent<RectTransform>();
rect.sizeDelta = new Vector2(900, 140);
// 添加背景图片
Image bg = valueObj.AddComponent<Image>();
bg.color = new Color(0.15f, 0.15f, 0.15f, 1f);
// 创建标签文本(顶部独占一行)
GameObject label = new GameObject("Label");
label.transform.SetParent(valueObj.transform, false);
RectTransform labelRect = label.AddComponent<RectTransform>();
labelRect.anchorMin = new Vector2(0, 1);
labelRect.anchorMax = new Vector2(1, 1);
labelRect.pivot = new Vector2(0, 1);
labelRect.anchoredPosition = new Vector2(20, -10);
labelRect.sizeDelta = new Vector2(-40, 35);
TextMeshProUGUI labelText = label.AddComponent<TextMeshProUGUI>();
labelText.text = "数值名称";
labelText.fontSize = 28;
labelText.alignment = TextAlignmentOptions.Left;
labelText.color = Color.white;
// 创建滑动条标题下方占据左侧60%
GameObject sliderObj = new GameObject("Slider");
sliderObj.transform.SetParent(valueObj.transform, false);
RectTransform sliderRect = sliderObj.AddComponent<RectTransform>();
sliderRect.anchorMin = new Vector2(0, 0);
sliderRect.anchorMax = new Vector2(0.55f, 0);
sliderRect.pivot = new Vector2(0, 0);
sliderRect.anchoredPosition = new Vector2(20, 15);
sliderRect.sizeDelta = new Vector2(-20, 50);
Slider slider = sliderObj.AddComponent<Slider>();
// 滑动条背景
GameObject sliderBg = new GameObject("Background");
sliderBg.transform.SetParent(sliderObj.transform, false);
RectTransform sliderBgRect = sliderBg.AddComponent<RectTransform>();
sliderBgRect.anchorMin = Vector2.zero;
sliderBgRect.anchorMax = Vector2.one;
sliderBgRect.sizeDelta = Vector2.zero;
Image sliderBgImage = sliderBg.AddComponent<Image>();
sliderBgImage.color = new Color(0.3f, 0.3f, 0.3f, 1f);
// 滑动条填充区域
GameObject fillArea = new GameObject("Fill Area");
fillArea.transform.SetParent(sliderObj.transform, false);
RectTransform fillAreaRect = fillArea.AddComponent<RectTransform>();
fillAreaRect.anchorMin = Vector2.zero;
fillAreaRect.anchorMax = Vector2.one;
fillAreaRect.sizeDelta = new Vector2(-10, -10);
GameObject fill = new GameObject("Fill");
fill.transform.SetParent(fillArea.transform, false);
RectTransform fillRect = fill.AddComponent<RectTransform>();
fillRect.anchorMin = Vector2.zero;
fillRect.anchorMax = Vector2.one;
fillRect.sizeDelta = Vector2.zero;
Image fillImage = fill.AddComponent<Image>();
fillImage.color = new Color(0.2f, 0.6f, 1f, 1f);
// 滑动条手柄区域
GameObject handleArea = new GameObject("Handle Slide Area");
handleArea.transform.SetParent(sliderObj.transform, false);
RectTransform handleAreaRect = handleArea.AddComponent<RectTransform>();
handleAreaRect.anchorMin = Vector2.zero;
handleAreaRect.anchorMax = Vector2.one;
handleAreaRect.sizeDelta = new Vector2(-10, 0);
GameObject handle = new GameObject("Handle");
handle.transform.SetParent(handleArea.transform, false);
RectTransform handleRect = handle.AddComponent<RectTransform>();
handleRect.sizeDelta = new Vector2(30, 30);
Image handleImage = handle.AddComponent<Image>();
handleImage.color = Color.white;
slider.fillRect = fillRect;
slider.handleRect = handleRect;
slider.targetGraphic = handleImage;
// 创建输入框(滑动条右侧)
GameObject inputFieldObj = new GameObject("InputField");
inputFieldObj.transform.SetParent(valueObj.transform, false);
RectTransform inputRect = inputFieldObj.AddComponent<RectTransform>();
inputRect.anchorMin = new Vector2(0.56f, 0);
inputRect.anchorMax = new Vector2(0.73f, 0);
inputRect.pivot = new Vector2(0, 0);
inputRect.anchoredPosition = new Vector2(5, 15);
inputRect.sizeDelta = new Vector2(0, 50);
Image inputBg = inputFieldObj.AddComponent<Image>();
inputBg.color = new Color(0.25f, 0.25f, 0.25f, 1f);
// 创建输入框的文本子对象
GameObject inputTextObj = new GameObject("Text");
inputTextObj.transform.SetParent(inputFieldObj.transform, false);
RectTransform inputTextRect = inputTextObj.AddComponent<RectTransform>();
inputTextRect.anchorMin = Vector2.zero;
inputTextRect.anchorMax = Vector2.one;
inputTextRect.sizeDelta = Vector2.zero;
TextMeshProUGUI inputTextComponent = inputTextObj.AddComponent<TextMeshProUGUI>();
inputTextComponent.fontSize = 28;
inputTextComponent.alignment = TextAlignmentOptions.Center;
inputTextComponent.color = Color.white;
TMP_InputField inputField = inputFieldObj.AddComponent<TMP_InputField>();
inputField.textComponent = inputTextComponent;
// 创建确认按钮(输入框右侧)- 手动创建而不是使用CreateButton
GameObject confirmBtn = new GameObject("ConfirmButton", typeof(RectTransform));
confirmBtn.transform.SetParent(valueObj.transform, false);
RectTransform confirmRect = confirmBtn.GetComponent<RectTransform>();
confirmRect.anchorMin = new Vector2(0.74f, 0);
confirmRect.anchorMax = new Vector2(1, 0);
confirmRect.pivot = new Vector2(0, 0);
confirmRect.anchoredPosition = new Vector2(5, 15);
confirmRect.sizeDelta = new Vector2(-20, 50);
Image confirmBtnImage = confirmBtn.AddComponent<Image>();
confirmBtnImage.color = new Color(0.2f, 0.8f, 0.2f, 1f);
Button confirmButton = confirmBtn.AddComponent<Button>();
confirmButton.targetGraphic = confirmBtnImage;
GameObject confirmBtnText = new GameObject("Text");
confirmBtnText.transform.SetParent(confirmBtn.transform, false);
RectTransform confirmBtnTextRect = confirmBtnText.AddComponent<RectTransform>();
confirmBtnTextRect.anchorMin = Vector2.zero;
confirmBtnTextRect.anchorMax = Vector2.one;
confirmBtnTextRect.sizeDelta = Vector2.zero;
TextMeshProUGUI confirmText = confirmBtnText.AddComponent<TextMeshProUGUI>();
confirmText.text = "确认";
confirmText.fontSize = 28;
confirmText.alignment = TextAlignmentOptions.Center;
confirmText.color = Color.white;
return valueObj;
}
/// <summary>
/// 确保场景中有EventSystem如果没有则创建一个
/// </summary>
@ -1064,7 +1375,7 @@ namespace MeowmentDebugTool
private static void SetupReferences(UniversalDebugTool tool, Canvas canvas, GameObject mainWindow,
GameObject floatingButton, GameObject parametersPage, GameObject customButtonsPage,
GameObject toolbarPage, GameObject settingsPage, GameObject consolePage, GameObject inputDialog)
GameObject customCheckBoxesPage, GameObject customValuesPage, GameObject settingsPage, GameObject consolePage, GameObject inputDialog)
{
// 使用反射设置私有字段
var type = typeof(UniversalDebugTool);
@ -1092,27 +1403,26 @@ namespace MeowmentDebugTool
type.GetField("buttonContainer", flags)?.SetValue(tool, customButtonsPage.transform.Find("Viewport/ButtonContainer").GetComponent<RectTransform>());
type.GetField("buttonsScrollRect", flags)?.SetValue(tool, customButtonsPage.GetComponent<ScrollRect>());
// 创建按钮预制件
// 自定义复选框页面
type.GetField("customCheckBoxesPage", flags)?.SetValue(tool, customCheckBoxesPage);
type.GetField("checkBoxContainer", flags)?.SetValue(tool, customCheckBoxesPage.transform.Find("Viewport/CheckBoxContainer").GetComponent<RectTransform>());
type.GetField("checkBoxesScrollRect", flags)?.SetValue(tool, customCheckBoxesPage.GetComponent<ScrollRect>());
// 自定义数值页面
type.GetField("customValuesPage", flags)?.SetValue(tool, customValuesPage);
type.GetField("valueContainer", flags)?.SetValue(tool, customValuesPage.transform.Find("Viewport/ValueContainer").GetComponent<RectTransform>());
type.GetField("valuesScrollRect", flags)?.SetValue(tool, customValuesPage.GetComponent<ScrollRect>());
// 创建按钮、复选框和数值预制件
GameObject buttonPrefab = CreateCustomButtonPrefab();
GameObject checkBoxPrefab = CreateCustomCheckBoxPrefab();
GameObject valuePrefab = CreateCustomValuePrefab();
GameObject tabButtonPrefab = CreateTabButtonPrefab();
type.GetField("buttonPrefab", flags)?.SetValue(tool, buttonPrefab);
type.GetField("checkBoxPrefab", flags)?.SetValue(tool, checkBoxPrefab);
type.GetField("valuePrefab", flags)?.SetValue(tool, valuePrefab);
type.GetField("tabButtonPrefab", flags)?.SetValue(tool, tabButtonPrefab);
// 工具栏页面
type.GetField("toolbarPage", flags)?.SetValue(tool, toolbarPage);
Transform toolbarPanel = toolbarPage.transform.Find("TimeAdjustPanel");
if (toolbarPanel != null)
{
type.GetField("timeAdjustSlider", flags)?.SetValue(tool, toolbarPanel.Find("TimeAdjustSlider")?.GetComponent<Slider>());
type.GetField("timeAdjustValueText", flags)?.SetValue(tool, toolbarPanel.Find("TimeAdjustValueText")?.GetComponent<TextMeshProUGUI>());
Transform btnContainer = toolbarPanel.Find("ButtonContainer");
if (btnContainer != null)
{
type.GetField("timeIncreaseButton", flags)?.SetValue(tool, btnContainer.Find("TimeIncreaseButton")?.GetComponent<Button>());
type.GetField("timeDecreaseButton", flags)?.SetValue(tool, btnContainer.Find("TimeDecreaseButton")?.GetComponent<Button>());
}
}
// 设置页面
type.GetField("settingsPage", flags)?.SetValue(tool, settingsPage);
Transform resPanel = settingsPage.transform.Find("ResolutionPanel");

View File

@ -47,12 +47,17 @@ namespace MeowmentDebugTool
[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 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;
@ -100,13 +105,22 @@ namespace MeowmentDebugTool
// 模块实例
private ParametersModule parametersModule;
private CustomButtonsModule customButtonsModule;
private ToolbarModule toolbarModule;
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
#endregion
#region
@ -167,6 +181,22 @@ namespace MeowmentDebugTool
private void Update()
{
// 只有初始化后才执行任何逻辑
if (!isInitialized)
return;
// 键盘测试模式按F键触发
if (useKeyboardTestMode && Input.GetKeyDown(KeyCode.F))
{
OnFourFingerTap();
}
// 检测四指点击(仅在非键盘测试模式下)
if (!useKeyboardTestMode)
{
DetectFourFingerTap();
}
// 更新控制台模块
if (consoleModule != null)
{
@ -276,6 +306,22 @@ namespace MeowmentDebugTool
Instance.customButtonsModule.ReloadCustomButtons();
}
// 4.5. 通过模块重新加载自定义复选框
if (Instance.customCheckBoxesModule != null)
{
Debug.Log("[MeowmentDebugTool] 重新加载自定义复选框以应用新字体...");
Instance.customCheckBoxesModule.SetSDFFont(fontAsset);
Instance.customCheckBoxesModule.ReloadCustomCheckBoxes();
}
// 4.6. 通过模块重新加载数值
if (Instance.customValuesModule != null)
{
Debug.Log("[MeowmentDebugTool] 重新加载数值以应用新字体...");
Instance.customValuesModule.SetSDFFont(fontAsset);
Instance.customValuesModule.ReloadCustomValues();
}
// 5. 为控制台模块应用字体
if (Instance.consoleModule != null)
{
@ -318,9 +364,11 @@ namespace MeowmentDebugTool
customButtonsModule = new CustomButtonsModule(
customButtonsPage, buttonContainer, buttonPrefab, buttonsScrollRect, CloseDebugWindow);
toolbarModule = new ToolbarModule(
toolbarPage, timeAdjustSlider, timeAdjustValueText,
timeIncreaseButton, timeDecreaseButton);
customCheckBoxesModule = new CustomCheckBoxesModule(
customCheckBoxesPage, checkBoxContainer, checkBoxPrefab, checkBoxesScrollRect);
customValuesModule = new CustomValuesModule(
customValuesPage, valueContainer, valuePrefab, valuesScrollRect, CloseDebugWindow);
settingsModule = new SettingsModule(
settingsPage, widthInputField, heightInputField,
@ -338,7 +386,8 @@ namespace MeowmentDebugTool
allModules.Add(consoleModule);
allModules.Add(parametersModule);
allModules.Add(customButtonsModule);
allModules.Add(toolbarModule);
allModules.Add(customCheckBoxesModule);
allModules.Add(customValuesModule);
allModules.Add(settingsModule);
// 注册所有页面
@ -575,6 +624,24 @@ namespace MeowmentDebugTool
// #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>
/// 显示调试工具
/// </summary>
@ -745,6 +812,93 @@ namespace MeowmentDebugTool
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的显示/隐藏(包括主窗口和浮窗)
/// </summary>
private void OnFourFingerTap()
{
Debug.Log("[MeowmentDebugTool] 检测到四指点击!");
// 检查主窗口或浮窗是否有任意一个显示
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
@ -777,5 +931,77 @@ namespace MeowmentDebugTool
ButtonColor = new Color(r, g, b, 1f);
}
}
/// <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 DisplayName { get; set; }
public Color CheckBoxColor { get; set; }
public DebugCheckBoxAttribute(string displayName = "", float r = 0.8f, float g = 0.8f, float b = 0.8f)
{
DisplayName = displayName;
CheckBoxColor = 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 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)
{
DisplayName = displayName;
MinValue = minValue;
MaxValue = maxValue;
// 如果defaultValue为-1未设置则使用minValue作为默认值
DefaultValue = defaultValue == -1 ? minValue : defaultValue;
ValueColor = new Color(r, g, b, 1f);
}
}
#endregion
}

View File

@ -1,5 +1,5 @@
using UnityEngine;
using MeowmentDebugTool;
/// <summary>
/// 示例调试函数集合
/// 使用 [DebugButton] 特性来标记方法,它们会自动出现在调试工具中
@ -185,4 +185,57 @@ public static class SampleDebugFunctions
Application.Quit();
#endif
}
}
// ========== 复选框示例 ==========
// 注意:复选框方法必须接受一个 bool 参数
[DebugCheckBox("显示FPS")]
public static void ToggleShowFPS(bool isOn)
{
Debug.Log($"显示FPS: {isOn}");
// 这里可以控制FPS显示组件的开关
}
[DebugCheckBox("上帝模式", 1f, 0.8f, 0.2f)]
public static void ToggleGodMode(bool isOn)
{
Debug.Log($"上帝模式: {isOn}");
// 这里可以设置角色无敌等参数
}
[DebugCheckBox("无限金币", 0.2f, 0.8f, 0.2f)]
public static void ToggleInfiniteCoin(bool isOn)
{
Debug.Log($"无限金币: {isOn}");
// 这里可以设置金币相关参数
}
[DebugCheckBox("显示碰撞体", 0.5f, 0.5f, 1f)]
public static void ToggleShowColliders(bool isOn)
{
Debug.Log($"显示碰撞体: {isOn}");
// 这里可以控制碰撞体的可视化
}
[DebugCheckBox("调试模式", 0.8f, 0.2f, 0.8f)]
public static void ToggleDebugMode(bool isOn)
{
Debug.Log($"调试模式: {isOn}");
Debug.unityLogger.logEnabled = isOn;
}
[DebugCheckBox("静音音效")]
public static void ToggleMuteSound(bool isOn)
{
Debug.Log($"静音音效: {isOn}");
AudioListener.volume = isOn ? 0f : 1f;
}
[DebugCheckBox("锁定帧率")]
public static void ToggleLockFramerate(bool isOn)
{
Debug.Log($"锁定帧率: {isOn}");
Application.targetFrameRate = isOn ? 30 : -1;
}
}

View File

@ -1,7 +1,7 @@
{
"name": "com.bywaystudios.meowmentdebugtool",
"displayName": "MeowmentDebugTool",
"version": "0.3.0",
"version": "0.3.7",
"description": "\u8c03\u8bd5\u5de5\u5177\uff0c\u96c6\u6210\u4e86\u5ba2\u6237\u7aef\u6d4b\u8bd5\u65f6\u7684\u5e38\u7528\u529f\u80fd\uff0c\u652f\u6301\u6269\u5c55\u81ea\u5b9a\u4e49\u6309\u94ae\uff0c\u65b9\u4fbf\u5f00\u53d1\u8005\u8c03\u8bd5\u548c\u6d4b\u8bd5\u3002",
"samples": [
{