0.3更新,新增控制台
This commit is contained in:
parent
1152c5032c
commit
62f990f702
@ -1,5 +1,5 @@
|
||||
<Solution>
|
||||
<Project Path="com.bywaystudios.meowmentdebugtool.Editor.csproj" />
|
||||
<Project Path="com.bywaystudios.meowmentdebugtool.csproj" />
|
||||
<Project Path="com.bywaystudios.meowmentdebugtool.Editor.csproj" />
|
||||
<Project Path="Assembly-CSharp.csproj" />
|
||||
</Solution>
|
||||
|
||||
@ -0,0 +1,215 @@
|
||||
# Console模块错误修复说明
|
||||
|
||||
## 🐛 已修复的问题
|
||||
|
||||
### 1. Rebuild Loop 错误
|
||||
|
||||
**问题描述**:
|
||||
```
|
||||
Trying to remove/add Background (UnityEngine.UI.Image) from/to rebuild list
|
||||
while we are already inside a rebuild loop. This is not supported.
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- 在 `OnLogMessageReceived` 回调中立即调用 `RefreshLogDisplay()` 刷新UI
|
||||
- 在Unity的Canvas rebuild循环中修改UI元素会触发此错误
|
||||
- `Canvas.ForceUpdateCanvases()` 在初始化时也会导致rebuild loop
|
||||
|
||||
**修复方案**:
|
||||
|
||||
#### ConsoleModule.cs
|
||||
1. ✅ **延迟刷新机制**: 添加 `needRefresh` 标志,不在回调中立即刷新UI
|
||||
```csharp
|
||||
private bool needRefresh = false;
|
||||
|
||||
// 在OnLogMessageReceived中
|
||||
needRefresh = true; // 标记需要刷新
|
||||
|
||||
// 在Update中
|
||||
if (needRefresh)
|
||||
{
|
||||
needRefresh = false;
|
||||
RefreshLogDisplay(); // 延迟到下一帧刷新
|
||||
}
|
||||
```
|
||||
|
||||
2. ✅ **滚动位置延迟设置**: 使用帧计数延迟设置滚动位置
|
||||
```csharp
|
||||
private int lastRefreshFrame = -1;
|
||||
|
||||
// 只在刷新后的第2帧设置滚动位置
|
||||
if (lockScroll && Time.frameCount > lastRefreshFrame + 1)
|
||||
{
|
||||
logScrollRect.verticalNormalizedPosition = 0f;
|
||||
}
|
||||
```
|
||||
|
||||
3. ✅ **过滤器事件优化**: 过滤器改变时也使用延迟刷新
|
||||
```csharp
|
||||
private void OnInfoFilterChanged(bool value)
|
||||
{
|
||||
infoFilter = value;
|
||||
needRefresh = true; // 而不是立即RefreshLogDisplay()
|
||||
}
|
||||
```
|
||||
|
||||
#### SettingsModule.cs
|
||||
4. ✅ **移除ForceUpdateCanvases**: Canvas会自动在下一帧更新
|
||||
```csharp
|
||||
private void ApplyResolution(Vector2 resolution)
|
||||
{
|
||||
mainWindow.sizeDelta = resolution;
|
||||
currentResolutionText.text = $"当前窗口尺寸: {resolution.x} x {resolution.y}";
|
||||
|
||||
// 移除了 Canvas.ForceUpdateCanvases();
|
||||
// Canvas会自动更新,不需要强制刷新
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 中文字体警告
|
||||
|
||||
**问题描述**:
|
||||
```
|
||||
The character with Unicode value \u5F84 was not found in the [LiberationSans SDF]
|
||||
font asset or any potential fallbacks.
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- RuntimeUIGenerator使用的默认字体 `LiberationSans SDF` 不支持中文字符
|
||||
- TextMeshPro需要专门的中文字体资源
|
||||
|
||||
**解决方案** (选择其一):
|
||||
|
||||
#### 方案1: 使用中文字体 (推荐)
|
||||
```csharp
|
||||
// 在游戏初始化时设置中文字体
|
||||
UniversalDebugTool.Init();
|
||||
|
||||
// 加载支持中文的TMP字体
|
||||
TMP_FontAsset chineseFont = Resources.Load<TMP_FontAsset>("Fonts/ChineseFont SDF");
|
||||
UniversalDebugTool.SetSDFFont(chineseFont);
|
||||
```
|
||||
|
||||
#### 方案2: 禁用字体警告
|
||||
在RuntimeUIGenerator的GetDefaultFont方法中添加过滤:
|
||||
```csharp
|
||||
private static TMP_FontAsset GetDefaultFont()
|
||||
{
|
||||
// 禁用TMP字体警告
|
||||
TMPro.TMP_Settings.warningsDisabled = true;
|
||||
|
||||
// 查找默认字体...
|
||||
}
|
||||
```
|
||||
|
||||
#### 方案3: 创建中文字体资源
|
||||
1. 在Unity中:Window > TextMeshPro > Font Asset Creator
|
||||
2. 选择支持中文的字体(如Arial Unicode MS、Microsoft YaHei等)
|
||||
3. 设置Character Set为Unicode或Custom Characters
|
||||
4. 添加常用中文字符
|
||||
5. 生成字体资源并在代码中加载
|
||||
|
||||
## 📋 修改总结
|
||||
|
||||
### 文件变更
|
||||
|
||||
#### ConsoleModule.cs
|
||||
- ✅ 添加 `needRefresh` 标志
|
||||
- ✅ 添加 `lastRefreshFrame` 帧计数
|
||||
- ✅ 修改 `OnLogMessageReceived` 使用延迟刷新
|
||||
- ✅ 修改 `Update` 方法处理延迟刷新和滚动
|
||||
- ✅ 修改所有过滤器事件使用延迟刷新
|
||||
|
||||
#### SettingsModule.cs
|
||||
- ✅ 移除 `Canvas.ForceUpdateCanvases()` 调用
|
||||
|
||||
### 性能影响
|
||||
|
||||
✅ **更好**: 延迟刷新减少了不必要的UI重建
|
||||
✅ **更稳定**: 避免了rebuild loop错误
|
||||
✅ **无副作用**: 延迟一帧刷新对用户体验无影响
|
||||
|
||||
## 🧪 测试建议
|
||||
|
||||
### 1. 测试Rebuild Loop修复
|
||||
```csharp
|
||||
void Start()
|
||||
{
|
||||
UniversalDebugTool.Init();
|
||||
|
||||
// 快速生成大量日志
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
Debug.Log($"测试日志 {i}");
|
||||
Debug.LogWarning($"警告 {i}");
|
||||
Debug.LogError($"错误 {i}");
|
||||
}
|
||||
|
||||
// 不应该再有rebuild loop错误
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 测试滚动功能
|
||||
```csharp
|
||||
void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Space))
|
||||
{
|
||||
Debug.Log($"新日志 - 帧数: {Time.frameCount}");
|
||||
// 应该自动滚动到底部(如果锁定滚动已启用)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 测试过滤器
|
||||
1. 生成多种类型的日志
|
||||
2. 点击Console标签页
|
||||
3. 切换Info/Warning/Error/Fatal过滤器
|
||||
4. 不应该有rebuild loop错误
|
||||
|
||||
### 4. 测试分辨率设置
|
||||
1. 进入Settings标签页
|
||||
2. 修改宽度和高度
|
||||
3. 点击"应用"按钮
|
||||
4. 不应该有rebuild loop错误
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 关于中文字体
|
||||
如果你的游戏需要显示中文,必须:
|
||||
1. 创建或导入支持中文的TMP字体资源
|
||||
2. 在初始化后调用 `UniversalDebugTool.SetSDFFont(yourChineseFont)`
|
||||
3. 或者在RuntimeUIGenerator中修改GetDefaultFont方法返回中文字体
|
||||
|
||||
### 最佳实践
|
||||
```csharp
|
||||
public class GameInit : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private TMP_FontAsset chineseFont;
|
||||
|
||||
void Start()
|
||||
{
|
||||
// 1. 初始化调试工具
|
||||
UniversalDebugTool.Init();
|
||||
|
||||
// 2. 设置中文字体(如果需要)
|
||||
if (chineseFont != null)
|
||||
{
|
||||
UniversalDebugTool.SetSDFFont(chineseFont);
|
||||
}
|
||||
|
||||
// 3. 其他初始化...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ✅ 预期结果
|
||||
|
||||
修复后,你应该看到:
|
||||
- ✅ 没有 "Trying to remove/add ... from/to rebuild list" 错误
|
||||
- ✅ Console正常显示日志
|
||||
- ✅ 过滤器正常工作
|
||||
- ✅ 滚动功能正常
|
||||
- ⚠️ 可能仍有字体警告(如果没有设置中文字体)
|
||||
|
||||
字体警告不影响功能,只是文本显示为方块。如果需要中文显示,请按照上述方案添加中文字体。
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d6ac60c7ac1db84299fc8dde7508c38
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -5,6 +5,50 @@
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||
版本号遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
|
||||
|
||||
## [0.3.0] - 2025-12-22
|
||||
|
||||
### Added
|
||||
- **控制台模块 (Console)**
|
||||
- 实时捕获并显示Unity日志(Info、Warning、Error、Fatal)
|
||||
- 支持日志类型过滤(可单独开关各类型日志显示)
|
||||
- 支持日志点击查看详细信息(堆栈跟踪)
|
||||
- 日志数量统计和实时更新
|
||||
- 支持清空日志功能
|
||||
- 支持锁定滚动功能(自动滚动到最新日志)
|
||||
- 日志项对象池优化,提升性能
|
||||
- 最大日志数限制(默认100条,可配置)
|
||||
- 日志详情区域显示完整堆栈信息
|
||||
- 使用UGUI实现,支持ScrollRect滚动
|
||||
|
||||
- **暂时隐藏功能**
|
||||
- 工具栏新增"暂时隐藏(5秒)"按钮
|
||||
- 用于截图时临时隐藏调试工具UI
|
||||
- 自动在指定时间后恢复显示
|
||||
- 支持防重入保护,避免重复触发
|
||||
- 恢复后保持浮窗状态,不自动打开主窗口
|
||||
|
||||
### Changed
|
||||
- 重构代码架构,将功能模块化
|
||||
- 新增 `IDebugModule` 接口
|
||||
- 拆分为独立模块:`ParametersModule`、`CustomButtonsModule`、`ToolbarModule`、`SettingsModule`、`ConsoleModule`
|
||||
- 优化控制台UI布局
|
||||
- ControlPanel高度优化为40像素
|
||||
- LogArea和DetailArea固定高度
|
||||
- 移除不常用的Spacer元素
|
||||
- 优化日志显示
|
||||
- 日志项高度从30增加到50像素
|
||||
- 字体大小从22增加到28
|
||||
- 添加上下边距提升可读性
|
||||
- 移除所有emoji字符,避免字体缺失警告
|
||||
- 锁定滚动功能默认开启
|
||||
|
||||
### Fixed
|
||||
- 修复Canvas rebuild loop错误(延迟刷新机制)
|
||||
- 修复日志项RectTransform锚点设置问题
|
||||
- 修复VerticalLayoutGroup子元素高度控制问题
|
||||
- 修复页面未激活时不必要的更新操作
|
||||
- 修复暂时隐藏功能第二次执行失败的问题
|
||||
|
||||
## [0.2.2] - 2025-12-19
|
||||
|
||||
### Changed
|
||||
|
||||
@ -0,0 +1,236 @@
|
||||
# Console 控制台模块 - 添加说明
|
||||
|
||||
## ✅ 已完成的工作
|
||||
|
||||
### 1. 新增文件
|
||||
|
||||
#### [LogNode.cs](Runtime/LogNode.cs)
|
||||
日志节点数据结构,包含:
|
||||
- LogType: 日志类型(Info/Warning/Error/Exception)
|
||||
- LogMessage: 日志消息
|
||||
- StackTrace: 堆栈跟踪
|
||||
- LogTime: 日志时间
|
||||
- LogFrameCount: 日志帧数
|
||||
|
||||
#### [ConsoleModule.cs](Runtime/ConsoleModule.cs)
|
||||
控制台模块实现,基于UGUI,包含以下功能:
|
||||
- ✅ 实时捕获Unity日志
|
||||
- ✅ 日志分类和过滤(Info/Warning/Error/Fatal)
|
||||
- ✅ 锁定滚动到底部
|
||||
- ✅ 点击查看详细堆栈信息
|
||||
- ✅ 清除日志功能
|
||||
- ✅ 日志颜色区分
|
||||
- ✅ 对象池性能优化
|
||||
- ✅ 最大日志行数限制(500行)
|
||||
- ✅ SDF字体支持
|
||||
|
||||
### 2. 修改的文件
|
||||
|
||||
#### [UniversalDebugTool.cs](Runtime/UniversalDebugTool.cs)
|
||||
添加了以下内容:
|
||||
1. **序列化字段** - Console页面的所有UI组件引用
|
||||
2. **ConsoleModule实例** - 控制台模块对象
|
||||
3. **初始化代码** - 在InitializeDebugTool中创建和注册Console模块
|
||||
4. **Update方法** - 调用consoleModule.Update()更新UI
|
||||
5. **OnDestroy方法** - 清理控制台模块资源
|
||||
6. **SetSDFFont支持** - 为Console模块应用字体
|
||||
|
||||
## 📋 需要在Unity中配置的UI
|
||||
|
||||
### Unity Inspector 配置清单
|
||||
|
||||
在UniversalDebugTool组件的Inspector中,需要配置以下字段:
|
||||
|
||||
```
|
||||
[控制台页面]
|
||||
├── Console Page (GameObject)
|
||||
├── Console Log Scroll Rect (ScrollRect)
|
||||
├── Console Log Content (RectTransform)
|
||||
├── Console Detail Scroll Rect (ScrollRect)
|
||||
├── Console Detail Text (TMP_Text)
|
||||
├── Console Clear Button (Button)
|
||||
├── Console Lock Scroll Toggle (Toggle)
|
||||
├── Console Info Filter Toggle (Toggle)
|
||||
├── Console Warning Filter Toggle (Toggle)
|
||||
├── Console Error Filter Toggle (Toggle)
|
||||
├── Console Fatal Filter Toggle (Toggle)
|
||||
└── Console Log Item Prefab (GameObject)
|
||||
```
|
||||
|
||||
### UI层次结构示例
|
||||
|
||||
```
|
||||
ConsolePage
|
||||
├── ControlPanel (HorizontalLayoutGroup)
|
||||
│ ├── ClearButton
|
||||
│ ├── LockScrollToggle
|
||||
│ ├── Spacer (LayoutElement - FlexibleWidth)
|
||||
│ ├── InfoFilterToggle
|
||||
│ ├── WarningFilterToggle
|
||||
│ ├── ErrorFilterToggle
|
||||
│ └── FatalFilterToggle
|
||||
│
|
||||
├── LogArea (60% 高度)
|
||||
│ └── LogScrollView (ScrollRect)
|
||||
│ ├── Viewport
|
||||
│ └── Content (VerticalLayoutGroup)
|
||||
│ └── [日志项动态生成在这里]
|
||||
│
|
||||
└── DetailArea (40% 高度)
|
||||
└── DetailScrollView (ScrollRect)
|
||||
├── Viewport
|
||||
└── Content
|
||||
└── DetailText
|
||||
```
|
||||
|
||||
### 日志项预制件 (ConsoleLogItemPrefab)
|
||||
|
||||
创建一个预制件,结构如下:
|
||||
```
|
||||
LogItem (GameObject)
|
||||
├── Toggle (Toggle组件)
|
||||
├── Background (Image)
|
||||
└── Label (TextMeshPro - Text)
|
||||
- RichText: 启用
|
||||
- Word Wrapping: 启用
|
||||
- Overflow: Overflow
|
||||
```
|
||||
|
||||
## 🎨 UI组件配置建议
|
||||
|
||||
### ControlPanel
|
||||
- Component: HorizontalLayoutGroup
|
||||
- Padding: (10, 10, 10, 10)
|
||||
- Spacing: 10
|
||||
- Child Force Expand: Width = false, Height = false
|
||||
|
||||
### LogScrollView
|
||||
- Component: ScrollRect
|
||||
- Content: 指向LogContent
|
||||
- Vertical: ✅
|
||||
- Horizontal: ❌
|
||||
- Movement Type: Elastic
|
||||
- Inertia: ✅
|
||||
- Scrollbar Visibility: Auto Hide
|
||||
|
||||
### LogContent
|
||||
- Component: VerticalLayoutGroup
|
||||
- Child Alignment: Upper Center
|
||||
- Child Control Size: Height = ✅
|
||||
- Child Force Expand: Width = ✅
|
||||
- Spacing: 2
|
||||
|
||||
### DetailScrollView
|
||||
- Component: ScrollRect
|
||||
- Vertical: ✅
|
||||
- Horizontal: ❌
|
||||
- Movement Type: Clamped
|
||||
|
||||
### DetailText
|
||||
- Rich Text: ✅
|
||||
- Wrapping: ✅
|
||||
- Overflow: Overflow
|
||||
- Font Size: 14-16
|
||||
|
||||
### Toggle组件
|
||||
- Is On: 根据默认值
|
||||
- Toggle Transition: Fade
|
||||
- 每个Toggle添加Label (TMP_Text)显示文本
|
||||
|
||||
## 🔧 代码集成
|
||||
|
||||
### 模块已自动集成
|
||||
ConsoleModule已经在UniversalDebugTool中自动初始化和管理,无需额外代码。
|
||||
|
||||
### 使用示例
|
||||
|
||||
```csharp
|
||||
// 1. 初始化调试工具(会自动初始化Console)
|
||||
UniversalDebugTool.Init();
|
||||
|
||||
// 2. 设置字体(可选)
|
||||
UniversalDebugTool.SetSDFFont(myFont);
|
||||
|
||||
// 3. 获取日志统计(可选)
|
||||
if (UniversalDebugTool.InstanceExists)
|
||||
{
|
||||
var console = UniversalDebugTool.Instance.consoleModule;
|
||||
if (console != null)
|
||||
{
|
||||
console.GetLogCount(out int info, out int warning,
|
||||
out int error, out int fatal);
|
||||
Debug.Log($"日志: Info={info}, Warn={warning}, " +
|
||||
$"Error={error}, Fatal={fatal}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 与GameFramework对比
|
||||
|
||||
### 主要差异
|
||||
|
||||
| 项目 | GameFramework | MeowmentDebugTool |
|
||||
|------|---------------|-------------------|
|
||||
| UI系统 | OnGUI | UGUI |
|
||||
| 渲染方式 | 即时模式(每帧重绘) | 保留模式(按需刷新) |
|
||||
| 性能 | 中等 | 更优(对象池优化) |
|
||||
| 布局方式 | GUILayout | LayoutGroup + RectTransform |
|
||||
| 交互组件 | GUILayout.Button/Toggle | Button/Toggle组件 |
|
||||
| 滚动视图 | GUILayout.BeginScrollView | ScrollRect组件 |
|
||||
| 定制性 | 通过GUISkin | 完全UGUI定制 |
|
||||
|
||||
### 实现等价性
|
||||
|
||||
✅ **完全实现的功能**:
|
||||
- 日志捕获(Application.logMessageReceived)
|
||||
- 日志分类(Info/Warning/Error/Fatal)
|
||||
- 过滤器(4种日志类型独立过滤)
|
||||
- 锁定滚动
|
||||
- 选中查看详情
|
||||
- 清除日志
|
||||
- 日志颜色区分
|
||||
- 最大行数限制
|
||||
|
||||
✅ **优化功能**:
|
||||
- 对象池(避免频繁创建销毁)
|
||||
- UGUI性能更好
|
||||
- 更灵活的布局系统
|
||||
|
||||
❌ **未实现的功能**:
|
||||
- 复制到剪贴板(可以通过DetailText选择复制)
|
||||
- SettingComponent持久化(使用PlayerPrefs替代即可)
|
||||
|
||||
## 🎯 后续步骤
|
||||
|
||||
1. **在Unity中创建UI**
|
||||
- 在Scene或Prefab中创建Console页面UI
|
||||
- 按照层次结构创建各个组件
|
||||
- 创建日志项预制件
|
||||
|
||||
2. **配置引用**
|
||||
- 在UniversalDebugTool组件Inspector中
|
||||
- 将所有Console相关UI组件拖拽到对应字段
|
||||
|
||||
3. **测试**
|
||||
- 运行游戏
|
||||
- 调用 `UniversalDebugTool.Init()`
|
||||
- 查看Console标签页
|
||||
- 测试各个过滤器和功能
|
||||
|
||||
4. **调整样式**(可选)
|
||||
- 修改颜色、字体大小
|
||||
- 调整布局间距
|
||||
- 优化日志项高度
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **日志项预制件必须有Toggle组件**
|
||||
2. **ScrollRect的Content必须正确设置**
|
||||
3. **VerticalLayoutGroup的设置影响滚动性能**
|
||||
4. **大量日志时注意性能(已有500行限制)**
|
||||
5. **确保Console Page在初始化时是隐藏的**
|
||||
|
||||
## 📚 参考文档
|
||||
|
||||
- [CONSOLE_MODULE_GUIDE.md](CONSOLE_MODULE_GUIDE.md) - 详细的UI结构和配置指南
|
||||
- [REFACTORING_NOTES.md](REFACTORING_NOTES.md) - 模块化重构说明
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9bc4f10ea5e6a9d49b550c5aec275f1a
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,171 @@
|
||||
# Console 控制台模块 UI 结构说明
|
||||
|
||||
## 概述
|
||||
|
||||
Console模块是一个基于UGUI的日志控制台,用于实时显示Unity的日志信息(Info/Warning/Error/Exception)。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 实时捕获Unity日志(Application.logMessageReceived)
|
||||
- ✅ 日志分类显示(Info/Warning/Error/Fatal)
|
||||
- ✅ 日志过滤器(可以单独开关各类型日志)
|
||||
- ✅ 锁定滚动到底部
|
||||
- ✅ 点击日志查看详细堆栈信息
|
||||
- ✅ 清除所有日志
|
||||
- ✅ 日志颜色区分
|
||||
- ✅ 最大日志行数限制(默认500行)
|
||||
- ✅ 对象池优化性能
|
||||
|
||||
## UI结构
|
||||
|
||||
### Console Page 层次结构
|
||||
```
|
||||
ConsolePage (GameObject)
|
||||
├── ControlPanel (水平布局)
|
||||
│ ├── ClearButton (Button) - 清空日志按钮
|
||||
│ ├── LockScrollToggle (Toggle) - 锁定滚动
|
||||
│ ├── Spacer (空白区域)
|
||||
│ ├── InfoFilterToggle (Toggle) - Info过滤器
|
||||
│ ├── WarningFilterToggle (Toggle) - Warning过滤器
|
||||
│ ├── ErrorFilterToggle (Toggle) - Error过滤器
|
||||
│ └── FatalFilterToggle (Toggle) - Fatal过滤器
|
||||
│
|
||||
├── LogScrollView (ScrollRect) - 日志列表区域
|
||||
│ └── LogContent (RectTransform) - 日志内容容器
|
||||
│ └── [动态生成的日志项]
|
||||
│
|
||||
└── DetailScrollView (ScrollRect) - 详细信息区域
|
||||
└── DetailText (TMP_Text) - 显示选中日志的详细信息和堆栈
|
||||
```
|
||||
|
||||
### 日志项预制件结构 (ConsoleLogItemPrefab)
|
||||
```
|
||||
LogItem (GameObject)
|
||||
├── Toggle (Toggle组件) - 用于选中日志
|
||||
├── Background (Image) - 背景
|
||||
└── Label (TMP_Text) - 显示日志文本
|
||||
```
|
||||
|
||||
## 序列化字段配置
|
||||
|
||||
在 UniversalDebugTool 的 Inspector 中需要配置以下字段:
|
||||
|
||||
### Console页面字段
|
||||
```csharp
|
||||
[Header("控制台页面")]
|
||||
consolePage // 控制台页面根对象
|
||||
consoleLogScrollRect // 日志列表ScrollRect
|
||||
consoleLogContent // 日志内容容器RectTransform
|
||||
consoleDetailScrollRect // 详情ScrollRect
|
||||
consoleDetailText // 详情文本TMP_Text
|
||||
consoleClearButton // 清空按钮
|
||||
consoleLockScrollToggle // 锁定滚动Toggle
|
||||
consoleInfoFilterToggle // Info过滤Toggle
|
||||
consoleWarningFilterToggle // Warning过滤Toggle
|
||||
consoleErrorFilterToggle // Error过滤Toggle
|
||||
consoleFatalFilterToggle // Fatal过滤Toggle
|
||||
consoleLogItemPrefab // 日志项预制件
|
||||
```
|
||||
|
||||
## UI组件要求
|
||||
|
||||
### 1. ControlPanel
|
||||
- 使用 HorizontalLayoutGroup
|
||||
- 建议添加 ContentSizeFitter
|
||||
- Padding: 10, 10, 10, 10
|
||||
- Spacing: 10
|
||||
|
||||
### 2. LogScrollView
|
||||
- ScrollRect 设置:
|
||||
- Vertical: true
|
||||
- Horizontal: false
|
||||
- Movement Type: Elastic
|
||||
- Scrollbar Visibility: Automatic If Needed
|
||||
- LogContent 需要 VerticalLayoutGroup:
|
||||
- Child Alignment: Upper Center
|
||||
- Child Force Expand: Width
|
||||
- Child Control Size: Height
|
||||
- Spacing: 2
|
||||
|
||||
### 3. DetailScrollView
|
||||
- ScrollRect 设置:
|
||||
- Vertical: true
|
||||
- Horizontal: false
|
||||
- Movement Type: Clamped
|
||||
- 建议高度: 150-200
|
||||
- DetailText 设置:
|
||||
- Enable Word Wrapping
|
||||
- Overflow: Overflow
|
||||
- Enable Rich Text
|
||||
|
||||
### 4. Toggles
|
||||
- 每个Toggle需要一个Label (TMP_Text)
|
||||
- 建议宽度: 100-120
|
||||
- Toggle Group: 不需要(可以多选)
|
||||
|
||||
## 日志颜色配置
|
||||
|
||||
在 ConsoleModule 中默认的颜色:
|
||||
```csharp
|
||||
Info: Color.white (255, 255, 255)
|
||||
Warning: Color.yellow (255, 255, 0)
|
||||
Error: Color.red (255, 0, 0)
|
||||
Fatal: Color(0.7f, 0.2f, 0.2f) (178, 51, 51)
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
1. **对象池机制**: 日志项使用对象池,避免频繁创建销毁
|
||||
2. **最大行数限制**: 默认500行,防止内存溢出
|
||||
3. **按需刷新**: 只在日志变化或过滤器改变时刷新显示
|
||||
4. **Toggle优化**: 使用Toggle而不是Button,减少重绘
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 在代码中获取日志统计
|
||||
```csharp
|
||||
if (UniversalDebugTool.InstanceExists)
|
||||
{
|
||||
var console = UniversalDebugTool.Instance.consoleModule;
|
||||
if (console != null)
|
||||
{
|
||||
console.GetLogCount(out int info, out int warning, out int error, out int fatal);
|
||||
Debug.Log($"日志统计 - Info:{info}, Warning:{warning}, Error:{error}, Fatal:{fatal}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 设置字体
|
||||
```csharp
|
||||
UniversalDebugTool.SetSDFFont(myFont);
|
||||
// 会自动应用到控制台模块
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **日志项预制件**: 必须包含 Toggle 和 TMP_Text 组件
|
||||
2. **ScrollRect**: 必须正确设置 Content 和 Viewport
|
||||
3. **性能**: 大量日志时建议增加日志项的高度以减少可见数量
|
||||
4. **线程安全**: 日志回调在主线程执行,无需担心线程问题
|
||||
5. **内存管理**: 超过最大行数的日志会自动删除
|
||||
|
||||
## 与GameFramework的差异
|
||||
|
||||
| 特性 | GameFramework (GUI) | MeowmentDebugTool (UGUI) |
|
||||
|------|---------------------|--------------------------|
|
||||
| UI系统 | OnGUI (即时模式) | UGUI (保留模式) |
|
||||
| 性能 | 每帧重绘 | 按需刷新 |
|
||||
| 布局 | GUILayout自动布局 | RectTransform + LayoutGroup |
|
||||
| 交互 | GUILayout.Button/Toggle | Button/Toggle组件 |
|
||||
| 对象池 | 不需要 | 使用对象池优化 |
|
||||
| 滚动 | GUILayout.BeginScrollView | ScrollRect组件 |
|
||||
|
||||
## 未来扩展
|
||||
|
||||
可以考虑添加的功能:
|
||||
- [ ] 搜索/过滤日志内容
|
||||
- [ ] 导出日志到文件
|
||||
- [ ] 日志标签/分类
|
||||
- [ ] 日志统计图表
|
||||
- [ ] 复制日志到剪贴板
|
||||
- [ ] 日志时间范围过滤
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e77813ce2d912b42b65134e2ce344f9
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,233 @@
|
||||
# Console模块快速设置指南
|
||||
|
||||
## 🚀 5分钟快速设置
|
||||
|
||||
### 步骤1: 创建Console页面GameObject
|
||||
|
||||
在UniversalDebugTool的预制件中,创建以下结构:
|
||||
|
||||
```
|
||||
[添加到ContentContainer下]
|
||||
└── ConsolePage
|
||||
├── ControlPanel
|
||||
│ ├── ClearButton (Button)
|
||||
│ ├── LockScrollToggle (Toggle + Label)
|
||||
│ ├── Spacer (Empty GameObject + LayoutElement)
|
||||
│ ├── InfoToggle (Toggle + Label)
|
||||
│ ├── WarningToggle (Toggle + Label)
|
||||
│ ├── ErrorToggle (Toggle + Label)
|
||||
│ └── FatalToggle (Toggle + Label)
|
||||
│
|
||||
├── LogArea
|
||||
│ └── LogScrollView (ScrollRect)
|
||||
│ └── Viewport
|
||||
│ └── Content (VerticalLayoutGroup)
|
||||
│
|
||||
└── DetailArea
|
||||
└── DetailScrollView (ScrollRect)
|
||||
└── Viewport
|
||||
└── Content
|
||||
└── DetailText (TMP_Text)
|
||||
```
|
||||
|
||||
### 步骤2: 配置组件
|
||||
|
||||
#### ConsolePage
|
||||
- RectTransform: Stretch (全屏)
|
||||
- 默认禁用(Active = false)
|
||||
|
||||
#### ControlPanel
|
||||
- Height: 50
|
||||
- Add Component: HorizontalLayoutGroup
|
||||
- Padding: 10, 10, 10, 10
|
||||
- Spacing: 10
|
||||
- Child Force Expand: 都不勾选
|
||||
|
||||
#### Spacer (占位符)
|
||||
- Add Component: LayoutElement
|
||||
- Flexible Width: 1
|
||||
|
||||
#### LogArea
|
||||
- Height: 60% (~400)
|
||||
- Add Component: VerticalLayoutGroup
|
||||
|
||||
#### LogScrollView
|
||||
- Add Component: ScrollRect
|
||||
- Content: LogContent
|
||||
- Vertical: ✅
|
||||
- Horizontal: ❌
|
||||
- Scrollbar Visibility: Auto Hide And Expand Viewport
|
||||
|
||||
#### LogContent
|
||||
- Add Component: VerticalLayoutGroup
|
||||
- Child Alignment: Upper Center
|
||||
- Spacing: 2
|
||||
- Child Control Size: Height ✅
|
||||
- Child Force Expand: Width ✅
|
||||
- Add Component: ContentSizeFitter
|
||||
- Vertical Fit: Preferred Size
|
||||
|
||||
#### DetailArea
|
||||
- Height: 40% (~200)
|
||||
|
||||
#### DetailScrollView
|
||||
- 同LogScrollView配置
|
||||
|
||||
#### DetailText
|
||||
- Font Size: 14
|
||||
- Color: White
|
||||
- Alignment: Top Left
|
||||
- Overflow: Overflow
|
||||
- Wrapping: Enabled
|
||||
- Rich Text: ✅
|
||||
|
||||
### 步骤3: 创建日志项预制件
|
||||
|
||||
创建一个新的预制件 `ConsoleLogItem`:
|
||||
|
||||
```
|
||||
LogItem
|
||||
├── Toggle (Toggle组件)
|
||||
│ - Is On: false
|
||||
│ - Transition: None或Fade
|
||||
│
|
||||
├── Background (Image)
|
||||
│ - Color: (30, 30, 30, 255)
|
||||
│
|
||||
└── Label (TextMeshPro)
|
||||
- Font Size: 12
|
||||
- Color: White
|
||||
- Alignment: Middle Left
|
||||
- Overflow: Truncate
|
||||
- Rich Text: ✅
|
||||
```
|
||||
|
||||
组件设置:
|
||||
- RectTransform:
|
||||
- Anchor: Stretch Horizontal
|
||||
- Height: 25
|
||||
- Left: 0, Right: 0
|
||||
- Toggle:
|
||||
- Target Graphic: Background
|
||||
- Graphic: Background
|
||||
|
||||
### 步骤4: 配置UniversalDebugTool
|
||||
|
||||
在Inspector中找到 `[控制台页面]` 部分,拖拽对应的对象:
|
||||
|
||||
```
|
||||
✅ Console Page → ConsolePage
|
||||
✅ Console Log Scroll Rect → LogScrollView
|
||||
✅ Console Log Content → LogContent
|
||||
✅ Console Detail Scroll Rect → DetailScrollView
|
||||
✅ Console Detail Text → DetailText
|
||||
✅ Console Clear Button → ClearButton
|
||||
✅ Console Lock Scroll Toggle → LockScrollToggle
|
||||
✅ Console Info Filter Toggle → InfoToggle
|
||||
✅ Console Warning Filter Toggle → WarningToggle
|
||||
✅ Console Error Filter Toggle → ErrorToggle
|
||||
✅ Console Fatal Filter Toggle → FatalToggle
|
||||
✅ Console Log Item Prefab → ConsoleLogItem预制件
|
||||
```
|
||||
|
||||
### 步骤5: 测试
|
||||
|
||||
```csharp
|
||||
// 在游戏开始时调用
|
||||
UniversalDebugTool.Init();
|
||||
|
||||
// 测试日志
|
||||
Debug.Log("这是一条Info日志");
|
||||
Debug.LogWarning("这是一条Warning日志");
|
||||
Debug.LogError("这是一条Error日志");
|
||||
```
|
||||
|
||||
运行游戏,点击调试工具,切换到"控制台"标签页,应该能看到日志了!
|
||||
|
||||
## 🎨 样式建议
|
||||
|
||||
### 颜色方案(深色主题)
|
||||
```
|
||||
Background: (20, 20, 20)
|
||||
ControlPanel: (40, 40, 40)
|
||||
LogItem: (30, 30, 30)
|
||||
LogItem Selected: (60, 60, 60)
|
||||
Text: (220, 220, 220)
|
||||
|
||||
日志颜色:
|
||||
Info: White (255, 255, 255)
|
||||
Warning: Yellow (255, 235, 0)
|
||||
Error: Red (255, 80, 80)
|
||||
Fatal: Dark Red (180, 50, 50)
|
||||
```
|
||||
|
||||
### 字体大小
|
||||
```
|
||||
ControlPanel按钮: 14
|
||||
Toggle标签: 12
|
||||
日志项: 11-12
|
||||
详情文本: 13-14
|
||||
```
|
||||
|
||||
### 间距
|
||||
```
|
||||
ControlPanel Spacing: 10
|
||||
LogContent Spacing: 2
|
||||
Padding: 10
|
||||
```
|
||||
|
||||
## ⚡ 性能优化提示
|
||||
|
||||
1. **限制可见日志项**: 调整LogArea高度,减少同时渲染的日志数量
|
||||
2. **日志项高度**: 25-30像素最佳,太高会影响滚动性能
|
||||
3. **最大日志数**: 默认500行,可在ConsoleModule构造函数中修改maxLine
|
||||
4. **禁用不需要的过滤器**: 减少日志显示可以提升性能
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### Q: 日志不显示?
|
||||
A: 检查:
|
||||
1. Console Page是否已添加到pages字典
|
||||
2. 是否调用了UniversalDebugTool.Init()
|
||||
3. 日志项预制件是否正确配置
|
||||
|
||||
### Q: 滚动不工作?
|
||||
A: 检查:
|
||||
1. ScrollRect的Content是否正确设置
|
||||
2. Content是否有VerticalLayoutGroup
|
||||
3. Content Size Fitter是否启用
|
||||
|
||||
### Q: 点击日志无反应?
|
||||
A: 检查:
|
||||
1. 日志项是否有Toggle组件
|
||||
2. Toggle的Target Graphic是否设置
|
||||
3. EventSystem是否存在于场景中
|
||||
|
||||
### Q: 性能问题?
|
||||
A: 尝试:
|
||||
1. 减少maxLine值(默认500)
|
||||
2. 减少LogArea高度
|
||||
3. 使用Text而非TextMeshPro(性能更好但效果差)
|
||||
4. 关闭不需要的日志类型
|
||||
|
||||
## 📱 移动端优化
|
||||
|
||||
如果在移动设备上使用:
|
||||
|
||||
1. 增大日志项高度(35-40像素)
|
||||
2. 增大字体(14-16)
|
||||
3. 减少最大日志数(200-300行)
|
||||
4. 使用简化的日志格式(去掉帧数显示)
|
||||
|
||||
## ✨ 完成!
|
||||
|
||||
现在你已经有一个功能完整的Console控制台了!
|
||||
|
||||
测试所有功能:
|
||||
- ✅ 清除日志
|
||||
- ✅ 锁定/解锁滚动
|
||||
- ✅ 过滤不同类型日志
|
||||
- ✅ 点击查看详情
|
||||
- ✅ 滚动浏览日志
|
||||
|
||||
享受调试吧! 🎉
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1531af79f3e63374182c727d39576258
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,186 @@
|
||||
# Console模块 - 运行时UI创建完成
|
||||
|
||||
## ✅ 已完成
|
||||
|
||||
Console模块的UI已经添加到RuntimeUIGenerator中,会在运行时自动创建。
|
||||
|
||||
## 🎯 使用方法
|
||||
|
||||
### 1. 直接运行测试
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using MeowmentDebugTool;
|
||||
|
||||
public class TestDebugTool : MonoBehaviour
|
||||
{
|
||||
void Start()
|
||||
{
|
||||
// 初始化调试工具(会自动创建所有UI包括Console)
|
||||
UniversalDebugTool.Init();
|
||||
|
||||
// 测试各种日志
|
||||
Debug.Log("这是一条普通日志");
|
||||
Debug.LogWarning("这是一条警告日志");
|
||||
Debug.LogError("这是一条错误日志");
|
||||
|
||||
// 测试异常
|
||||
try
|
||||
{
|
||||
throw new System.Exception("测试异常");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// 按空格键生成测试日志
|
||||
if (Input.GetKeyDown(KeyCode.Space))
|
||||
{
|
||||
Debug.Log($"测试日志 - 帧数: {Time.frameCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 运行游戏
|
||||
|
||||
1. 创建一个空GameObject
|
||||
2. 添加TestDebugTool脚本
|
||||
3. 运行游戏
|
||||
4. Console页面会自动出现在标签页中
|
||||
|
||||
### 3. 测试功能
|
||||
|
||||
- ✅ **清空按钮**: 清除所有日志
|
||||
- ✅ **锁定滚动**: 自动滚动到最新日志
|
||||
- ✅ **过滤器**: 显示/隐藏不同类型的日志
|
||||
- Info (白色)
|
||||
- Warning (黄色)
|
||||
- Error (红色)
|
||||
- Fatal (深红色)
|
||||
- ✅ **点击日志**: 在下方显示详细信息和堆栈跟踪
|
||||
- ✅ **实时计数**: Toggle显示每种日志的数量
|
||||
|
||||
## 📋 UI结构说明
|
||||
|
||||
RuntimeUIGenerator会创建以下结构:
|
||||
|
||||
```
|
||||
ConsolePage
|
||||
├── ControlPanel (控制面板 - 高度80)
|
||||
│ ├── ConsoleClearButton (清空按钮)
|
||||
│ ├── ConsoleLockScrollToggle (锁定滚动)
|
||||
│ ├── Spacer (弹性空白)
|
||||
│ ├── ConsoleInfoFilterToggle (Info过滤)
|
||||
│ ├── ConsoleWarningFilterToggle (Warning过滤)
|
||||
│ ├── ConsoleErrorFilterToggle (Error过滤)
|
||||
│ └── ConsoleFatalFilterToggle (Fatal过滤)
|
||||
│
|
||||
├── LogArea (60% 高度)
|
||||
│ └── ConsoleLogScrollView
|
||||
│ └── Viewport
|
||||
│ └── ConsoleLogContent (垂直布局组)
|
||||
│ └── [日志项动态生成]
|
||||
│
|
||||
└── DetailArea (40% 高度)
|
||||
└── ConsoleDetailScrollView
|
||||
└── Viewport
|
||||
└── Content
|
||||
└── ConsoleDetailText (详细信息)
|
||||
```
|
||||
|
||||
## 🎨 样式配置
|
||||
|
||||
### 颜色方案
|
||||
- **背景**: (12, 12, 12) - 深灰色
|
||||
- **日志区域**: (5, 5, 5) - 更深的灰色
|
||||
- **详情区域**: (8, 8, 8) - 中等深灰色
|
||||
- **日志项背景**: (12, 12, 12)
|
||||
- **日志项选中**: (25, 35, 45) - 蓝灰色
|
||||
|
||||
### 日志颜色(ConsoleModule中定义)
|
||||
- **Info**: (255, 255, 255) - 白色
|
||||
- **Warning**: (255, 255, 0) - 黄色
|
||||
- **Error**: (255, 0, 0) - 红色
|
||||
- **Fatal**: (178, 51, 51) - 深红色
|
||||
|
||||
### 字体大小
|
||||
- Toggle标签: 24
|
||||
- 日志项: 22
|
||||
- 详情文本: 24
|
||||
|
||||
## 🔧 自定义配置
|
||||
|
||||
如果需要修改样式,可以在RuntimeUIGenerator.cs中找到CreateConsolePage方法进行调整:
|
||||
|
||||
### 修改日志项高度
|
||||
```csharp
|
||||
// 在CreateConsoleLogItemPrefab方法中
|
||||
rect.sizeDelta = new Vector2(0, 30); // 改为你想要的高度
|
||||
```
|
||||
|
||||
### 修改区域比例
|
||||
```csharp
|
||||
// 在CreateConsolePage方法中
|
||||
logAreaLayout.flexibleHeight = 3; // 日志区域权重
|
||||
detailAreaLayout.flexibleHeight = 2; // 详情区域权重
|
||||
```
|
||||
|
||||
### 修改最大日志数
|
||||
```csharp
|
||||
// 在ConsoleModule.cs构造函数中
|
||||
private int maxLine = 500; // 改为你想要的数量
|
||||
```
|
||||
|
||||
## 🚀 性能提示
|
||||
|
||||
1. **默认500行限制**: 超过的日志会自动删除
|
||||
2. **对象池优化**: 日志项会被重用,不会频繁创建销毁
|
||||
3. **按需刷新**: 只在日志变化或过滤器改变时刷新UI
|
||||
4. **适中的日志项高度**: 30像素平衡了可读性和性能
|
||||
|
||||
## 📱 移动端建议
|
||||
|
||||
如果在移动设备上运行:
|
||||
|
||||
```csharp
|
||||
// 在CreateConsoleLogItemPrefab中调整
|
||||
labelTmp.fontSize = 28; // 增大字体
|
||||
rect.sizeDelta = new Vector2(0, 40); // 增加高度
|
||||
```
|
||||
|
||||
## 🐛 调试技巧
|
||||
|
||||
### 查看Console模块是否初始化成功
|
||||
```csharp
|
||||
if (UniversalDebugTool.InstanceExists)
|
||||
{
|
||||
var console = UniversalDebugTool.Instance.GetType()
|
||||
.GetField("consoleModule", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
||||
?.GetValue(UniversalDebugTool.Instance);
|
||||
|
||||
if (console != null)
|
||||
{
|
||||
Debug.Log("✅ Console模块已初始化");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 检查UI是否创建
|
||||
在Hierarchy中查找:
|
||||
- UniversalDebugTool_Canvas
|
||||
- MainWindow
|
||||
- ContentContainer
|
||||
- ConsolePage ← 应该能找到这个
|
||||
|
||||
## ✨ 完成!
|
||||
|
||||
现在你可以直接运行游戏测试Console模块了!
|
||||
|
||||
所有的UI都会在调用 `UniversalDebugTool.Init()` 时自动创建。
|
||||
|
||||
享受调试吧! 🎉
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa6bf6f6937d0ff4da7b444f776bf41e
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,59 +1,129 @@
|
||||
# MeowMent Debug Tool(通用调试工具)
|
||||
# MeowMent Debug Tool(喵刻调试工具)
|
||||
|
||||
一个面向运行时的 Unity 调试工具,内置多标签页调试面板、可拖拽悬浮按钮、设备/系统信息查看,以及通过特性自动生成的“自定义调试按钮”。
|
||||
一个强大的 Unity 运行时调试工具,提供多标签页调试面板、控制台日志、可拖拽悬浮按钮、系统信息查看以及自定义调试按钮等功能。
|
||||
|
||||
核心脚本:
|
||||
|
||||
- `Runtime/UniversalDebugTool.cs`
|
||||
- `Runtime/DraggableFloatingButton.cs`
|
||||
- `Editor/DebugToolPrefabGenerator.cs`
|
||||
- `Editor/CreateTabButtonPrefab.cs`
|
||||
**版本:** 0.3.0
|
||||
**适用平台:** 全平台(Windows、Android、iOS等)
|
||||
**Unity版本:** 2021.3 及以上
|
||||
|
||||
---
|
||||
|
||||
## 功能概览
|
||||
## 📋 目录
|
||||
|
||||
- 多标签页调试面板(默认分辨率 1080x2340):
|
||||
- 参数:设备信息、系统信息,一键复制到剪贴板。
|
||||
- 自定义按钮:通过特性自动收集项目中的调试方法并生成按钮。
|
||||
- 工具栏:简单的“时间调整”示例入口,可按项目需要扩展。
|
||||
- 设置:运行时修改调试窗口尺寸、重置为默认分辨率。
|
||||
- 悬浮调试按钮:
|
||||
- 关闭主窗口后,会显示一个可拖拽、自动吸附边缘的浮动按钮。
|
||||
- 点击可再次打开调试主窗口,拖动不会误触打开(内部做了拖拽判定)。
|
||||
- 运行时公共 API:
|
||||
- `UniversalDebugTool.Show() / Hide() / Toggle()` 控制显示。
|
||||
- `UniversalDebugTool.ShowInputDialog(...)` 弹出输入对话框。
|
||||
- `UniversalDebugTool.Instance.ReloadCustomButtons()` 重新扫描并生成自定义按钮。
|
||||
- [功能特性](#功能特性)
|
||||
- [给策划使用 - 基础功能](#给策划使用---基础功能)
|
||||
- [给客户端使用 - 初始化配置](#给客户端使用---初始化配置)
|
||||
- [给后端使用 - 添加自定义按钮](#给后端使用---添加自定义按钮)
|
||||
- [常见问题](#常见问题)
|
||||
|
||||
---
|
||||
|
||||
## 安装
|
||||
## 🎯 功能特性
|
||||
|
||||
### 方式一:本地拷贝(推荐在开发环境使用)
|
||||
### 核心功能模块
|
||||
- **参数查看**:实时查看设备信息、系统信息,支持一键复制
|
||||
- **自定义按钮**:通过特性自动生成调试按钮,无需手动添加
|
||||
- **工具栏**:时间调整工具、截图隐藏功能
|
||||
- **控制台**:实时捕获Unity日志,支持过滤、搜索、查看堆栈
|
||||
- **设置**:运行时修改分辨率
|
||||
|
||||
1. 将 `com.bywaystudios.meowmentdebugtool@0.1.4` 整个文件夹放到你项目的 `Packages/` 目录下。
|
||||
2. 打开 Unity,等待重新导入即可在菜单中看到调试工具相关项。
|
||||
|
||||
### 方式二:通过自建 Git / 私有仓库
|
||||
|
||||
如果你已经把此包发布到自己的 Git 仓库或私有 Package Registry,可参考 Unity 官方文档,通过修改 `Packages/manifest.json` 的 `dependencies` / `scopedRegistries` 来添加:
|
||||
|
||||
```jsonc
|
||||
"dependencies": {
|
||||
"com.bywaystudios.meowmentdebugtool": "0.1.4"
|
||||
}
|
||||
```
|
||||
|
||||
> 具体部署方式视团队实际环境而定,这里不展开。
|
||||
### UI特性
|
||||
- 可拖拽悬浮按钮,自动吸附边缘
|
||||
- 多标签页设计,清晰分类
|
||||
- Canvas层级30000,始终在最上层
|
||||
- 支持自定义TextMeshPro字体
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
## 👥 给策划使用 - 基础功能
|
||||
|
||||
### 1. 在代码中初始化调试工具
|
||||
### 1. 如何打开调试工具
|
||||
|
||||
调试工具现在支持**运行时自动生成UI**,无需手动创建预制体。只需在代码中调用:
|
||||
游戏运行后,屏幕左侧会显示一个**蓝色圆形浮窗按钮**:
|
||||
|
||||
- **点击浮窗** → 打开调试主窗口
|
||||
- **拖动浮窗** → 改变位置(会自动吸附到屏幕边缘)
|
||||
- **点击左上角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;
|
||||
@ -62,14 +132,14 @@ using TMPro;
|
||||
|
||||
public class GameInitializer : MonoBehaviour
|
||||
{
|
||||
public TMP_FontAsset customFont; // 可选:自定义字体
|
||||
[SerializeField] private TMP_FontAsset customFont; // 拖入自定义字体
|
||||
|
||||
void Start()
|
||||
{
|
||||
// 初始化调试工具(自动创建UI)
|
||||
// 初始化调试工具
|
||||
UniversalDebugTool.Init();
|
||||
|
||||
// 可选:设置自定义SDF字体
|
||||
// 设置自定义字体(可选)
|
||||
if (customFont != null)
|
||||
{
|
||||
UniversalDebugTool.SetSDFFont(customFont);
|
||||
@ -78,27 +148,34 @@ public class GameInitializer : MonoBehaviour
|
||||
}
|
||||
```
|
||||
|
||||
**初始化说明:**
|
||||
### 2. API说明
|
||||
|
||||
- `UniversalDebugTool.Init()` 会自动创建完整的UI系统,包括:
|
||||
- 独立的 Canvas(排序顺序 30000,保证在最上层渲染)
|
||||
- 主调试窗口(含标签页、参数页、自定义按钮页、工具栏页、设置页)
|
||||
- 可拖拽的调试悬浮按钮
|
||||
- EventSystem(如果场景中没有)
|
||||
- `UniversalDebugTool.SetSDFFont()` 可以为所有UI文本设置自定义字体(可选)
|
||||
#### `UniversalDebugTool.Init()`
|
||||
初始化调试工具,自动创建:
|
||||
- Canvas(排序顺序30000)
|
||||
- 主调试窗口
|
||||
- 悬浮按钮
|
||||
- EventSystem(如果没有)
|
||||
|
||||
### 2. 运行时使用
|
||||
**注意:**
|
||||
- 只需调用一次
|
||||
- 工具会自动DontDestroyOnLoad,场景切换不销毁
|
||||
- 未调用Init()前不显示任何UI
|
||||
|
||||
进入 Play 模式后:
|
||||
#### `UniversalDebugTool.SetSDFFont(TMP_FontAsset fontAsset)`
|
||||
设置所有UI文本的字体:
|
||||
- 包括已创建的UI和后续创建的按钮
|
||||
- 支持中文、特殊字符
|
||||
- 建议使用支持中文的SDF字体
|
||||
|
||||
- 默认显示主调试窗口在左上角
|
||||
- 点击左上角的 **X** 按钮,会隐藏主窗口并显示悬浮按钮
|
||||
- 拖动悬浮按钮改变位置(自动吸附边缘)
|
||||
- 点击悬浮按钮可重新打开主窗口(拖动不会误触)
|
||||
**字体推荐:**
|
||||
- 思源黑体 SDF
|
||||
- 阿里巴巴普惠体 SDF
|
||||
- 或项目现有的中文字体
|
||||
|
||||
### 3. 条件编译(推荐)
|
||||
|
||||
为了确保在移除包后不影响主工程,建议使用条件编译:
|
||||
为了确保打包时不包含调试代码:
|
||||
|
||||
```csharp
|
||||
void Start()
|
||||
@ -110,166 +187,361 @@ void Start()
|
||||
}
|
||||
```
|
||||
|
||||
`MEOWMENT_DEBUG_TOOL` 宏会在包安装时自动定义,卸载时自动移除。
|
||||
**说明:**
|
||||
- `MEOWMENT_DEBUG_TOOL` 宏在包安装时自动定义
|
||||
- 移除包时自动移除,不影响项目编译
|
||||
- 正式包不会包含调试工具代码
|
||||
|
||||
---
|
||||
|
||||
## 自定义调试按钮
|
||||
|
||||
调试工具会在运行时通过反射扫描所有程序集,查找带有 `DebugButtonAttribute` 特性的方法,并在“自定义按钮”标签页中为其自动生成一个按钮。
|
||||
|
||||
### 编写一个自定义调试方法
|
||||
|
||||
> 要求:
|
||||
> - 方法必须是 `static`。
|
||||
> - 当前实现假定为**无参数**方法。如果需要参数,可结合 `ShowInputDialog` 自行封装。
|
||||
|
||||
示例:
|
||||
### 4. 完整示例
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using MeowmentDebugTool;
|
||||
using TMPro;
|
||||
|
||||
public static class DemoDebugActions
|
||||
public class Test : MonoBehaviour
|
||||
{
|
||||
// 在“自定义按钮”页中会生成一个名为“打印金币数量”的按钮
|
||||
[DebugButton("打印金币数量", 0.3f, 0.6f, 1f)]
|
||||
private static void PrintCoinCount()
|
||||
public TMP_FontAsset fontAsset;
|
||||
|
||||
void Start()
|
||||
{
|
||||
Debug.Log($"Coins: {PlayerData.Coin}");
|
||||
}
|
||||
|
||||
// 不传 displayName 时,按钮文字默认使用方法名
|
||||
[DebugButton]
|
||||
private static void KillAllEnemies()
|
||||
{
|
||||
EnemyManager.KillAll();
|
||||
#if MEOWMENT_DEBUG_TOOL
|
||||
// 初始化调试工具
|
||||
UniversalDebugTool.Init();
|
||||
|
||||
// 设置字体
|
||||
if (fontAsset != null)
|
||||
{
|
||||
UniversalDebugTool.SetSDFFont(fontAsset);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`DebugButtonAttribute` 构造函数签名:
|
||||
|
||||
```csharp
|
||||
public DebugButtonAttribute(string displayName = "", float r = 0.8f, float g = 0.8f, float b = 0.8f);
|
||||
```
|
||||
|
||||
- `displayName`:按钮显示文字(可为空,为空则使用方法名)。
|
||||
- `r g b`:按钮背景色的 RGB 分量(0~1)。
|
||||
|
||||
写好方法后:
|
||||
|
||||
1. 进入 Play 模式。
|
||||
2. 打开调试工具 → 切换到“自定义按钮”标签页。
|
||||
3. 即可看到刚才添加的调试按钮,点击执行对应逻辑。
|
||||
|
||||
如果你在运行时新增/修改了带有 `DebugButtonAttribute` 的方法(如通过热更等方式),可以调用:
|
||||
|
||||
```csharp
|
||||
UniversalDebugTool.Instance.ReloadCustomButtons();
|
||||
```
|
||||
|
||||
来重新扫描并刷新按钮列表。
|
||||
|
||||
---
|
||||
|
||||
## 输入对话框(可选)
|
||||
## 🔧 给后端使用 - 添加自定义按钮
|
||||
|
||||
调试工具内置了一个简单的输入对话框,可与自定义按钮组合使用:
|
||||
### 1. 基础用法
|
||||
|
||||
使用 `[DebugButton]` 特性标记静态方法,调试工具会自动生成按钮:
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
|
||||
public static class PlayerDebugFunctions
|
||||
{
|
||||
[DebugButton("清空玩家数据")]
|
||||
private static void ClearPlayerData()
|
||||
{
|
||||
PlayerPrefs.DeleteAll();
|
||||
Debug.Log("玩家数据已清空");
|
||||
}
|
||||
|
||||
[DebugButton("添加1000金币")]
|
||||
private static void AddCoins()
|
||||
{
|
||||
PlayerData.Coins += 1000;
|
||||
Debug.Log($"当前金币:{PlayerData.Coins}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**运行效果:**
|
||||
- 在"自定义按钮"页面会出现两个按钮
|
||||
- 点击按钮执行对应方法
|
||||
|
||||
### 2. 特性参数详解
|
||||
|
||||
```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 DemoInputDebug
|
||||
public static class AdvancedDebugFunctions
|
||||
{
|
||||
[DebugButton("设置玩家等级")]
|
||||
private static void SetPlayerLevel()
|
||||
{
|
||||
UniversalDebugTool.ShowInputDialog(
|
||||
"输入玩家等级",
|
||||
"输入等级",
|
||||
onConfirmAction: text =>
|
||||
{
|
||||
if (int.TryParse(text, out var level))
|
||||
if (int.TryParse(text, out int level))
|
||||
{
|
||||
PlayerData.Level = level;
|
||||
Debug.Log($"设置玩家等级为: {level}");
|
||||
Debug.Log($"等级已设置为:{level}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"无效等级: {text}");
|
||||
Debug.LogWarning("请输入有效数字");
|
||||
}
|
||||
},
|
||||
initialValue: "1",
|
||||
contentType: TMP_InputField.ContentType.IntegerNumber
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
调用后会弹出一个输入框,用户点击确认时会把输入字符串传回回调函数中。
|
||||
|
||||
---
|
||||
|
||||
## 公共 API 一览
|
||||
|
||||
以下方法均定义在 `UniversalDebugTool` 中:
|
||||
|
||||
**初始化和配置:**
|
||||
- `static void Init()`:初始化调试工具,自动创建完整的UI系统(包括Canvas、主窗口、悬浮按钮、EventSystem等)。
|
||||
- `static void SetSDFFont(TMP_FontAsset fontAsset)`:为所有UI文本设置自定义SDF字体,包括预制件模板和后续创建的按钮。
|
||||
|
||||
**显示控制:**
|
||||
- `static bool InstanceExists`:当前场景中是否存在实例。
|
||||
- `static UniversalDebugTool Instance`:单例实例。
|
||||
- `static void Show()`:显示调试工具 GameObject。
|
||||
- `static void Hide()`:隐藏调试工具 GameObject。
|
||||
- `static void Toggle()`:在"窗口显示"和"窗口关闭 + 显示悬浮按钮"之间切换。
|
||||
|
||||
**自定义按钮:**
|
||||
- `void ReloadCustomButtons()`:重新扫描并生成自定义按钮。
|
||||
|
||||
**输入对话框:**
|
||||
- `static void ShowInputDialog(string title, Action<string> onConfirmAction, string initialValue = "", TMP_InputField.ContentType contentType = TMP_InputField.ContentType.Standard)`:显示输入对话框。
|
||||
- `static void CloseInputDialog()`:关闭输入对话框并清理回调。
|
||||
|
||||
**信息查看:**
|
||||
- `void CopyDeviceInfoToClipboard()` / `void CopySystemInfoToClipboard()`:复制对应信息到系统剪贴板。
|
||||
|
||||
你也可以在自己项目的 UI / 快捷键逻辑中调用这些方法,例如:
|
||||
|
||||
```csharp
|
||||
// 示例:按 F1 切换调试面板
|
||||
void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F1))
|
||||
|
||||
[DebugButton("设置玩家名称")]
|
||||
private static void SetPlayerName()
|
||||
{
|
||||
UniversalDebugTool.Toggle();
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
## ❓ 常见问题
|
||||
|
||||
**依赖:**
|
||||
- Unity 新 UI(`UnityEngine.UI`)
|
||||
- TextMesh Pro(`TMPro`)
|
||||
- 包会自动创建 EventSystem(如果场景中没有)
|
||||
### Q1: 字体显示方块/乱码怎么办?
|
||||
**A:** 调用 `UniversalDebugTool.SetSDFFont()` 设置支持中文的SDF字体。
|
||||
|
||||
**使用建议:**
|
||||
- 默认 UI 参考分辨率为 1080×2340,适合手机竖屏调试。可在"设置"页中修改窗口大小以适配不同设备。
|
||||
- 主窗口锚点在左上角,不受分辨率变化影响。
|
||||
- 悬浮按钮可在全屏范围内拖动,会自动吸附到边缘。
|
||||
- 工具栏中的"时间调整"逻辑仅是示例,需要在 `AdjustGameTime` 中实现实际的时间修改逻辑。
|
||||
### Q2: 自定义按钮不显示?
|
||||
**A:** 检查:
|
||||
- 方法是否为 `static`
|
||||
- 是否添加了 `[DebugButton]` 特性
|
||||
- 是否在 `#if MEOWMENT_DEBUG_TOOL` 内
|
||||
|
||||
**字体设置:**
|
||||
- 所有 UI 文本默认使用 TMP 的默认字体。
|
||||
- 调用 `SetSDFFont()` 可以为所有文本(包括后续创建的自定义按钮)设置自定义字体。
|
||||
### Q3: 第二次点击"暂时隐藏"无效?
|
||||
**A:** 等待上一次隐藏结束,或查看控制台是否有"已经在隐藏状态中"的警告。
|
||||
|
||||
**条件编译:**
|
||||
- 建议使用 `#if MEOWMENT_DEBUG_TOOL` 包裹调试工具相关代码。
|
||||
- 包安装时会自动定义 `MEOWMENT_DEBUG_TOOL` 宏,卸载时自动移除。
|
||||
- 这样可以确保移除包后不影响主工程编译。
|
||||
### Q4: 如何在正式版移除调试工具?
|
||||
**A:** 删除Packages目录下的工具包文件夹即可,`#if MEOWMENT_DEBUG_TOOL` 包裹的代码会自动失效。
|
||||
|
||||
**进一步定制:**
|
||||
如需修改 UI 布局、颜色、标签页等,可以直接修改 `RuntimeUIGenerator.cs` 中的 UI 生成逻辑。
|
||||
### 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)
|
||||
|
||||
---
|
||||
|
||||
## 📧 联系方式
|
||||
|
||||
如有问题或建议,请联系开发团队。
|
||||
|
||||
---
|
||||
|
||||
**祝调试顺利!** 🐱
|
||||
|
||||
111
Packages/com.bywaystudios.meowmentdebugtool/REFACTORING_NOTES.md
Normal file
111
Packages/com.bywaystudios.meowmentdebugtool/REFACTORING_NOTES.md
Normal file
@ -0,0 +1,111 @@
|
||||
# MeowmentDebugTool 模块化重构说明
|
||||
|
||||
## 重构概述
|
||||
|
||||
已将 UniversalDebugTool 的4个主要功能模块分离成独立的脚本文件,使代码结构更清晰、更易维护。
|
||||
|
||||
## 新增文件
|
||||
|
||||
### 1. IDebugModule.cs
|
||||
- **作用**: 调试模块的基础接口
|
||||
- **方法**:
|
||||
- `Initialize()`: 初始化模块
|
||||
- `GetPage()`: 获取模块的UI页面GameObject
|
||||
- `GetModuleName()`: 获取模块名称
|
||||
|
||||
### 2. ParametersModule.cs
|
||||
- **作用**: 参数查看模块,显示设备和系统信息
|
||||
- **主要功能**:
|
||||
- 显示设备信息(设备名称、型号、处理器等)
|
||||
- 显示系统信息(Unity版本、平台、分辨率、图形设备等)
|
||||
- 复制信息到剪贴板
|
||||
- 刷新信息
|
||||
|
||||
### 3. CustomButtonsModule.cs
|
||||
- **作用**: 自定义按钮模块,通过反射加载标记为DebugButton的方法
|
||||
- **主要功能**:
|
||||
- 自动扫描并加载所有带 `[DebugButton]` 特性的静态方法
|
||||
- 动态创建按钮UI
|
||||
- 设置SDF字体
|
||||
- 支持自定义按钮回调
|
||||
- 点击按钮后自动关闭调试窗口
|
||||
|
||||
### 4. ToolbarModule.cs
|
||||
- **作用**: 工具栏模块,提供时间调整等工具
|
||||
- **主要功能**:
|
||||
- 时间调整滑块(支持秒和分钟显示)
|
||||
- 增加/减少时间按钮
|
||||
- 可扩展的游戏时间调整接口
|
||||
|
||||
### 5. SettingsModule.cs
|
||||
- **作用**: 设置模块,提供分辨率设置等功能
|
||||
- **主要功能**:
|
||||
- 自定义窗口分辨率
|
||||
- 重置为默认分辨率
|
||||
- 实时显示当前窗口尺寸
|
||||
|
||||
## UniversalDebugTool.cs 的改动
|
||||
|
||||
### 主要变化
|
||||
|
||||
1. **移除了具体实现代码**:
|
||||
- 参数查看功能 → ParametersModule
|
||||
- 自定义按钮功能 → CustomButtonsModule
|
||||
- 工具栏功能 → ToolbarModule
|
||||
- 分辨率设置功能 → SettingsModule
|
||||
|
||||
2. **新增模块管理**:
|
||||
```csharp
|
||||
private ParametersModule parametersModule;
|
||||
private CustomButtonsModule customButtonsModule;
|
||||
private ToolbarModule toolbarModule;
|
||||
private SettingsModule settingsModule;
|
||||
private List<IDebugModule> allModules = new List<IDebugModule>();
|
||||
```
|
||||
|
||||
3. **初始化流程**:
|
||||
- `InitializeDebugTool()`: 创建所有模块实例并注册页面
|
||||
- `InitializeAllModules()`: 调用各模块的Initialize方法
|
||||
|
||||
4. **保留的公共API**:
|
||||
- `CopyDeviceInfoToClipboard()` - 委托给 ParametersModule
|
||||
- `CopySystemInfoToClipboard()` - 委托给 ParametersModule
|
||||
- `RefreshAllInfo()` - 委托给 ParametersModule
|
||||
- `SetCustomButtonCallback()` - 委托给 CustomButtonsModule
|
||||
- `ReloadCustomButtons()` - 委托给 CustomButtonsModule
|
||||
|
||||
## 优势
|
||||
|
||||
1. **职责分离**: 每个模块只负责一个功能领域
|
||||
2. **易于维护**: 修改某个功能时只需要编辑对应的模块文件
|
||||
3. **可扩展性**: 添加新模块只需实现 IDebugModule 接口
|
||||
4. **代码清晰**: UniversalDebugTool.cs 现在主要负责协调各模块,代码更简洁
|
||||
|
||||
## 使用方式
|
||||
|
||||
使用方式完全不变,所有现有的API和功能保持兼容:
|
||||
|
||||
```csharp
|
||||
// 初始化调试工具
|
||||
UniversalDebugTool.Init();
|
||||
|
||||
// 设置字体
|
||||
UniversalDebugTool.SetSDFFont(myFont);
|
||||
|
||||
// 刷新信息
|
||||
UniversalDebugTool.Instance.RefreshAllInfo();
|
||||
|
||||
// 自定义按钮仍然使用相同的特性
|
||||
[DebugButton("测试按钮")]
|
||||
public static void TestMethod()
|
||||
{
|
||||
Debug.Log("测试");
|
||||
}
|
||||
```
|
||||
|
||||
## 未来扩展
|
||||
|
||||
如果需要添加新的调试功能模块,只需:
|
||||
1. 创建新的类并实现 `IDebugModule` 接口
|
||||
2. 在 `UniversalDebugTool.InitializeDebugTool()` 中实例化并添加到 `allModules` 列表
|
||||
3. 调用 `InitializeAllModules()` 会自动初始化新模块
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a75e76e7a8253b4a97f7c353af12a0f
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,579 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
namespace MeowmentDebugTool
|
||||
{
|
||||
/// <summary>
|
||||
/// 控制台模块 - 显示Unity日志(基于UGUI实现)
|
||||
/// </summary>
|
||||
public class ConsoleModule : IDebugModule
|
||||
{
|
||||
#region 字段
|
||||
private GameObject consolePage;
|
||||
|
||||
// UI组件
|
||||
private ScrollRect logScrollRect;
|
||||
private RectTransform logContent;
|
||||
private ScrollRect detailScrollRect;
|
||||
private TMP_Text detailText;
|
||||
|
||||
// 按钮
|
||||
private Button clearButton;
|
||||
private Toggle lockScrollToggle;
|
||||
private Toggle infoFilterToggle;
|
||||
private Toggle warningFilterToggle;
|
||||
private Toggle errorFilterToggle;
|
||||
private Toggle fatalFilterToggle;
|
||||
|
||||
// 日志项预制件
|
||||
private GameObject logItemPrefab;
|
||||
|
||||
// 日志数据
|
||||
private Queue<LogNode> logNodes = new Queue<LogNode>();
|
||||
private LogNode selectedNode = null;
|
||||
|
||||
// 日志计数
|
||||
private int infoCount = 0;
|
||||
private int warningCount = 0;
|
||||
private int errorCount = 0;
|
||||
private int fatalCount = 0;
|
||||
|
||||
// 设置
|
||||
private bool lockScroll = true;
|
||||
private int maxLine = 200;
|
||||
private bool infoFilter = true;
|
||||
private bool warningFilter = true;
|
||||
private bool errorFilter = true;
|
||||
private bool fatalFilter = true;
|
||||
|
||||
// 颜色设置
|
||||
private Color32 infoColor = Color.white;
|
||||
private Color32 warningColor = Color.yellow;
|
||||
private Color32 errorColor = Color.red;
|
||||
private Color32 fatalColor = new Color(0.7f, 0.2f, 0.2f);
|
||||
|
||||
// UI对象池
|
||||
private List<GameObject> logItemPool = new List<GameObject>();
|
||||
private List<GameObject> activeLogItems = new List<GameObject>();
|
||||
|
||||
// 保存的字体
|
||||
private TMP_FontAsset savedFontAsset = null;
|
||||
|
||||
// 延迟刷新标记
|
||||
private bool needRefresh = false;
|
||||
private int lastRefreshFrame = -1;
|
||||
#endregion
|
||||
|
||||
#region 构造函数
|
||||
public ConsoleModule(GameObject page, ScrollRect logScroll, RectTransform logContentTransform,
|
||||
ScrollRect detailScroll, TMP_Text detail,
|
||||
Button clearBtn, Toggle lockToggle, Toggle infoToggle, Toggle warningToggle,
|
||||
Toggle errorToggle, Toggle fatalToggle, GameObject logItemPrefabObj)
|
||||
{
|
||||
consolePage = page;
|
||||
logScrollRect = logScroll;
|
||||
logContent = logContentTransform;
|
||||
detailScrollRect = detailScroll;
|
||||
detailText = detail;
|
||||
clearButton = clearBtn;
|
||||
lockScrollToggle = lockToggle;
|
||||
infoFilterToggle = infoToggle;
|
||||
warningFilterToggle = warningToggle;
|
||||
errorFilterToggle = errorToggle;
|
||||
fatalFilterToggle = fatalToggle;
|
||||
logItemPrefab = logItemPrefabObj;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDebugModule 实现
|
||||
public void Initialize()
|
||||
{
|
||||
Debug.Log("[ConsoleModule] 初始化控制台模块...");
|
||||
|
||||
// 检查必要的组件
|
||||
if (consolePage == null)
|
||||
Debug.LogError("[ConsoleModule] consolePage is null!");
|
||||
if (logScrollRect == null)
|
||||
Debug.LogError("[ConsoleModule] logScrollRect is null!");
|
||||
if (logContent == null)
|
||||
Debug.LogError("[ConsoleModule] logContent is null!");
|
||||
if (detailScrollRect == null)
|
||||
Debug.LogError("[ConsoleModule] detailScrollRect is null!");
|
||||
if (detailText == null)
|
||||
Debug.LogError("[ConsoleModule] detailText is null!");
|
||||
if (clearButton == null)
|
||||
Debug.LogError("[ConsoleModule] clearButton is null!");
|
||||
if (logItemPrefab == null)
|
||||
Debug.LogError("[ConsoleModule] logItemPrefab is null!");
|
||||
|
||||
// 注册Unity日志回调
|
||||
Application.logMessageReceived += OnLogMessageReceived;
|
||||
|
||||
// 设置按钮事件
|
||||
if (clearButton != null)
|
||||
clearButton.onClick.AddListener(ClearAllLogs);
|
||||
|
||||
// 设置Toggle事件
|
||||
if (lockScrollToggle != null)
|
||||
{
|
||||
lockScrollToggle.isOn = lockScroll;
|
||||
lockScrollToggle.onValueChanged.AddListener(OnLockScrollChanged);
|
||||
}
|
||||
|
||||
if (infoFilterToggle != null)
|
||||
{
|
||||
infoFilterToggle.isOn = infoFilter;
|
||||
infoFilterToggle.onValueChanged.AddListener(OnInfoFilterChanged);
|
||||
}
|
||||
|
||||
if (warningFilterToggle != null)
|
||||
{
|
||||
warningFilterToggle.isOn = warningFilter;
|
||||
warningFilterToggle.onValueChanged.AddListener(OnWarningFilterChanged);
|
||||
}
|
||||
|
||||
if (errorFilterToggle != null)
|
||||
{
|
||||
errorFilterToggle.isOn = errorFilter;
|
||||
errorFilterToggle.onValueChanged.AddListener(OnErrorFilterChanged);
|
||||
}
|
||||
|
||||
if (fatalFilterToggle != null)
|
||||
{
|
||||
fatalFilterToggle.isOn = fatalFilter;
|
||||
fatalFilterToggle.onValueChanged.AddListener(OnFatalFilterChanged);
|
||||
}
|
||||
|
||||
// 清空详情
|
||||
if (detailText != null)
|
||||
detailText.text = "点击日志查看详细信息...";
|
||||
|
||||
Debug.Log("[ConsoleModule] 控制台模块初始化完成");
|
||||
}
|
||||
|
||||
public GameObject GetPage()
|
||||
{
|
||||
return consolePage;
|
||||
}
|
||||
|
||||
public string GetModuleName()
|
||||
{
|
||||
return "控制台";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 公共方法
|
||||
/// <summary>
|
||||
/// 设置SDF字体
|
||||
/// </summary>
|
||||
public void SetSDFFont(TMP_FontAsset fontAsset)
|
||||
{
|
||||
savedFontAsset = fontAsset;
|
||||
|
||||
// 应用到详情文本
|
||||
if (detailText != null && fontAsset != null)
|
||||
{
|
||||
detailText.font = fontAsset;
|
||||
}
|
||||
|
||||
// 刷新已有日志项
|
||||
RefreshLogDisplay();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取日志统计信息
|
||||
/// </summary>
|
||||
public void GetLogCount(out int info, out int warning, out int error, out int fatal)
|
||||
{
|
||||
RefreshCount();
|
||||
info = infoCount;
|
||||
warning = warningCount;
|
||||
error = errorCount;
|
||||
fatal = fatalCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新(每帧调用)
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
// 如果页面未激活,跳过更新
|
||||
if (consolePage == null || !consolePage.activeSelf)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 先处理延迟刷新
|
||||
if (needRefresh)
|
||||
{
|
||||
needRefresh = false;
|
||||
lastRefreshFrame = Time.frameCount;
|
||||
RefreshLogDisplay();
|
||||
}
|
||||
|
||||
// 如果锁定滚动,自动滚动到底部
|
||||
// 只在刷新后的下一帧设置,避免rebuild loop
|
||||
if (lockScroll && logScrollRect != null && Time.frameCount > lastRefreshFrame + 1)
|
||||
{
|
||||
logScrollRect.verticalNormalizedPosition = 0f;
|
||||
}
|
||||
|
||||
// 更新Toggle文本显示
|
||||
UpdateFilterToggleText();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 销毁
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
Application.logMessageReceived -= OnLogMessageReceived;
|
||||
ClearAllLogs();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 私有方法
|
||||
private void OnLogMessageReceived(string logMessage, string stackTrace, LogType logType)
|
||||
{
|
||||
// 将Assert转换为Error
|
||||
if (logType == LogType.Assert)
|
||||
{
|
||||
logType = LogType.Error;
|
||||
}
|
||||
|
||||
// 创建日志节点
|
||||
LogNode logNode = LogNode.Create(logType, logMessage, stackTrace);
|
||||
logNodes.Enqueue(logNode);
|
||||
|
||||
// 限制最大行数
|
||||
while (logNodes.Count > maxLine)
|
||||
{
|
||||
logNodes.Dequeue();
|
||||
}
|
||||
|
||||
// 标记需要刷新,而不是立即刷新(避免rebuild loop)
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
private void RefreshLogDisplay()
|
||||
{
|
||||
// 如果页面未激活,延迟刷新到下次页面激活时
|
||||
if (consolePage == null || !consolePage.activeSelf)
|
||||
{
|
||||
needRefresh = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (logContent == null)
|
||||
{
|
||||
Debug.LogError("[ConsoleModule] logContent is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (logItemPrefab == null)
|
||||
{
|
||||
Debug.LogError("[ConsoleModule] logItemPrefab is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 回收所有激活的日志项
|
||||
foreach (var item in activeLogItems)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
item.SetActive(false);
|
||||
if (!logItemPool.Contains(item))
|
||||
{
|
||||
logItemPool.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
activeLogItems.Clear();
|
||||
|
||||
// 遍历日志节点并创建UI
|
||||
int index = 0;
|
||||
int displayCount = 0;
|
||||
foreach (LogNode logNode in logNodes)
|
||||
{
|
||||
// 根据过滤器判断是否显示
|
||||
if (!ShouldShowLog(logNode.LogType))
|
||||
continue;
|
||||
|
||||
// 从对象池获取或创建日志项
|
||||
GameObject logItem = GetLogItemFromPool();
|
||||
if (logItem == null)
|
||||
{
|
||||
Debug.LogError("[ConsoleModule] Failed to get log item from pool!");
|
||||
continue;
|
||||
}
|
||||
|
||||
logItem.transform.SetParent(logContent, false);
|
||||
logItem.SetActive(true);
|
||||
activeLogItems.Add(logItem);
|
||||
|
||||
// 调试:确认对象状态
|
||||
if (!logItem.activeSelf)
|
||||
{
|
||||
Debug.LogWarning($"[ConsoleModule] LogItem {logItem.name} is not active after SetActive(true)!");
|
||||
}
|
||||
|
||||
// 设置日志项内容
|
||||
SetupLogItem(logItem, logNode, index);
|
||||
|
||||
index++;
|
||||
displayCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldShowLog(LogType logType)
|
||||
{
|
||||
switch (logType)
|
||||
{
|
||||
case LogType.Log:
|
||||
return infoFilter;
|
||||
case LogType.Warning:
|
||||
return warningFilter;
|
||||
case LogType.Error:
|
||||
return errorFilter;
|
||||
case LogType.Exception:
|
||||
return fatalFilter;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject GetLogItemFromPool()
|
||||
{
|
||||
if (logItemPool.Count > 0)
|
||||
{
|
||||
GameObject item = logItemPool[0];
|
||||
logItemPool.RemoveAt(0);
|
||||
return item;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logItemPrefab == null)
|
||||
{
|
||||
Debug.LogError("[ConsoleModule] logItemPrefab is null!");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建新的日志项
|
||||
GameObject newItem = UnityEngine.Object.Instantiate(logItemPrefab);
|
||||
newItem.name = "LogItem_" + activeLogItems.Count;
|
||||
return newItem;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupLogItem(GameObject logItem, LogNode logNode, int index)
|
||||
{
|
||||
if (logItem == null)
|
||||
{
|
||||
Debug.LogError("[ConsoleModule] logItem is null in SetupLogItem!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取日志项的Toggle和Text组件
|
||||
Toggle toggle = logItem.GetComponent<Toggle>();
|
||||
TMP_Text text = logItem.GetComponentInChildren<TMP_Text>();
|
||||
|
||||
if (toggle == null)
|
||||
{
|
||||
Debug.LogWarning($"[ConsoleModule] Toggle component not found on {logItem.name}!");
|
||||
}
|
||||
|
||||
if (text == null)
|
||||
{
|
||||
Debug.LogWarning($"[ConsoleModule] TMP_Text component not found on {logItem.name}!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置文本内容和颜色
|
||||
Color32 color = GetLogStringColor(logNode.LogType);
|
||||
string logText = GetLogString(logNode);
|
||||
|
||||
text.text = logText;
|
||||
text.color = color;
|
||||
|
||||
// 应用字体
|
||||
if (savedFontAsset != null)
|
||||
{
|
||||
text.font = savedFontAsset;
|
||||
}
|
||||
|
||||
if (toggle != null)
|
||||
{
|
||||
// 设置Toggle状态
|
||||
toggle.isOn = (selectedNode == logNode);
|
||||
|
||||
// 设置Toggle事件
|
||||
toggle.onValueChanged.RemoveAllListeners();
|
||||
toggle.onValueChanged.AddListener((isOn) =>
|
||||
{
|
||||
if (isOn)
|
||||
{
|
||||
OnLogItemSelected(logNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLogItemSelected(LogNode logNode)
|
||||
{
|
||||
selectedNode = logNode;
|
||||
|
||||
// 显示详细信息
|
||||
if (detailText != null && logNode != null)
|
||||
{
|
||||
Color32 color = GetLogStringColor(logNode.LogType);
|
||||
string colorHex = ColorUtility.ToHtmlStringRGBA(color);
|
||||
|
||||
string detailInfo = $"<color=#{colorHex}><b>{logNode.LogMessage}</b></color>\n\n";
|
||||
detailInfo += $"<color=#888888>时间: {logNode.LogTime.ToLocalTime():HH:mm:ss.fff}\n";
|
||||
detailInfo += $"帧数: {logNode.LogFrameCount}\n";
|
||||
detailInfo += $"类型: {logNode.LogType}</color>\n\n";
|
||||
|
||||
if (!string.IsNullOrEmpty(logNode.StackTrace))
|
||||
{
|
||||
detailInfo += $"<color=#AAAAAA>堆栈跟踪:\n{logNode.StackTrace}</color>";
|
||||
}
|
||||
|
||||
detailText.text = detailInfo;
|
||||
}
|
||||
|
||||
// 重置详情滚动位置
|
||||
if (detailScrollRect != null)
|
||||
{
|
||||
detailScrollRect.verticalNormalizedPosition = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetLogString(LogNode logNode)
|
||||
{
|
||||
return $"[{logNode.LogTime.ToLocalTime():HH:mm:ss.fff}][{logNode.LogFrameCount}] {logNode.LogMessage}";
|
||||
}
|
||||
|
||||
private Color32 GetLogStringColor(LogType logType)
|
||||
{
|
||||
switch (logType)
|
||||
{
|
||||
case LogType.Log:
|
||||
return infoColor;
|
||||
case LogType.Warning:
|
||||
return warningColor;
|
||||
case LogType.Error:
|
||||
return errorColor;
|
||||
case LogType.Exception:
|
||||
return fatalColor;
|
||||
default:
|
||||
return Color.white;
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshCount()
|
||||
{
|
||||
infoCount = 0;
|
||||
warningCount = 0;
|
||||
errorCount = 0;
|
||||
fatalCount = 0;
|
||||
|
||||
foreach (LogNode logNode in logNodes)
|
||||
{
|
||||
switch (logNode.LogType)
|
||||
{
|
||||
case LogType.Log:
|
||||
infoCount++;
|
||||
break;
|
||||
case LogType.Warning:
|
||||
warningCount++;
|
||||
break;
|
||||
case LogType.Error:
|
||||
errorCount++;
|
||||
break;
|
||||
case LogType.Exception:
|
||||
fatalCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFilterToggleText()
|
||||
{
|
||||
RefreshCount();
|
||||
|
||||
if (infoFilterToggle != null)
|
||||
{
|
||||
var label = infoFilterToggle.GetComponentInChildren<TMP_Text>();
|
||||
if (label != null)
|
||||
label.text = $"Info ({infoCount})";
|
||||
}
|
||||
|
||||
if (warningFilterToggle != null)
|
||||
{
|
||||
var label = warningFilterToggle.GetComponentInChildren<TMP_Text>();
|
||||
if (label != null)
|
||||
label.text = $"Warning ({warningCount})";
|
||||
}
|
||||
|
||||
if (errorFilterToggle != null)
|
||||
{
|
||||
var label = errorFilterToggle.GetComponentInChildren<TMP_Text>();
|
||||
if (label != null)
|
||||
label.text = $"Error ({errorCount})";
|
||||
}
|
||||
|
||||
if (fatalFilterToggle != null)
|
||||
{
|
||||
var label = fatalFilterToggle.GetComponentInChildren<TMP_Text>();
|
||||
if (label != null)
|
||||
label.text = $"Fatal ({fatalCount})";
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearAllLogs()
|
||||
{
|
||||
logNodes.Clear();
|
||||
selectedNode = null;
|
||||
|
||||
if (detailText != null)
|
||||
detailText.text = "点击日志查看详细信息...";
|
||||
|
||||
RefreshLogDisplay();
|
||||
|
||||
Debug.Log("[ConsoleModule] 已清空所有日志");
|
||||
}
|
||||
|
||||
private void OnLockScrollChanged(bool value)
|
||||
{
|
||||
lockScroll = value;
|
||||
}
|
||||
|
||||
private void OnInfoFilterChanged(bool value)
|
||||
{
|
||||
infoFilter = value;
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
private void OnWarningFilterChanged(bool value)
|
||||
{
|
||||
warningFilter = value;
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
private void OnErrorFilterChanged(bool value)
|
||||
{
|
||||
errorFilter = value;
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
private void OnFatalFilterChanged(bool value)
|
||||
{
|
||||
fatalFilter = value;
|
||||
needRefresh = true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1660ec87345f07e4992b23d662f497dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
namespace MeowmentDebugTool
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义按钮模块 - 通过反射加载标记为DebugButton的方法
|
||||
/// </summary>
|
||||
public class CustomButtonsModule : IDebugModule
|
||||
{
|
||||
#region 字段
|
||||
private GameObject customButtonsPage;
|
||||
private RectTransform buttonContainer;
|
||||
private GameObject buttonPrefab;
|
||||
private ScrollRect buttonsScrollRect;
|
||||
|
||||
// 自定义按钮回调
|
||||
private Action<Button, TMP_Text> customButtonCallback;
|
||||
|
||||
// 保存的SDF字体资源
|
||||
private TMP_FontAsset savedFontAsset = null;
|
||||
|
||||
// 关闭窗口回调
|
||||
private Action onCloseWindowCallback;
|
||||
#endregion
|
||||
|
||||
#region 构造函数
|
||||
public CustomButtonsModule(GameObject page, RectTransform container, GameObject prefab,
|
||||
ScrollRect scrollRect, Action closeWindowCallback)
|
||||
{
|
||||
customButtonsPage = page;
|
||||
buttonContainer = container;
|
||||
buttonPrefab = prefab;
|
||||
buttonsScrollRect = scrollRect;
|
||||
onCloseWindowCallback = closeWindowCallback;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDebugModule 实现
|
||||
public void Initialize()
|
||||
{
|
||||
Debug.Log("[CustomButtonsModule] 初始化自定义按钮模块...");
|
||||
LoadCustomButtons();
|
||||
}
|
||||
|
||||
public GameObject GetPage()
|
||||
{
|
||||
return customButtonsPage;
|
||||
}
|
||||
|
||||
public string GetModuleName()
|
||||
{
|
||||
return "自定义按钮";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 公共方法
|
||||
/// <summary>
|
||||
/// 设置SDF字体
|
||||
/// </summary>
|
||||
public void SetSDFFont(TMP_FontAsset fontAsset)
|
||||
{
|
||||
savedFontAsset = fontAsset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置自定义按钮回调
|
||||
/// </summary>
|
||||
public void SetCustomButtonCallback(Action<Button, TMP_Text> callback)
|
||||
{
|
||||
customButtonCallback = callback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新加载自定义按钮
|
||||
/// </summary>
|
||||
public void ReloadCustomButtons()
|
||||
{
|
||||
LoadCustomButtons();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 私有方法
|
||||
/// <summary>
|
||||
/// 加载所有自定义按钮(使用反射)
|
||||
/// </summary>
|
||||
private void LoadCustomButtons()
|
||||
{
|
||||
if (buttonContainer == null || buttonPrefab == null)
|
||||
{
|
||||
Debug.LogWarning("[CustomButtonsModule] 按钮容器或按钮预制件未设置");
|
||||
return;
|
||||
}
|
||||
|
||||
// 清空现有按钮
|
||||
foreach (Transform child in buttonContainer)
|
||||
{
|
||||
UnityEngine.Object.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($"[CustomButtonsModule] 无法访问程序集 {assembly.FullName}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateCustomButton(MethodInfo method, DebugButtonAttribute attribute)
|
||||
{
|
||||
GameObject buttonObj = UnityEngine.Object.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($"[CustomButtonsModule] 执行调试方法: {method.Name}");
|
||||
|
||||
// 调用回调
|
||||
customButtonCallback?.Invoke(button, buttonText);
|
||||
customButtonCallback = null;
|
||||
|
||||
// 点击按钮后关闭主窗口
|
||||
onCloseWindowCallback?.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[CustomButtonsModule] 执行调试方法 {method.Name} 时出错: {e.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b567af0f535ece48be5c25d68c5a311
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace MeowmentDebugTool
|
||||
{
|
||||
/// <summary>
|
||||
/// 调试模块接口
|
||||
/// </summary>
|
||||
public interface IDebugModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化模块
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// 获取模块页面
|
||||
/// </summary>
|
||||
GameObject GetPage();
|
||||
|
||||
/// <summary>
|
||||
/// 模块名称
|
||||
/// </summary>
|
||||
string GetModuleName();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1053c580c7f549d4ea082c1f92e2349c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MeowmentDebugTool
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志节点
|
||||
/// </summary>
|
||||
public class LogNode
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志类型
|
||||
/// </summary>
|
||||
public LogType LogType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日志消息
|
||||
/// </summary>
|
||||
public string LogMessage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 堆栈跟踪
|
||||
/// </summary>
|
||||
public string StackTrace { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日志时间
|
||||
/// </summary>
|
||||
public DateTime LogTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日志帧数
|
||||
/// </summary>
|
||||
public int LogFrameCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建日志节点
|
||||
/// </summary>
|
||||
public static LogNode Create(LogType logType, string logMessage, string stackTrace)
|
||||
{
|
||||
LogNode logNode = new LogNode();
|
||||
logNode.LogType = logType;
|
||||
logNode.LogMessage = logMessage;
|
||||
logNode.StackTrace = stackTrace;
|
||||
logNode.LogTime = DateTime.UtcNow;
|
||||
logNode.LogFrameCount = Time.frameCount;
|
||||
return logNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 664e008aa85114d4abe0e031e4ce2da5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,132 @@
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace MeowmentDebugTool
|
||||
{
|
||||
/// <summary>
|
||||
/// 参数查看模块 - 显示设备和系统信息
|
||||
/// </summary>
|
||||
public class ParametersModule : IDebugModule
|
||||
{
|
||||
#region 字段
|
||||
private GameObject parametersPage;
|
||||
private TMP_Text deviceInfoText;
|
||||
private TMP_Text systemInfoText;
|
||||
private ScrollRect parametersScrollRect;
|
||||
#endregion
|
||||
|
||||
#region 构造函数
|
||||
public ParametersModule(GameObject page, TMP_Text deviceInfo, TMP_Text systemInfo, ScrollRect scrollRect)
|
||||
{
|
||||
parametersPage = page;
|
||||
deviceInfoText = deviceInfo;
|
||||
systemInfoText = systemInfo;
|
||||
parametersScrollRect = scrollRect;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDebugModule 实现
|
||||
public void Initialize()
|
||||
{
|
||||
Debug.Log("[ParametersModule] 初始化参数查看模块...");
|
||||
UpdateDeviceInfo();
|
||||
UpdateSystemInfo();
|
||||
}
|
||||
|
||||
public GameObject GetPage()
|
||||
{
|
||||
return parametersPage;
|
||||
}
|
||||
|
||||
public string GetModuleName()
|
||||
{
|
||||
return "参数";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 公共方法
|
||||
/// <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 私有方法
|
||||
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;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee0f30848472b844c87ba31a39a3fca5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -53,13 +53,14 @@ namespace MeowmentDebugTool
|
||||
GameObject customButtonsPage = CreateCustomButtonsPage(contentContainer.transform);
|
||||
GameObject toolbarPage = CreateToolbarPage(contentContainer.transform);
|
||||
GameObject settingsPage = CreateSettingsPage(contentContainer.transform);
|
||||
GameObject consolePage = CreateConsolePage(contentContainer.transform);
|
||||
|
||||
// 创建输入对话框
|
||||
GameObject inputDialog = CreateInputDialog(canvasObj.transform);
|
||||
|
||||
// 设置引用
|
||||
SetupReferences(tool, canvas, mainWindow, floatingButton,
|
||||
parametersPage, customButtonsPage, toolbarPage, settingsPage, inputDialog);
|
||||
parametersPage, customButtonsPage, toolbarPage, settingsPage, consolePage, inputDialog);
|
||||
|
||||
return tool;
|
||||
}
|
||||
@ -309,6 +310,14 @@ namespace MeowmentDebugTool
|
||||
GameObject increaseBtn = CreateButton("TimeIncreaseButton", btnContainer.transform, "+10秒");
|
||||
GameObject decreaseBtn = CreateButton("TimeDecreaseButton", btnContainer.transform, "-10秒");
|
||||
|
||||
// 暂时隐藏按钮
|
||||
GameObject hideBtn = CreateButton("HideTemporarilyButton", timePanel.transform, "暂时隐藏 (5秒)");
|
||||
hideBtn.GetComponent<RectTransform>().sizeDelta = new Vector2(0, 100);
|
||||
hideBtn.GetComponent<Image>().color = new Color(0.5f, 0.3f, 0.1f, 1f); // 橙褐色
|
||||
hideBtn.GetComponent<Button>().onClick.AddListener(() => {
|
||||
UniversalDebugTool.HideTemporarily(5f);
|
||||
});
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
@ -395,6 +404,199 @@ namespace MeowmentDebugTool
|
||||
return page;
|
||||
}
|
||||
|
||||
private static GameObject CreateConsolePage(Transform parent)
|
||||
{
|
||||
GameObject page = new GameObject("ConsolePage");
|
||||
page.transform.SetParent(parent, false);
|
||||
RectTransform rect = page.AddComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.sizeDelta = Vector2.zero;
|
||||
|
||||
VerticalLayoutGroup vLayout = page.AddComponent<VerticalLayoutGroup>();
|
||||
vLayout.spacing = 10;
|
||||
vLayout.padding = new RectOffset(10, 10, 10, 10);
|
||||
vLayout.childControlWidth = true;
|
||||
vLayout.childControlHeight = false;
|
||||
vLayout.childForceExpandWidth = true;
|
||||
vLayout.childForceExpandHeight = false;
|
||||
|
||||
// 控制面板
|
||||
GameObject controlPanel = new GameObject("ControlPanel");
|
||||
controlPanel.transform.SetParent(page.transform, false);
|
||||
RectTransform controlRect = controlPanel.AddComponent<RectTransform>();
|
||||
controlRect.sizeDelta = new Vector2(0, 40);
|
||||
|
||||
// 添加LayoutElement来控制高度
|
||||
LayoutElement controlLayout = controlPanel.AddComponent<LayoutElement>();
|
||||
controlLayout.preferredHeight = 40;
|
||||
controlLayout.flexibleHeight = 0;
|
||||
|
||||
HorizontalLayoutGroup hLayout = controlPanel.AddComponent<HorizontalLayoutGroup>();
|
||||
hLayout.spacing = 10;
|
||||
hLayout.padding = new RectOffset(10, 10, 10, 10);
|
||||
hLayout.childControlWidth = false;
|
||||
hLayout.childControlHeight = true;
|
||||
hLayout.childForceExpandHeight = false;
|
||||
|
||||
// 清空按钮
|
||||
GameObject clearBtn = CreateButton("ConsoleClearButton", controlPanel.transform, "清空");
|
||||
clearBtn.GetComponent<RectTransform>().sizeDelta = new Vector2(150, 0);
|
||||
LayoutElement clearLayout = clearBtn.AddComponent<LayoutElement>();
|
||||
clearLayout.preferredHeight = 35;
|
||||
|
||||
// 锁定滚动Toggle
|
||||
GameObject lockToggle = CreateToggle("ConsoleLockScrollToggle", controlPanel.transform, "锁定滚动");
|
||||
lockToggle.GetComponent<RectTransform>().sizeDelta = new Vector2(180, 0);
|
||||
LayoutElement lockLayout = lockToggle.AddComponent<LayoutElement>();
|
||||
lockLayout.preferredHeight = 35;
|
||||
|
||||
// 过滤器Toggles
|
||||
GameObject infoToggle = CreateToggle("ConsoleInfoFilterToggle", controlPanel.transform, "Info (0)");
|
||||
infoToggle.GetComponent<RectTransform>().sizeDelta = new Vector2(150, 0);
|
||||
infoToggle.GetComponent<Toggle>().isOn = true;
|
||||
LayoutElement infoLayout = infoToggle.AddComponent<LayoutElement>();
|
||||
infoLayout.preferredHeight = 35;
|
||||
|
||||
GameObject warningToggle = CreateToggle("ConsoleWarningFilterToggle", controlPanel.transform, "Warning (0)");
|
||||
warningToggle.GetComponent<RectTransform>().sizeDelta = new Vector2(180, 0);
|
||||
warningToggle.GetComponent<Toggle>().isOn = true;
|
||||
LayoutElement warningLayout = warningToggle.AddComponent<LayoutElement>();
|
||||
warningLayout.preferredHeight = 35;
|
||||
|
||||
GameObject errorToggle = CreateToggle("ConsoleErrorFilterToggle", controlPanel.transform, "Error (0)");
|
||||
errorToggle.GetComponent<RectTransform>().sizeDelta = new Vector2(150, 0);
|
||||
errorToggle.GetComponent<Toggle>().isOn = true;
|
||||
LayoutElement errorLayout = errorToggle.AddComponent<LayoutElement>();
|
||||
errorLayout.preferredHeight = 35;
|
||||
|
||||
GameObject fatalToggle = CreateToggle("ConsoleFatalFilterToggle", controlPanel.transform, "Fatal (0)");
|
||||
fatalToggle.GetComponent<RectTransform>().sizeDelta = new Vector2(150, 0);
|
||||
fatalToggle.GetComponent<Toggle>().isOn = true;
|
||||
LayoutElement fatalLayout = fatalToggle.AddComponent<LayoutElement>();
|
||||
fatalLayout.preferredHeight = 35;
|
||||
|
||||
// 日志区域 (60%)
|
||||
GameObject logArea = new GameObject("LogArea");
|
||||
logArea.transform.SetParent(page.transform, false);
|
||||
RectTransform logAreaRect = logArea.AddComponent<RectTransform>();
|
||||
logAreaRect.sizeDelta = new Vector2(0, 1300);
|
||||
LayoutElement logAreaLayout = logArea.AddComponent<LayoutElement>();
|
||||
logAreaLayout.preferredHeight = 1300;
|
||||
logAreaLayout.flexibleHeight = 0;
|
||||
|
||||
// 日志ScrollView
|
||||
GameObject logScrollView = new GameObject("ConsoleLogScrollView");
|
||||
logScrollView.transform.SetParent(logArea.transform, false);
|
||||
RectTransform logScrollRect = logScrollView.AddComponent<RectTransform>();
|
||||
logScrollRect.anchorMin = Vector2.zero;
|
||||
logScrollRect.anchorMax = Vector2.one;
|
||||
logScrollRect.sizeDelta = Vector2.zero;
|
||||
|
||||
ScrollRect logScroll = logScrollView.AddComponent<ScrollRect>();
|
||||
logScroll.horizontal = false;
|
||||
logScroll.vertical = true;
|
||||
logScroll.movementType = ScrollRect.MovementType.Elastic;
|
||||
|
||||
// Viewport
|
||||
GameObject logViewport = new GameObject("Viewport");
|
||||
logViewport.transform.SetParent(logScrollView.transform, false);
|
||||
RectTransform logViewportRect = logViewport.AddComponent<RectTransform>();
|
||||
logViewportRect.anchorMin = Vector2.zero;
|
||||
logViewportRect.anchorMax = Vector2.one;
|
||||
logViewportRect.sizeDelta = Vector2.zero;
|
||||
logViewport.AddComponent<Image>().color = new Color(0.05f, 0.05f, 0.05f, 1f);
|
||||
logViewport.AddComponent<Mask>().showMaskGraphic = true;
|
||||
logScroll.viewport = logViewportRect;
|
||||
|
||||
// Content
|
||||
GameObject logContent = new GameObject("ConsoleLogContent");
|
||||
logContent.transform.SetParent(logViewport.transform, false);
|
||||
RectTransform logContentRect = logContent.AddComponent<RectTransform>();
|
||||
logContentRect.anchorMin = new Vector2(0, 1);
|
||||
logContentRect.anchorMax = new Vector2(1, 1);
|
||||
logContentRect.pivot = new Vector2(0.5f, 1);
|
||||
logContentRect.sizeDelta = new Vector2(0, 0);
|
||||
|
||||
VerticalLayoutGroup logVLayout = logContent.AddComponent<VerticalLayoutGroup>();
|
||||
logVLayout.spacing = 2;
|
||||
logVLayout.childAlignment = TextAnchor.UpperCenter;
|
||||
logVLayout.childControlWidth = true;
|
||||
logVLayout.childControlHeight = true;
|
||||
logVLayout.childForceExpandWidth = true;
|
||||
logVLayout.childForceExpandHeight = false;
|
||||
|
||||
ContentSizeFitter logFitter = logContent.AddComponent<ContentSizeFitter>();
|
||||
logFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
logScroll.content = logContentRect;
|
||||
|
||||
// 详情区域 (40%)
|
||||
GameObject detailArea = new GameObject("DetailArea");
|
||||
detailArea.transform.SetParent(page.transform, false);
|
||||
RectTransform detailAreaRect = detailArea.AddComponent<RectTransform>();
|
||||
detailAreaRect.sizeDelta = new Vector2(0, 800);
|
||||
LayoutElement detailAreaLayout = detailArea.AddComponent<LayoutElement>();
|
||||
detailAreaLayout.preferredHeight = 800;
|
||||
detailAreaLayout.flexibleHeight = 0;
|
||||
|
||||
// 详情ScrollView
|
||||
GameObject detailScrollView = new GameObject("ConsoleDetailScrollView");
|
||||
detailScrollView.transform.SetParent(detailArea.transform, false);
|
||||
RectTransform detailScrollRect = detailScrollView.AddComponent<RectTransform>();
|
||||
detailScrollRect.anchorMin = Vector2.zero;
|
||||
detailScrollRect.anchorMax = Vector2.one;
|
||||
detailScrollRect.sizeDelta = Vector2.zero;
|
||||
|
||||
ScrollRect detailScroll = detailScrollView.AddComponent<ScrollRect>();
|
||||
detailScroll.horizontal = false;
|
||||
detailScroll.vertical = true;
|
||||
detailScroll.movementType = ScrollRect.MovementType.Clamped;
|
||||
|
||||
// Viewport
|
||||
GameObject detailViewport = new GameObject("Viewport");
|
||||
detailViewport.transform.SetParent(detailScrollView.transform, false);
|
||||
RectTransform detailViewportRect = detailViewport.AddComponent<RectTransform>();
|
||||
detailViewportRect.anchorMin = Vector2.zero;
|
||||
detailViewportRect.anchorMax = Vector2.one;
|
||||
detailViewportRect.sizeDelta = Vector2.zero;
|
||||
detailViewport.AddComponent<Image>().color = new Color(0.08f, 0.08f, 0.08f, 1f);
|
||||
detailViewport.AddComponent<Mask>().showMaskGraphic = true;
|
||||
detailScroll.viewport = detailViewportRect;
|
||||
|
||||
// Content
|
||||
GameObject detailContent = new GameObject("Content");
|
||||
detailContent.transform.SetParent(detailViewport.transform, false);
|
||||
RectTransform detailContentRect = detailContent.AddComponent<RectTransform>();
|
||||
detailContentRect.anchorMin = new Vector2(0, 1);
|
||||
detailContentRect.anchorMax = new Vector2(1, 1);
|
||||
detailContentRect.pivot = new Vector2(0.5f, 1);
|
||||
detailContentRect.sizeDelta = new Vector2(-20, 0);
|
||||
detailContentRect.anchoredPosition = Vector2.zero;
|
||||
|
||||
// 详情文本
|
||||
GameObject detailText = CreateTextObject("ConsoleDetailText", detailContent.transform, "点击日志查看详细信息...");
|
||||
TextMeshProUGUI detailTMP = detailText.GetComponent<TextMeshProUGUI>();
|
||||
detailTMP.fontSize = 24;
|
||||
detailTMP.alignment = TextAlignmentOptions.TopLeft;
|
||||
detailTMP.enableWordWrapping = true;
|
||||
detailTMP.overflowMode = TextOverflowModes.Overflow;
|
||||
detailTMP.richText = true;
|
||||
detailTMP.color = new Color(0.8f, 0.8f, 0.8f, 1f);
|
||||
RectTransform detailTextRect = detailText.GetComponent<RectTransform>();
|
||||
detailTextRect.anchorMin = new Vector2(0, 1);
|
||||
detailTextRect.anchorMax = new Vector2(1, 1);
|
||||
detailTextRect.pivot = new Vector2(0.5f, 1);
|
||||
detailTextRect.sizeDelta = new Vector2(0, 0);
|
||||
|
||||
ContentSizeFitter detailFitter = detailText.AddComponent<ContentSizeFitter>();
|
||||
detailFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
detailScroll.content = detailContentRect;
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
private static GameObject CreateInputDialog(Transform parent)
|
||||
{
|
||||
GameObject dialog = new GameObject("InputDialog");
|
||||
@ -535,6 +737,151 @@ namespace MeowmentDebugTool
|
||||
return textObj;
|
||||
}
|
||||
|
||||
private static GameObject CreateToggle(string name, Transform parent, string labelText)
|
||||
{
|
||||
GameObject toggleObj = new GameObject(name);
|
||||
toggleObj.transform.SetParent(parent, false);
|
||||
RectTransform rect = toggleObj.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(160, 60);
|
||||
|
||||
// Background
|
||||
Image bgImg = toggleObj.AddComponent<Image>();
|
||||
bgImg.color = new Color(0.2f, 0.2f, 0.2f, 1f);
|
||||
|
||||
Toggle toggle = toggleObj.AddComponent<Toggle>();
|
||||
toggle.isOn = true;
|
||||
toggle.transition = Selectable.Transition.ColorTint;
|
||||
|
||||
// Checkmark Background
|
||||
GameObject checkmarkBg = new GameObject("Background");
|
||||
checkmarkBg.transform.SetParent(toggleObj.transform, false);
|
||||
RectTransform checkmarkBgRect = checkmarkBg.AddComponent<RectTransform>();
|
||||
checkmarkBgRect.anchorMin = new Vector2(0, 0.5f);
|
||||
checkmarkBgRect.anchorMax = new Vector2(0, 0.5f);
|
||||
checkmarkBgRect.pivot = new Vector2(0, 0.5f);
|
||||
checkmarkBgRect.sizeDelta = new Vector2(40, 40);
|
||||
checkmarkBgRect.anchoredPosition = new Vector2(10, 0);
|
||||
|
||||
Image checkmarkBgImg = checkmarkBg.AddComponent<Image>();
|
||||
checkmarkBgImg.color = new Color(0.8f, 0.8f, 0.8f, 1f);
|
||||
|
||||
// Checkmark
|
||||
GameObject checkmark = new GameObject("Checkmark");
|
||||
checkmark.transform.SetParent(checkmarkBg.transform, false);
|
||||
RectTransform checkmarkRect = checkmark.AddComponent<RectTransform>();
|
||||
checkmarkRect.anchorMin = Vector2.zero;
|
||||
checkmarkRect.anchorMax = Vector2.one;
|
||||
checkmarkRect.sizeDelta = new Vector2(-10, -10);
|
||||
|
||||
Image checkmarkImg = checkmark.AddComponent<Image>();
|
||||
checkmarkImg.color = new Color(0.2f, 0.8f, 0.2f, 1f);
|
||||
|
||||
toggle.targetGraphic = checkmarkBgImg;
|
||||
toggle.graphic = checkmarkImg;
|
||||
|
||||
// Label
|
||||
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.offsetMin = new Vector2(60, 0);
|
||||
labelRect.offsetMax = new Vector2(-10, 0);
|
||||
|
||||
TextMeshProUGUI labelTmp = label.AddComponent<TextMeshProUGUI>();
|
||||
labelTmp.text = labelText;
|
||||
labelTmp.fontSize = 24;
|
||||
labelTmp.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
labelTmp.color = Color.white;
|
||||
|
||||
// 设置默认字体
|
||||
TMP_FontAsset defaultFont = GetDefaultFont();
|
||||
if (defaultFont != null)
|
||||
{
|
||||
labelTmp.font = defaultFont;
|
||||
}
|
||||
|
||||
return toggleObj;
|
||||
}
|
||||
|
||||
private static GameObject CreateConsoleLogItemPrefab()
|
||||
{
|
||||
GameObject logItem = new GameObject("ConsoleLogItem");
|
||||
RectTransform rect = logItem.AddComponent<RectTransform>();
|
||||
// 设置锚点为水平拉伸,顶部对齐
|
||||
rect.anchorMin = new Vector2(0, 1);
|
||||
rect.anchorMax = new Vector2(1, 1);
|
||||
rect.pivot = new Vector2(0.5f, 1);
|
||||
// 设置固定高度50,宽度自动拉伸
|
||||
rect.sizeDelta = new Vector2(0, 50);
|
||||
|
||||
// 添加LayoutElement来确保布局组件正确控制尺寸
|
||||
LayoutElement layoutElement = logItem.AddComponent<LayoutElement>();
|
||||
layoutElement.minHeight = 50;
|
||||
layoutElement.preferredHeight = 50;
|
||||
layoutElement.flexibleWidth = 1;
|
||||
|
||||
// Toggle组件
|
||||
Toggle toggle = logItem.AddComponent<Toggle>();
|
||||
toggle.isOn = false;
|
||||
toggle.transition = Selectable.Transition.ColorTint;
|
||||
|
||||
// Background
|
||||
GameObject background = new GameObject("Background");
|
||||
background.transform.SetParent(logItem.transform, false);
|
||||
RectTransform bgRect = background.AddComponent<RectTransform>();
|
||||
bgRect.anchorMin = Vector2.zero;
|
||||
bgRect.anchorMax = Vector2.one;
|
||||
bgRect.sizeDelta = Vector2.zero;
|
||||
|
||||
Image bgImg = background.AddComponent<Image>();
|
||||
bgImg.color = new Color(0.12f, 0.12f, 0.12f, 1f);
|
||||
|
||||
toggle.targetGraphic = bgImg;
|
||||
|
||||
// Label (日志文本)
|
||||
GameObject label = new GameObject("Label");
|
||||
label.transform.SetParent(logItem.transform, false);
|
||||
RectTransform labelRect = label.AddComponent<RectTransform>();
|
||||
labelRect.anchorMin = Vector2.zero;
|
||||
labelRect.anchorMax = Vector2.one;
|
||||
labelRect.offsetMin = new Vector2(10, 5);
|
||||
labelRect.offsetMax = new Vector2(-10, -5);
|
||||
|
||||
TextMeshProUGUI labelTmp = label.AddComponent<TextMeshProUGUI>();
|
||||
labelTmp.fontSize = 28;
|
||||
labelTmp.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
labelTmp.color = Color.white;
|
||||
labelTmp.overflowMode = TextOverflowModes.Truncate;
|
||||
labelTmp.richText = true;
|
||||
labelTmp.enableWordWrapping = false;
|
||||
|
||||
// 设置默认字体
|
||||
TMP_FontAsset defaultFont = GetDefaultFont();
|
||||
if (defaultFont != null)
|
||||
{
|
||||
labelTmp.font = defaultFont;
|
||||
}
|
||||
|
||||
// 设置ColorBlock以支持选中状态
|
||||
ColorBlock colors = toggle.colors;
|
||||
colors.normalColor = new Color(0.12f, 0.12f, 0.12f, 1f);
|
||||
colors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1f);
|
||||
colors.selectedColor = new Color(0.25f, 0.35f, 0.45f, 1f);
|
||||
colors.pressedColor = new Color(0.15f, 0.15f, 0.15f, 1f);
|
||||
toggle.colors = colors;
|
||||
|
||||
// 不需要设置为不激活,因为Instantiate时会继承这个状态
|
||||
// 而且作为模板时不会显示在场景中
|
||||
|
||||
// 不销毁预制件
|
||||
Object.DontDestroyOnLoad(logItem);
|
||||
|
||||
Debug.Log("[RuntimeUIGenerator] Created ConsoleLogItem prefab");
|
||||
|
||||
return logItem;
|
||||
}
|
||||
|
||||
private static GameObject CreateInputField(string name, Transform parent, string placeholder)
|
||||
{
|
||||
GameObject inputObj = new GameObject(name);
|
||||
@ -717,7 +1064,7 @@ namespace MeowmentDebugTool
|
||||
|
||||
private static void SetupReferences(UniversalDebugTool tool, Canvas canvas, GameObject mainWindow,
|
||||
GameObject floatingButton, GameObject parametersPage, GameObject customButtonsPage,
|
||||
GameObject toolbarPage, GameObject settingsPage, GameObject inputDialog)
|
||||
GameObject toolbarPage, GameObject settingsPage, GameObject consolePage, GameObject inputDialog)
|
||||
{
|
||||
// 使用反射设置私有字段
|
||||
var type = typeof(UniversalDebugTool);
|
||||
@ -788,6 +1135,27 @@ namespace MeowmentDebugTool
|
||||
type.GetField("currentResolutionText", flags)?.SetValue(tool, resPanel.Find("CurrentResolutionText")?.GetComponent<TextMeshProUGUI>());
|
||||
}
|
||||
|
||||
// 控制台页面
|
||||
type.GetField("consolePage", flags)?.SetValue(tool, consolePage);
|
||||
type.GetField("consoleLogScrollRect", flags)?.SetValue(tool, consolePage.transform.Find("LogArea/ConsoleLogScrollView")?.GetComponent<ScrollRect>());
|
||||
type.GetField("consoleLogContent", flags)?.SetValue(tool, consolePage.transform.Find("LogArea/ConsoleLogScrollView/Viewport/ConsoleLogContent")?.GetComponent<RectTransform>());
|
||||
type.GetField("consoleDetailScrollRect", flags)?.SetValue(tool, consolePage.transform.Find("DetailArea/ConsoleDetailScrollView")?.GetComponent<ScrollRect>());
|
||||
type.GetField("consoleDetailText", flags)?.SetValue(tool, consolePage.transform.Find("DetailArea/ConsoleDetailScrollView/Viewport/Content/ConsoleDetailText")?.GetComponent<TextMeshProUGUI>());
|
||||
Transform controlPanel = consolePage.transform.Find("ControlPanel");
|
||||
if (controlPanel != null)
|
||||
{
|
||||
type.GetField("consoleClearButton", flags)?.SetValue(tool, controlPanel.Find("ConsoleClearButton")?.GetComponent<Button>());
|
||||
type.GetField("consoleLockScrollToggle", flags)?.SetValue(tool, controlPanel.Find("ConsoleLockScrollToggle")?.GetComponent<Toggle>());
|
||||
type.GetField("consoleInfoFilterToggle", flags)?.SetValue(tool, controlPanel.Find("ConsoleInfoFilterToggle")?.GetComponent<Toggle>());
|
||||
type.GetField("consoleWarningFilterToggle", flags)?.SetValue(tool, controlPanel.Find("ConsoleWarningFilterToggle")?.GetComponent<Toggle>());
|
||||
type.GetField("consoleErrorFilterToggle", flags)?.SetValue(tool, controlPanel.Find("ConsoleErrorFilterToggle")?.GetComponent<Toggle>());
|
||||
type.GetField("consoleFatalFilterToggle", flags)?.SetValue(tool, controlPanel.Find("ConsoleFatalFilterToggle")?.GetComponent<Toggle>());
|
||||
}
|
||||
|
||||
// 创建日志项预制件
|
||||
GameObject consoleLogItemPrefab = CreateConsoleLogItemPrefab();
|
||||
type.GetField("consoleLogItemPrefab", flags)?.SetValue(tool, consoleLogItemPrefab);
|
||||
|
||||
// 输入对话框
|
||||
type.GetField("inputDialog", flags)?.SetValue(tool, inputDialog);
|
||||
Transform dialogPanel = inputDialog.transform.Find("Panel");
|
||||
|
||||
@ -0,0 +1,128 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
namespace MeowmentDebugTool
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置模块 - 提供分辨率设置等功能
|
||||
/// </summary>
|
||||
public class SettingsModule : IDebugModule
|
||||
{
|
||||
#region 字段
|
||||
private GameObject settingsPage;
|
||||
private TMP_InputField widthInputField;
|
||||
private TMP_InputField heightInputField;
|
||||
private Button applyResolutionButton;
|
||||
private Button resetResolutionButton;
|
||||
private TMP_Text currentResolutionText;
|
||||
|
||||
// 主窗口引用
|
||||
private RectTransform mainWindow;
|
||||
private Canvas canvas;
|
||||
|
||||
// 默认分辨率
|
||||
private Vector2 defaultResolution = new Vector2(1080, 2340);
|
||||
private Vector2 currentCustomResolution;
|
||||
#endregion
|
||||
|
||||
#region 构造函数
|
||||
public SettingsModule(GameObject page, TMP_InputField widthInput, TMP_InputField heightInput,
|
||||
Button applyButton, Button resetButton, TMP_Text resolutionText,
|
||||
RectTransform mainWin, Canvas canvasRef)
|
||||
{
|
||||
settingsPage = page;
|
||||
widthInputField = widthInput;
|
||||
heightInputField = heightInput;
|
||||
applyResolutionButton = applyButton;
|
||||
resetResolutionButton = resetButton;
|
||||
currentResolutionText = resolutionText;
|
||||
mainWindow = mainWin;
|
||||
canvas = canvasRef;
|
||||
|
||||
currentCustomResolution = defaultResolution;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDebugModule 实现
|
||||
public void Initialize()
|
||||
{
|
||||
Debug.Log("[SettingsModule] 初始化设置模块...");
|
||||
|
||||
if (applyResolutionButton != null)
|
||||
applyResolutionButton.onClick.AddListener(ApplyCustomResolution);
|
||||
|
||||
if (resetResolutionButton != null)
|
||||
resetResolutionButton.onClick.AddListener(ResetToDefaultResolution);
|
||||
|
||||
// 应用默认分辨率
|
||||
ApplyResolution(defaultResolution);
|
||||
}
|
||||
|
||||
public GameObject GetPage()
|
||||
{
|
||||
return settingsPage;
|
||||
}
|
||||
|
||||
public string GetModuleName()
|
||||
{
|
||||
return "设置";
|
||||
}
|
||||
#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($"[SettingsModule] 已应用自定义分辨率: {width} x {height}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[SettingsModule] 分辨率值必须大于0");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[SettingsModule] 无效的分辨率值");
|
||||
}
|
||||
}
|
||||
|
||||
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($"[SettingsModule] 已重置为默认分辨率: {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}";
|
||||
}
|
||||
|
||||
// 不需要强制刷新Canvas,会导致rebuild loop
|
||||
// Canvas会自动在下一帧更新
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ad06ef8252844e478314f4dac5759ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,142 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
namespace MeowmentDebugTool
|
||||
{
|
||||
/// <summary>
|
||||
/// 工具栏模块 - 提供时间调整等工具
|
||||
/// </summary>
|
||||
public class ToolbarModule : IDebugModule
|
||||
{
|
||||
#region 字段
|
||||
private GameObject toolbarPage;
|
||||
private Slider timeAdjustSlider;
|
||||
private TMP_Text timeAdjustValueText;
|
||||
private Button timeIncreaseButton;
|
||||
private Button timeDecreaseButton;
|
||||
|
||||
private float timeAdjustValue = 60f; // 当前设置的时间调整值(秒)
|
||||
#endregion
|
||||
|
||||
#region 构造函数
|
||||
public ToolbarModule(GameObject page, Slider slider, TMP_Text valueText,
|
||||
Button increaseButton, Button decreaseButton)
|
||||
{
|
||||
toolbarPage = page;
|
||||
timeAdjustSlider = slider;
|
||||
timeAdjustValueText = valueText;
|
||||
timeIncreaseButton = increaseButton;
|
||||
timeDecreaseButton = decreaseButton;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDebugModule 实现
|
||||
public void Initialize()
|
||||
{
|
||||
Debug.Log("[ToolbarModule] 初始化工具栏模块...");
|
||||
|
||||
// 设置时间调整工具
|
||||
if (timeAdjustSlider != null)
|
||||
{
|
||||
timeAdjustSlider.onValueChanged.AddListener(OnTimeAdjustValueChanged);
|
||||
// 初始化显示
|
||||
UpdateTimeAdjustDisplay(timeAdjustSlider.value);
|
||||
}
|
||||
|
||||
if (timeIncreaseButton != null)
|
||||
timeIncreaseButton.onClick.AddListener(OnIncreaseTime);
|
||||
|
||||
if (timeDecreaseButton != null)
|
||||
timeDecreaseButton.onClick.AddListener(OnDecreaseTime);
|
||||
}
|
||||
|
||||
public GameObject GetPage()
|
||||
{
|
||||
return toolbarPage;
|
||||
}
|
||||
|
||||
public string GetModuleName()
|
||||
{
|
||||
return "工具栏";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 私有方法
|
||||
/// <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($"⏰ [ToolbarModule] 增加时间: +{adjustSeconds}秒");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 减少时间
|
||||
/// </summary>
|
||||
private void OnDecreaseTime()
|
||||
{
|
||||
float adjustSeconds = -timeAdjustValue;
|
||||
|
||||
AdjustGameTime(adjustSeconds);
|
||||
|
||||
Debug.Log($"⏰ [ToolbarModule] 减少时间: {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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74cdbdc5fe514fd44823b16c1364ef38
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -62,12 +62,26 @@ namespace MeowmentDebugTool
|
||||
[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;
|
||||
[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 GameObject consoleLogItemPrefab;
|
||||
|
||||
// [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 私有变量
|
||||
@ -77,18 +91,22 @@ namespace MeowmentDebugTool
|
||||
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;
|
||||
|
||||
// 模块实例
|
||||
private ParametersModule parametersModule;
|
||||
private CustomButtonsModule customButtonsModule;
|
||||
private ToolbarModule toolbarModule;
|
||||
private SettingsModule settingsModule;
|
||||
private ConsoleModule consoleModule;
|
||||
private List<IDebugModule> allModules = new List<IDebugModule>();
|
||||
|
||||
// 暂时隐藏状态标志
|
||||
private bool isHiding = false;
|
||||
#endregion
|
||||
|
||||
#region 单例
|
||||
@ -121,8 +139,6 @@ namespace MeowmentDebugTool
|
||||
return;
|
||||
}
|
||||
|
||||
currentCustomResolution = defaultResolution;
|
||||
|
||||
// 设置Canvas为最上层
|
||||
SetCanvasToTop();
|
||||
|
||||
@ -139,23 +155,36 @@ namespace MeowmentDebugTool
|
||||
{
|
||||
// 只有在已初始化但还没调用过InitializeDebugTool时才执行
|
||||
// 这避免了Awake和Init()的重复调用问题
|
||||
if (isInitialized && pages.Count == 0)
|
||||
if (isInitialized && allModules.Count == 0)
|
||||
{
|
||||
// 这是通过Init()创建的实例,需要在Start中完成初始化
|
||||
// 此时反射设置的字段已经生效
|
||||
InitializeDebugTool();
|
||||
UpdateDeviceInfo();
|
||||
UpdateSystemInfo();
|
||||
LoadCustomButtons();
|
||||
InitializeAllModules();
|
||||
ShowMainWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// 更新控制台模块
|
||||
if (consoleModule != null)
|
||||
{
|
||||
consoleModule.Update();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (instance == this)
|
||||
{
|
||||
instance = null;
|
||||
|
||||
// 销毁控制台模块
|
||||
if (consoleModule != null)
|
||||
{
|
||||
consoleModule.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@ -185,12 +214,10 @@ namespace MeowmentDebugTool
|
||||
if (InstanceExists)
|
||||
{
|
||||
Instance.InitializeDebugTool();
|
||||
Instance.UpdateDeviceInfo();
|
||||
Instance.UpdateSystemInfo();
|
||||
Instance.LoadCustomButtons();
|
||||
Instance.InitializeAllModules();
|
||||
Instance.ShowMainWindow();
|
||||
|
||||
Debug.Log("[MeowmentDebugTool] ✅ 初始化完成!");
|
||||
Debug.Log("[MeowmentDebugTool] 初始化完成!");
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,9 +268,19 @@ namespace MeowmentDebugTool
|
||||
savedFontAsset = fontAsset;
|
||||
Debug.Log("[MeowmentDebugTool] 字体资源已保存,后续创建的按钮将自动应用此字体");
|
||||
|
||||
// 4. 重新加载自定义按钮,使新字体应用到已存在的CustomButton
|
||||
Debug.Log("[MeowmentDebugTool] 重新加载自定义按钮以应用新字体...");
|
||||
Instance.ReloadCustomButtons();
|
||||
// 4. 通过模块重新加载自定义按钮
|
||||
if (Instance.customButtonsModule != null)
|
||||
{
|
||||
Debug.Log("[MeowmentDebugTool] 重新加载自定义按钮以应用新字体...");
|
||||
Instance.customButtonsModule.SetSDFFont(fontAsset);
|
||||
Instance.customButtonsModule.ReloadCustomButtons();
|
||||
}
|
||||
|
||||
// 5. 为控制台模块应用字体
|
||||
if (Instance.consoleModule != null)
|
||||
{
|
||||
Instance.consoleModule.SetSDFFont(fontAsset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -274,11 +311,41 @@ namespace MeowmentDebugTool
|
||||
{
|
||||
Debug.Log("🚀 初始化UniversalDebugTool...");
|
||||
|
||||
// 创建模块实例
|
||||
parametersModule = new ParametersModule(
|
||||
parametersPage, deviceInfoText, systemInfoText, parametersScrollRect);
|
||||
|
||||
customButtonsModule = new CustomButtonsModule(
|
||||
customButtonsPage, buttonContainer, buttonPrefab, buttonsScrollRect, CloseDebugWindow);
|
||||
|
||||
toolbarModule = new ToolbarModule(
|
||||
toolbarPage, timeAdjustSlider, timeAdjustValueText,
|
||||
timeIncreaseButton, timeDecreaseButton);
|
||||
|
||||
settingsModule = new SettingsModule(
|
||||
settingsPage, widthInputField, heightInputField,
|
||||
applyResolutionButton, resetResolutionButton, currentResolutionText,
|
||||
mainWindow, canvas);
|
||||
|
||||
consoleModule = new ConsoleModule(
|
||||
consolePage, consoleLogScrollRect, consoleLogContent,
|
||||
consoleDetailScrollRect, consoleDetailText,
|
||||
consoleClearButton, consoleLockScrollToggle,
|
||||
consoleInfoFilterToggle, consoleWarningFilterToggle,
|
||||
consoleErrorFilterToggle, consoleFatalFilterToggle, consoleLogItemPrefab);
|
||||
|
||||
// 添加所有模块到列表
|
||||
allModules.Add(consoleModule);
|
||||
allModules.Add(parametersModule);
|
||||
allModules.Add(customButtonsModule);
|
||||
allModules.Add(toolbarModule);
|
||||
allModules.Add(settingsModule);
|
||||
|
||||
// 注册所有页面
|
||||
RegisterPage("参数", parametersPage);
|
||||
RegisterPage("自定义按钮", customButtonsPage);
|
||||
RegisterPage("工具栏", toolbarPage);
|
||||
RegisterPage("设置", settingsPage);
|
||||
foreach (var module in allModules)
|
||||
{
|
||||
RegisterPage(module.GetModuleName(), module.GetPage());
|
||||
}
|
||||
|
||||
Debug.Log($"📄 已注册{pages.Count}个页面");
|
||||
|
||||
@ -289,26 +356,6 @@ namespace MeowmentDebugTool
|
||||
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)
|
||||
{
|
||||
@ -325,13 +372,23 @@ namespace MeowmentDebugTool
|
||||
ShowPage("参数");
|
||||
}
|
||||
|
||||
// 应用默认分辨率
|
||||
ApplyResolution(defaultResolution);
|
||||
|
||||
Debug.Log("✅ UniversalDebugTool初始化完成!");
|
||||
Debug.Log("[OK] UniversalDebugTool初始化完成!");
|
||||
Debug.Log("💡 提示: 点击窗口顶部的标签切换页面,或按F1-F4使用快捷键");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化所有模块
|
||||
/// </summary>
|
||||
private void InitializeAllModules()
|
||||
{
|
||||
Debug.Log("🔧 初始化所有模块...");
|
||||
foreach (var module in allModules)
|
||||
{
|
||||
module.Initialize();
|
||||
}
|
||||
Debug.Log("[OK] 所有模块初始化完成!");
|
||||
}
|
||||
|
||||
private void RegisterPage(string pageName, GameObject pageObject)
|
||||
{
|
||||
if (pageObject != null && !pages.ContainsKey(pageName))
|
||||
@ -365,7 +422,7 @@ namespace MeowmentDebugTool
|
||||
if (buttonText != null)
|
||||
{
|
||||
buttonText.text = page.Key;
|
||||
Debug.Log($"✅ 创建标签: [{page.Key}]");
|
||||
Debug.Log($"[OK] 创建标签: [{page.Key}]");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -378,7 +435,7 @@ namespace MeowmentDebugTool
|
||||
tabButtons[page.Key] = button;
|
||||
}
|
||||
|
||||
Debug.Log("✅ 标签按钮创建完成!");
|
||||
Debug.Log("[OK] 标签按钮创建完成!");
|
||||
}
|
||||
#endregion
|
||||
|
||||
@ -421,64 +478,13 @@ namespace MeowmentDebugTool
|
||||
}
|
||||
#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;
|
||||
}
|
||||
|
||||
#region 参数查看功能(通过ParametersModule)
|
||||
/// <summary>
|
||||
/// 复制设备信息到剪贴板
|
||||
/// </summary>
|
||||
public void CopyDeviceInfoToClipboard()
|
||||
{
|
||||
if (deviceInfoText != null)
|
||||
{
|
||||
GUIUtility.systemCopyBuffer = deviceInfoText.text;
|
||||
Debug.Log("设备信息已复制到剪贴板");
|
||||
}
|
||||
parametersModule?.CopyDeviceInfoToClipboard();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -486,11 +492,7 @@ namespace MeowmentDebugTool
|
||||
/// </summary>
|
||||
public void CopySystemInfoToClipboard()
|
||||
{
|
||||
if (systemInfoText != null)
|
||||
{
|
||||
GUIUtility.systemCopyBuffer = systemInfoText.text;
|
||||
Debug.Log("系统信息已复制到剪贴板");
|
||||
}
|
||||
parametersModule?.CopySystemInfoToClipboard();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -498,110 +500,20 @@ namespace MeowmentDebugTool
|
||||
/// </summary>
|
||||
public void RefreshAllInfo()
|
||||
{
|
||||
UpdateDeviceInfo();
|
||||
UpdateSystemInfo();
|
||||
parametersModule?.RefreshAllInfo();
|
||||
}
|
||||
#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}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#region 自定义按钮功能(通过CustomButtonsModule)
|
||||
/// <summary>
|
||||
/// 设置自定义按钮回调
|
||||
/// </summary>
|
||||
public static void SetCustomButtonCallback(Action<Button, TMP_Text> callback)
|
||||
{
|
||||
customButtonCallback = callback;
|
||||
if (InstanceExists && Instance.customButtonsModule != null)
|
||||
{
|
||||
Instance.customButtonsModule.SetCustomButtonCallback(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -609,191 +521,58 @@ namespace MeowmentDebugTool
|
||||
/// </summary>
|
||||
public void ReloadCustomButtons()
|
||||
{
|
||||
LoadCustomButtons();
|
||||
customButtonsModule?.ReloadCustomButtons();
|
||||
}
|
||||
#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;
|
||||
|
||||
|
||||
// #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;
|
||||
// var inst = Instance;
|
||||
// if (inst.inputDialog == null) return;
|
||||
|
||||
inst.inputDialog.SetActive(false);
|
||||
inst.inputDialogInputField.text = "";
|
||||
inst.inputDialogConfirmBtn.onClick.RemoveAllListeners();
|
||||
inst.inputDialogCancelBtn.onClick.RemoveAllListeners();
|
||||
}
|
||||
#endregion
|
||||
// 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>
|
||||
@ -847,7 +626,7 @@ namespace MeowmentDebugTool
|
||||
if (floatingButton != null)
|
||||
floatingButton.SetActive(true);
|
||||
|
||||
Debug.Log("🔽 调试窗口已关闭");
|
||||
Debug.Log("[关闭] 调试窗口已关闭");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -878,7 +657,7 @@ namespace MeowmentDebugTool
|
||||
if (mainWindow != null)
|
||||
mainWindow.gameObject.SetActive(true);
|
||||
|
||||
Debug.Log("🔼 调试窗口已打开");
|
||||
Debug.Log("[打开] 调试窗口已打开");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -892,6 +671,79 @@ namespace MeowmentDebugTool
|
||||
if (floatingButton != null)
|
||||
floatingButton.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暂时隐藏调试工具UI(用于截图等场景)
|
||||
/// </summary>
|
||||
/// <param name="seconds">隐藏的秒数,默认5秒</param>
|
||||
public static void HideTemporarily(float seconds = 5f)
|
||||
{
|
||||
if (!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] ✓ UI已成功恢复(浮窗状态)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[MeowmentDebugTool] ✗ Canvas恢复失败!");
|
||||
}
|
||||
|
||||
isHiding = false;
|
||||
Debug.Log("[MeowmentDebugTool] 暂时隐藏流程结束");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
@ -71,11 +71,11 @@ public static class SampleDebugFunctions
|
||||
Debug.Log("已触发垃圾回收");
|
||||
}
|
||||
|
||||
[DebugButton("卸载未使用资源", 0.9f, 0.5f, 0.3f)]
|
||||
[DebugButton("清理未使用资源", 0.9f, 0.5f, 0.3f)]
|
||||
public static void UnloadUnusedAssets()
|
||||
{
|
||||
Resources.UnloadUnusedAssets();
|
||||
Debug.Log("已卸载未使用的资源");
|
||||
Debug.Log("已清理未使用的资源");
|
||||
}
|
||||
|
||||
// 场景管理
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "com.bywaystudios.meowmentdebugtool",
|
||||
"displayName": "MeowmentDebugTool",
|
||||
"version": "0.2.2",
|
||||
"version": "0.3.0",
|
||||
"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": [
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user