更新包
This commit is contained in:
parent
14f9f3f3f2
commit
51c111bc98
@ -1,4 +1,5 @@
|
||||
<Solution>
|
||||
<Project Path="Hontbei.Meowmentdebugtool.Editor.csproj" />
|
||||
<Project Path="Hontbei.Meowmentdebugtool.csproj" />
|
||||
<Project Path="com.bywaystudios.meowmentdebugtool.Editor.csproj" />
|
||||
<Project Path="com.bywaystudios.meowmentdebugtool.csproj" />
|
||||
<Project Path="Assembly-CSharp.csproj" />
|
||||
</Solution>
|
||||
|
||||
35
Packages/com.bywaystudios.meowmentdebugtool/CHANGELOG.md
Normal file
35
Packages/com.bywaystudios.meowmentdebugtool/CHANGELOG.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Changelog
|
||||
|
||||
所有重要的变更都会记录在这个文件中。
|
||||
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||
版本号遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
|
||||
|
||||
## [0.2.2] - 2025-12-19
|
||||
|
||||
### Changed
|
||||
- 默认显示浮窗而不是主窗口,提升用户体验
|
||||
- 点击自定义按钮后自动关闭主窗口,方便用户直接查看功能效果
|
||||
|
||||
### Fixed
|
||||
- 优化初始化逻辑,避免重复初始化问题
|
||||
|
||||
## [0.2.0] - 2025-12-XX
|
||||
|
||||
### Added
|
||||
- 初始版本发布
|
||||
- 支持多标签页调试界面系统
|
||||
- 参数查看页面(设备信息、系统信息)
|
||||
- 自定义按钮功能(通过反射自动加载带 `[DebugButton]` 特性的方法)
|
||||
- 工具栏页面(时间调整工具)
|
||||
- 设置页面(分辨率调整)
|
||||
- 可拖拽的悬浮按钮
|
||||
- 输入对话框功能
|
||||
- 支持运行时动态创建UI
|
||||
- 支持自定义 TextMeshPro SDF 字体
|
||||
|
||||
### Features
|
||||
- Canvas层级设置为30000,确保始终在最上层
|
||||
- 未调用 `Init()` 前不显示任何UI
|
||||
- 支持快捷键切换页面(F1-F4)
|
||||
- DontDestroyOnLoad 确保场景切换时不销毁
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 612f48a21477a8447b81076490854b01
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,196 @@
|
||||
# MeowmentDebugTool 使用指南
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 初始化调试工具(自动创建UI)
|
||||
|
||||
**只需一行代码,自动创建完整的调试UI:**
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using MeowmentDebugTool;
|
||||
|
||||
public class GameStarter : MonoBehaviour
|
||||
{
|
||||
void Start()
|
||||
{
|
||||
// 一行代码初始化,自动创建UI
|
||||
UniversalDebugTool.Init();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**特点:**
|
||||
- ✅ 无需手动创建预制件
|
||||
- ✅ 无需拖拽引用
|
||||
- ✅ 自动生成完整UI
|
||||
- ✅ 开箱即用
|
||||
|
||||
### 2. 使用快捷键或API控制
|
||||
|
||||
```csharp
|
||||
// 显示调试工具
|
||||
UniversalDebugTool.Show();
|
||||
|
||||
// 隐藏调试工具
|
||||
UniversalDebugTool.Hide();
|
||||
|
||||
// 切换显示/隐藏
|
||||
UniversalDebugTool.Toggle();
|
||||
```
|
||||
|
||||
## 如何在主项目中使用自定义调试按钮
|
||||
|
||||
为了确保包可以自由装卸而不影响主工程的运行,请使用条件编译符号包裹调试代码。
|
||||
|
||||
### 基本用法
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
|
||||
#if MEOWMENT_DEBUG_TOOL
|
||||
using MeowmentDebugTool;
|
||||
#endif
|
||||
|
||||
public class GameManager : MonoBehaviour
|
||||
{
|
||||
// ✅ 正确的用法 - 使用条件编译
|
||||
#if MEOWMENT_DEBUG_TOOL
|
||||
[DebugButton("重置游戏数据")]
|
||||
public static void ResetGameData()
|
||||
{
|
||||
Debug.Log("游戏数据已重置");
|
||||
PlayerPrefs.DeleteAll();
|
||||
}
|
||||
#endif
|
||||
|
||||
// ❌ 错误的用法 - 不使用条件编译
|
||||
// 当包被卸载时,这会导致编译错误
|
||||
// [DebugButton("重置游戏数据")]
|
||||
// public static void ResetGameData()
|
||||
// {
|
||||
// Debug.Log("游戏数据已重置");
|
||||
// PlayerPrefs.DeleteAll();
|
||||
// }
|
||||
}
|
||||
```
|
||||
|
||||
### 高级示例
|
||||
|
||||
#### 1. 带颜色的按钮
|
||||
|
||||
```csharp
|
||||
#if MEOWMENT_DEBUG_TOOL
|
||||
[DebugButton("危险操作", 1f, 0f, 0f)] // 红色按钮
|
||||
public static void DangerousOperation()
|
||||
{
|
||||
Debug.LogWarning("执行危险操作!");
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
#### 2. 多个调试按钮
|
||||
|
||||
```csharp
|
||||
public class DebugCommands
|
||||
{
|
||||
#if MEOWMENT_DEBUG_TOOL
|
||||
[DebugButton("添加金币 +100")]
|
||||
public static void AddGold()
|
||||
{
|
||||
// 你的逻辑
|
||||
Debug.Log("添加100金币");
|
||||
}
|
||||
|
||||
[DebugButton("添加经验 +500")]
|
||||
public static void AddExp()
|
||||
{
|
||||
// 你的逻辑
|
||||
Debug.Log("添加500经验");
|
||||
}
|
||||
|
||||
[DebugButton("解锁所有关卡", 0f, 1f, 0f)] // 绿色按钮
|
||||
public static void UnlockAllLevels()
|
||||
{
|
||||
// 你的逻辑
|
||||
Debug.Log("所有关卡已解锁");
|
||||
}
|
||||
|
||||
[DebugButton("清空背包", 1f, 0.5f, 0f)] // 橙色按钮
|
||||
public static void ClearInventory()
|
||||
{
|
||||
// 你的逻辑
|
||||
Debug.Log("背包已清空");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 调用包内API
|
||||
|
||||
```csharp
|
||||
public class GameUI : MonoBehaviour
|
||||
{
|
||||
private void Update()
|
||||
{
|
||||
// 按下 F12 打开/关闭调试工具
|
||||
if (Input.GetKeyDown(KeyCode.F12))
|
||||
{
|
||||
#if MEOWMENT_DEBUG_TOOL
|
||||
UniversalDebugTool.Toggle();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSettingsButtonClick()
|
||||
{
|
||||
// 显示输入对话框
|
||||
#if MEOWMENT_DEBUG_TOOL
|
||||
UniversalDebugTool.ShowInputDialog(
|
||||
"输入玩家名称",
|
||||
(name) => {
|
||||
Debug.Log($"玩家名称:{name}");
|
||||
},
|
||||
"默认名称"
|
||||
);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 工作原理
|
||||
|
||||
1. **包安装时**:Unity 会自动定义 `MEOWMENT_DEBUG_TOOL` 宏
|
||||
2. **包卸载时**:`MEOWMENT_DEBUG_TOOL` 宏会被自动移除
|
||||
3. **条件编译**:`#if MEOWMENT_DEBUG_TOOL` 块内的代码在包卸载后会被编译器忽略
|
||||
|
||||
### 最佳实践
|
||||
|
||||
✅ **推荐做法**:
|
||||
- 所有使用 `DebugButtonAttribute` 的地方都用 `#if MEOWMENT_DEBUG_TOOL` 包裹
|
||||
- 所有调用 `UniversalDebugTool` API 的地方都用条件编译包裹
|
||||
- 将所有调试方法集中在一个或几个专门的类中
|
||||
|
||||
❌ **不推荐做法**:
|
||||
- 直接使用特性而不加条件编译
|
||||
- 在核心业务逻辑中混入调试代码
|
||||
- 依赖调试工具的功能来实现正式功能
|
||||
|
||||
### 常见问题
|
||||
|
||||
**Q: 为什么要使用条件编译?**
|
||||
A: 当你卸载这个包时,`DebugButtonAttribute` 类就不存在了,会导致编译错误。使用条件编译可以让代码在包不存在时被编译器忽略。
|
||||
|
||||
**Q: 忘记加条件编译怎么办?**
|
||||
A: 如果卸载包后出现编译错误,只需在报错的代码前后加上 `#if MEOWMENT_DEBUG_TOOL` 和 `#endif` 即可。
|
||||
|
||||
**Q: 可以在正式发布版本中使用吗?**
|
||||
A: 可以!条件编译的代码不会影响性能。但建议在正式版本中完全移除或禁用调试工具包。
|
||||
|
||||
**Q: 如何完全禁用调试工具?**
|
||||
A: 在 Player Settings → Scripting Define Symbols 中移除 `MEOWMENT_DEBUG_TOOL`,或直接卸载这个包。
|
||||
|
||||
### 参考
|
||||
|
||||
- [Unity 条件编译文档](https://docs.unity3d.com/Manual/PlatformDependentCompilation.html)
|
||||
- [Package Manager 版本定义](https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html#define-symbols)
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab342e44111b45e409658513642a8ea9
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,12 +1,15 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using MeowmentDebugTool;
|
||||
|
||||
/// <summary>
|
||||
/// 诊断UniversalDebugTool的引用问题
|
||||
/// </summary>
|
||||
public class DebugToolDiagnostic : EditorWindow
|
||||
namespace MeowmentDebugTool.Editor
|
||||
{
|
||||
[MenuItem("Tools/Debug Tool/诊断引用问题")]
|
||||
/// <summary>
|
||||
/// 诊断UniversalDebugTool的引用问题
|
||||
/// </summary>
|
||||
public class DebugToolDiagnostic : EditorWindow
|
||||
{
|
||||
[MenuItem("Tools/Debug Tool/诊断引用问题")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<DebugToolDiagnostic>("调试工具诊断");
|
||||
@ -124,4 +127,5 @@ public class DebugToolDiagnostic : EditorWindow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,14 +2,17 @@ using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
using MeowmentDebugTool;
|
||||
|
||||
/// <summary>
|
||||
/// 通用调试工具预制件生成器
|
||||
/// 用于创建UniversalDebugTool所需的完整UI预制件
|
||||
/// </summary>
|
||||
public class DebugToolPrefabGenerator : EditorWindow
|
||||
namespace MeowmentDebugTool.Editor
|
||||
{
|
||||
private string prefabSavePath = "Assets/UniversalDebugTool.prefab";
|
||||
/// <summary>
|
||||
/// 通用调试工具预制件生成器
|
||||
/// 用于创建UniversalDebugTool所需的完整UI预制件
|
||||
/// </summary>
|
||||
public class DebugToolPrefabGenerator : EditorWindow
|
||||
{
|
||||
private string prefabSavePath = "Assets/UniversalDebugTool.prefab";
|
||||
|
||||
[MenuItem("Tools/Debug Tool/生成调试工具预制件")]
|
||||
public static void ShowWindow()
|
||||
@ -972,4 +975,5 @@ public class DebugToolPrefabGenerator : EditorWindow
|
||||
|
||||
so.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MeowmentDebugTool.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动添加 MEOWMENT_DEBUG_TOOL 宏定义
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
public static class DefineSymbolsManager
|
||||
{
|
||||
private const string DEFINE_SYMBOL = "MEOWMENT_DEBUG_TOOL";
|
||||
|
||||
static DefineSymbolsManager()
|
||||
{
|
||||
AddDefineSymbol();
|
||||
}
|
||||
|
||||
private static void AddDefineSymbol()
|
||||
{
|
||||
var target = EditorUserBuildSettings.selectedBuildTargetGroup;
|
||||
|
||||
// 获取当前已定义的符号
|
||||
string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(target);
|
||||
var defines = definesString.Split(';');
|
||||
|
||||
// 检查是否已存在
|
||||
bool exists = false;
|
||||
foreach (var define in defines)
|
||||
{
|
||||
if (define.Trim() == DEFINE_SYMBOL)
|
||||
{
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不存在则添加
|
||||
if (!exists)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(definesString))
|
||||
{
|
||||
definesString += ";";
|
||||
}
|
||||
definesString += DEFINE_SYMBOL;
|
||||
|
||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(target, definesString);
|
||||
Debug.Log($"[MeowmentDebugTool] 已添加宏定义: {DEFINE_SYMBOL}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5698ea2959bec274ea3e28b53f9ba8e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "Hontbei.Meowmentdebugtool.Editor",
|
||||
"name": "com.bywaystudios.meowmentdebugtool.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"Hontbei.Meowmentdebugtool",
|
||||
"com.bywaystudios.meowmentdebugtool",
|
||||
"Unity.TextMeshPro"
|
||||
],
|
||||
"includePlatforms": [
|
||||
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee28ff85037a9844f95ee9595c069fa4
|
||||
guid: a7ea52de8e3323042b5161a7449156b4
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
@ -51,32 +51,66 @@
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 在场景中生成调试工具预制体
|
||||
### 1. 在代码中初始化调试工具
|
||||
|
||||
在 Unity 菜单中执行:
|
||||
调试工具现在支持**运行时自动生成UI**,无需手动创建预制体。只需在代码中调用:
|
||||
|
||||
> Tools → Debug Tool → 生成调试工具预制件
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using MeowmentDebugTool;
|
||||
using TMPro;
|
||||
|
||||
在弹窗中选择保存路径(默认:`Assets/UniversalDebugTool.prefab`),点击“生成预制件”。
|
||||
public class GameInitializer : MonoBehaviour
|
||||
{
|
||||
public TMP_FontAsset customFont; // 可选:自定义字体
|
||||
|
||||
void Start()
|
||||
{
|
||||
// 初始化调试工具(自动创建UI)
|
||||
UniversalDebugTool.Init();
|
||||
|
||||
// 可选:设置自定义SDF字体
|
||||
if (customFont != null)
|
||||
{
|
||||
UniversalDebugTool.SetSDFFont(customFont);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
生成后的预制体包含:
|
||||
**初始化说明:**
|
||||
|
||||
- 一个独立的 `Canvas`(UI 根节点,排序顺序很高,保证在最上层渲染)。
|
||||
- `UniversalDebugTool` 主组件和其所需的 UI 层级。
|
||||
- 主调试窗口(含标签页、参数页、自定义按钮页、工具栏页、设置页)。
|
||||
- 可拖拽的调试悬浮按钮。
|
||||
- `UniversalDebugTool.Init()` 会自动创建完整的UI系统,包括:
|
||||
- 独立的 Canvas(排序顺序 30000,保证在最上层渲染)
|
||||
- 主调试窗口(含标签页、参数页、自定义按钮页、工具栏页、设置页)
|
||||
- 可拖拽的调试悬浮按钮
|
||||
- EventSystem(如果场景中没有)
|
||||
- `UniversalDebugTool.SetSDFFont()` 可以为所有UI文本设置自定义字体(可选)
|
||||
|
||||
### 2. 把预制体放入场景
|
||||
### 2. 运行时使用
|
||||
|
||||
1. 在 Project 窗口中找到生成的 `UniversalDebugTool` 预制体。
|
||||
2. 将其拖入需要调试的场景(建议放到常驻/启动场景里)。
|
||||
3. 进入 Play 模式即可看到调试窗口。
|
||||
进入 Play 模式后:
|
||||
|
||||
默认行为:
|
||||
- 默认显示主调试窗口在左上角
|
||||
- 点击左上角的 **X** 按钮,会隐藏主窗口并显示悬浮按钮
|
||||
- 拖动悬浮按钮改变位置(自动吸附边缘)
|
||||
- 点击悬浮按钮可重新打开主窗口(拖动不会误触)
|
||||
|
||||
- 进入游戏即显示主调试窗口,并隐藏悬浮按钮。
|
||||
- 点击顶部左侧关闭按钮,会隐藏主窗口并显示悬浮按钮。
|
||||
- 拖动悬浮按钮改变位置;点击一次可重新打开主窗口。
|
||||
### 3. 条件编译(推荐)
|
||||
|
||||
为了确保在移除包后不影响主工程,建议使用条件编译:
|
||||
|
||||
```csharp
|
||||
void Start()
|
||||
{
|
||||
#if MEOWMENT_DEBUG_TOOL
|
||||
UniversalDebugTool.Init();
|
||||
UniversalDebugTool.SetSDFFont(customFont);
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
`MEOWMENT_DEBUG_TOOL` 宏会在包安装时自动定义,卸载时自动移除。
|
||||
|
||||
---
|
||||
|
||||
@ -179,14 +213,25 @@ public static class DemoInputDebug
|
||||
|
||||
以下方法均定义在 `UniversalDebugTool` 中:
|
||||
|
||||
**初始化和配置:**
|
||||
- `static void Init()`:初始化调试工具,自动创建完整的UI系统(包括Canvas、主窗口、悬浮按钮、EventSystem等)。
|
||||
- `static void SetSDFFont(TMP_FontAsset fontAsset)`:为所有UI文本设置自定义SDF字体,包括预制件模板和后续创建的按钮。
|
||||
|
||||
**显示控制:**
|
||||
- `static bool InstanceExists`:当前场景中是否存在实例。
|
||||
- `static UniversalDebugTool Instance`:单例实例(通过 `FindObjectOfType` 查找)。
|
||||
- `static UniversalDebugTool Instance`:单例实例。
|
||||
- `static void Show()`:显示调试工具 GameObject。
|
||||
- `static void Hide()`:隐藏调试工具 GameObject。
|
||||
- `static void Toggle()`:在“窗口显示”和“窗口关闭 + 显示悬浮按钮”之间切换。
|
||||
- `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 / 快捷键逻辑中调用这些方法,例如:
|
||||
@ -206,11 +251,25 @@ void Update()
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 依赖:
|
||||
- Unity 新 UI(`UnityEngine.UI`)。
|
||||
- TextMesh Pro(`TMPro`)。
|
||||
- 默认 UI 参考分辨率为 1080×2340,建议以手机竖屏调试为主。可在“设置”页中修改窗口大小以适配不同设备。
|
||||
- 工具栏中的“时间调整”逻辑仅是示例,需要你在 `AdjustGameTime` 中实现自己项目的时间修改逻辑。
|
||||
- 悬浮按钮依赖 `DraggableFloatingButton` 组件,请不要从生成的预制体中移除此组件,否则会失去拖拽/吸附功能。
|
||||
**依赖:**
|
||||
- Unity 新 UI(`UnityEngine.UI`)
|
||||
- TextMesh Pro(`TMPro`)
|
||||
- 包会自动创建 EventSystem(如果场景中没有)
|
||||
|
||||
如需进一步定制(增删标签页、调整布局、修改颜色风格等),推荐直接在生成的 `UniversalDebugTool` 预制体上修改 UI 结构和 `UniversalDebugTool` 组件的序列化字段。
|
||||
**使用建议:**
|
||||
- 默认 UI 参考分辨率为 1080×2340,适合手机竖屏调试。可在"设置"页中修改窗口大小以适配不同设备。
|
||||
- 主窗口锚点在左上角,不受分辨率变化影响。
|
||||
- 悬浮按钮可在全屏范围内拖动,会自动吸附到边缘。
|
||||
- 工具栏中的"时间调整"逻辑仅是示例,需要在 `AdjustGameTime` 中实现实际的时间修改逻辑。
|
||||
|
||||
**字体设置:**
|
||||
- 所有 UI 文本默认使用 TMP 的默认字体。
|
||||
- 调用 `SetSDFFont()` 可以为所有文本(包括后续创建的自定义按钮)设置自定义字体。
|
||||
|
||||
**条件编译:**
|
||||
- 建议使用 `#if MEOWMENT_DEBUG_TOOL` 包裹调试工具相关代码。
|
||||
- 包安装时会自动定义 `MEOWMENT_DEBUG_TOOL` 宏,卸载时自动移除。
|
||||
- 这样可以确保移除包后不影响主工程编译。
|
||||
|
||||
**进一步定制:**
|
||||
如需修改 UI 布局、颜色、标签页等,可以直接修改 `RuntimeUIGenerator.cs` 中的 UI 生成逻辑。
|
||||
@ -0,0 +1,235 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace MeowmentDebugTool
|
||||
{
|
||||
/// <summary>
|
||||
/// 可拖动的悬浮按钮
|
||||
/// 点击打开调试工具,可以在屏幕上自由拖动
|
||||
/// </summary>
|
||||
public class DraggableFloatingButton : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||
{
|
||||
[Header("设置")]
|
||||
[Tooltip("是否限制在屏幕范围内")]
|
||||
public bool clampToScreen = true;
|
||||
|
||||
[Tooltip("与屏幕边缘的最小距离")]
|
||||
public float edgePadding = 20f;
|
||||
|
||||
[Header("自动吸附")]
|
||||
[Tooltip("是否自动吸附到屏幕边缘")]
|
||||
public bool snapToEdge = true;
|
||||
|
||||
[Tooltip("吸附动画时间")]
|
||||
public float snapDuration = 0.3f;
|
||||
|
||||
private RectTransform rectTransform;
|
||||
private Canvas canvas;
|
||||
private Vector2 dragOffset;
|
||||
private bool isDragging = false;
|
||||
private Vector2 targetPosition;
|
||||
private bool isSnapping = false;
|
||||
|
||||
// 用于区分点击和拖动
|
||||
private Vector2 dragStartPosition;
|
||||
private const float clickThreshold = 5f; // 小于这个距离算点击
|
||||
private bool wasDragging = false; // 标记刚才是否拖动过
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
canvas = GetComponentInParent<Canvas>();
|
||||
|
||||
if (canvas == null)
|
||||
{
|
||||
Debug.LogError("DraggableFloatingButton 必须在Canvas下!");
|
||||
}
|
||||
|
||||
targetPosition = rectTransform.anchoredPosition;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// 平滑移动到目标位置(吸附动画)
|
||||
if (isSnapping && !isDragging)
|
||||
{
|
||||
rectTransform.anchoredPosition = Vector2.Lerp(
|
||||
rectTransform.anchoredPosition,
|
||||
targetPosition,
|
||||
Time.deltaTime / snapDuration
|
||||
);
|
||||
|
||||
if (Vector2.Distance(rectTransform.anchoredPosition, targetPosition) < 0.5f)
|
||||
{
|
||||
rectTransform.anchoredPosition = targetPosition;
|
||||
isSnapping = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
isDragging = true;
|
||||
isSnapping = false;
|
||||
|
||||
// 记录起始位置,用于判断是点击还是拖动
|
||||
dragStartPosition = eventData.position;
|
||||
|
||||
// 计算拖动偏移
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
canvas.transform as RectTransform,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out Vector2 localPoint
|
||||
);
|
||||
|
||||
dragOffset = rectTransform.anchoredPosition - localPoint;
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!isDragging) return;
|
||||
|
||||
// 转换屏幕坐标到Canvas局部坐标
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
canvas.transform as RectTransform,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out Vector2 localPoint
|
||||
);
|
||||
|
||||
// 应用拖动偏移
|
||||
Vector2 newPosition = localPoint + dragOffset;
|
||||
|
||||
// 限制在屏幕范围内
|
||||
if (clampToScreen)
|
||||
{
|
||||
newPosition = ClampToScreen(newPosition);
|
||||
}
|
||||
|
||||
rectTransform.anchoredPosition = newPosition;
|
||||
}
|
||||
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
isDragging = false;
|
||||
|
||||
// 计算拖动距离
|
||||
float dragDistance = Vector2.Distance(dragStartPosition, eventData.position);
|
||||
|
||||
// 如果拖动距离超过阈值,说明是拖动而不是点击
|
||||
if (dragDistance > clickThreshold)
|
||||
{
|
||||
wasDragging = true;
|
||||
// 在下一帧重置标记
|
||||
StartCoroutine(ResetDragFlag());
|
||||
}
|
||||
|
||||
// 自动吸附到最近的边缘
|
||||
if (snapToEdge)
|
||||
{
|
||||
SnapToNearestEdge();
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator ResetDragFlag()
|
||||
{
|
||||
yield return null; // 等待一帧
|
||||
wasDragging = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否刚拖动过(用于Button点击事件判断)
|
||||
/// </summary>
|
||||
public bool GetWasDragging()
|
||||
{
|
||||
return wasDragging;
|
||||
}
|
||||
|
||||
private Vector2 ClampToScreen(Vector2 position)
|
||||
{
|
||||
RectTransform canvasRect = canvas.transform as RectTransform;
|
||||
Vector2 canvasSize = canvasRect.sizeDelta;
|
||||
Vector2 buttonSize = rectTransform.sizeDelta;
|
||||
|
||||
// 计算可移动范围
|
||||
float minX = -canvasSize.x / 2 + buttonSize.x / 2 + edgePadding;
|
||||
float maxX = canvasSize.x / 2 - buttonSize.x / 2 - edgePadding;
|
||||
float minY = -canvasSize.y / 2 + buttonSize.y / 2 + edgePadding;
|
||||
float maxY = canvasSize.y / 2 - buttonSize.y / 2 - edgePadding;
|
||||
|
||||
position.x = Mathf.Clamp(position.x, minX, maxX);
|
||||
position.y = Mathf.Clamp(position.y, minY, maxY);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
private void SnapToNearestEdge()
|
||||
{
|
||||
RectTransform canvasRect = canvas.transform as RectTransform;
|
||||
Vector2 canvasSize = canvasRect.sizeDelta;
|
||||
Vector2 currentPos = rectTransform.anchoredPosition;
|
||||
|
||||
// 计算到各个边缘的距离
|
||||
float distToLeft = Mathf.Abs(currentPos.x + canvasSize.x / 2);
|
||||
float distToRight = Mathf.Abs(currentPos.x - canvasSize.x / 2);
|
||||
float distToTop = Mathf.Abs(currentPos.y - canvasSize.y / 2);
|
||||
float distToBottom = Mathf.Abs(currentPos.y + canvasSize.y / 2);
|
||||
|
||||
// 找到最近的边
|
||||
float minDist = Mathf.Min(distToLeft, distToRight, distToTop, distToBottom);
|
||||
|
||||
Vector2 snapPosition = currentPos;
|
||||
Vector2 buttonSize = rectTransform.sizeDelta;
|
||||
|
||||
if (minDist == distToLeft)
|
||||
{
|
||||
// 吸附到左边
|
||||
snapPosition.x = -canvasSize.x / 2 + buttonSize.x / 2 + edgePadding;
|
||||
}
|
||||
else if (minDist == distToRight)
|
||||
{
|
||||
// 吸附到右边
|
||||
snapPosition.x = canvasSize.x / 2 - buttonSize.x / 2 - edgePadding;
|
||||
}
|
||||
else if (minDist == distToTop)
|
||||
{
|
||||
// 吸附到顶部
|
||||
snapPosition.y = canvasSize.y / 2 - buttonSize.y / 2 - edgePadding;
|
||||
}
|
||||
else if (minDist == distToBottom)
|
||||
{
|
||||
// 吸附到底部
|
||||
snapPosition.y = -canvasSize.y / 2 + buttonSize.y / 2 + edgePadding;
|
||||
}
|
||||
|
||||
// 确保在屏幕范围内
|
||||
snapPosition = ClampToScreen(snapPosition);
|
||||
|
||||
targetPosition = snapPosition;
|
||||
isSnapping = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置悬浮按钮位置
|
||||
/// </summary>
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
if (clampToScreen)
|
||||
{
|
||||
position = ClampToScreen(position);
|
||||
}
|
||||
rectTransform.anchoredPosition = position;
|
||||
targetPosition = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前位置
|
||||
/// </summary>
|
||||
public Vector2 GetPosition()
|
||||
{
|
||||
return rectTransform.anchoredPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,812 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
using TMPro;
|
||||
|
||||
namespace MeowmentDebugTool
|
||||
{
|
||||
/// <summary>
|
||||
/// 运行时UI生成器 - 自动创建调试工具的完整UI
|
||||
/// </summary>
|
||||
public static class RuntimeUIGenerator
|
||||
{
|
||||
private const int CANVAS_WIDTH = 1080;
|
||||
private const int CANVAS_HEIGHT = 2340;
|
||||
|
||||
/// <summary>
|
||||
/// 创建完整的调试工具UI
|
||||
/// </summary>
|
||||
public static UniversalDebugTool CreateDebugToolUI()
|
||||
{
|
||||
// 检查并创建EventSystem
|
||||
EnsureEventSystem();
|
||||
|
||||
// 创建根Canvas
|
||||
GameObject canvasObj = new GameObject("UniversalDebugTool_Canvas");
|
||||
Canvas canvas = canvasObj.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 30000;
|
||||
canvas.overrideSorting = true;
|
||||
|
||||
CanvasScaler scaler = canvasObj.AddComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
|
||||
scaler.matchWidthOrHeight = 0.5f;
|
||||
|
||||
canvasObj.AddComponent<GraphicRaycaster>();
|
||||
|
||||
// 创建 UniversalDebugTool 组件
|
||||
UniversalDebugTool tool = canvasObj.AddComponent<UniversalDebugTool>();
|
||||
|
||||
// 创建主窗口
|
||||
GameObject mainWindow = CreateMainWindow(canvasObj.transform);
|
||||
|
||||
// 创建悬浮按钮
|
||||
GameObject floatingButton = CreateFloatingButton(canvasObj.transform);
|
||||
|
||||
// 创建标签页
|
||||
GameObject tabContainer = mainWindow.transform.Find("TabButtonContainer").gameObject;
|
||||
GameObject contentContainer = mainWindow.transform.Find("ContentContainer").gameObject;
|
||||
|
||||
GameObject parametersPage = CreateParametersPage(contentContainer.transform);
|
||||
GameObject customButtonsPage = CreateCustomButtonsPage(contentContainer.transform);
|
||||
GameObject toolbarPage = CreateToolbarPage(contentContainer.transform);
|
||||
GameObject settingsPage = CreateSettingsPage(contentContainer.transform);
|
||||
|
||||
// 创建输入对话框
|
||||
GameObject inputDialog = CreateInputDialog(canvasObj.transform);
|
||||
|
||||
// 设置引用
|
||||
SetupReferences(tool, canvas, mainWindow, floatingButton,
|
||||
parametersPage, customButtonsPage, toolbarPage, settingsPage, inputDialog);
|
||||
|
||||
return tool;
|
||||
}
|
||||
|
||||
private static GameObject CreateMainWindow(Transform parent)
|
||||
{
|
||||
GameObject mainWindow = new GameObject("MainWindow");
|
||||
mainWindow.transform.SetParent(parent, false);
|
||||
RectTransform rect = mainWindow.AddComponent<RectTransform>();
|
||||
rect.anchorMin = new Vector2(0, 1);
|
||||
rect.anchorMax = new Vector2(0, 1);
|
||||
rect.pivot = new Vector2(0, 1);
|
||||
rect.sizeDelta = new Vector2(CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||
rect.anchoredPosition = Vector2.zero;
|
||||
|
||||
// 背景
|
||||
Image bg = mainWindow.AddComponent<Image>();
|
||||
bg.color = new Color(0.1f, 0.1f, 0.1f, 0.95f);
|
||||
|
||||
// 关闭按钮(左上角)
|
||||
GameObject closeBtn = CreateButton("CloseButton", mainWindow.transform, "X");
|
||||
RectTransform closeBtnRect = closeBtn.GetComponent<RectTransform>();
|
||||
closeBtnRect.anchorMin = new Vector2(0, 1);
|
||||
closeBtnRect.anchorMax = new Vector2(0, 1);
|
||||
closeBtnRect.pivot = new Vector2(0, 1);
|
||||
closeBtnRect.sizeDelta = new Vector2(100, 100);
|
||||
closeBtnRect.anchoredPosition = new Vector2(10, -10);
|
||||
closeBtn.GetComponent<Image>().color = new Color(0.8f, 0.2f, 0.2f, 1f);
|
||||
|
||||
// 标签按钮容器(从关闭按钮右侧开始)
|
||||
GameObject tabContainer = new GameObject("TabButtonContainer");
|
||||
tabContainer.transform.SetParent(mainWindow.transform, false);
|
||||
RectTransform tabRect = tabContainer.AddComponent<RectTransform>();
|
||||
tabRect.anchorMin = new Vector2(0, 1);
|
||||
tabRect.anchorMax = new Vector2(1, 1);
|
||||
tabRect.pivot = new Vector2(0, 1);
|
||||
tabRect.sizeDelta = new Vector2(-120, 100);
|
||||
tabRect.anchoredPosition = new Vector2(120, 0);
|
||||
|
||||
HorizontalLayoutGroup tabLayout = tabContainer.AddComponent<HorizontalLayoutGroup>();
|
||||
tabLayout.childControlWidth = true;
|
||||
tabLayout.childControlHeight = true;
|
||||
tabLayout.childForceExpandWidth = true;
|
||||
tabLayout.childForceExpandHeight = true;
|
||||
tabLayout.spacing = 10;
|
||||
tabLayout.padding = new RectOffset(10, 20, 10, 10);
|
||||
|
||||
// 内容容器
|
||||
GameObject contentContainer = new GameObject("ContentContainer");
|
||||
contentContainer.transform.SetParent(mainWindow.transform, false);
|
||||
RectTransform contentRect = contentContainer.AddComponent<RectTransform>();
|
||||
contentRect.anchorMin = new Vector2(0, 0);
|
||||
contentRect.anchorMax = new Vector2(1, 1);
|
||||
contentRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
contentRect.offsetMin = new Vector2(20, 20);
|
||||
contentRect.offsetMax = new Vector2(-20, -120);
|
||||
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
private static GameObject CreateFloatingButton(Transform parent)
|
||||
{
|
||||
GameObject floatBtn = new GameObject("FloatingButton");
|
||||
floatBtn.transform.SetParent(parent, false);
|
||||
RectTransform rect = floatBtn.AddComponent<RectTransform>();
|
||||
rect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
rect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
rect.pivot = new Vector2(0.5f, 0.5f);
|
||||
rect.sizeDelta = new Vector2(120, 120);
|
||||
rect.anchoredPosition = new Vector2(400, 0);
|
||||
|
||||
Image img = floatBtn.AddComponent<Image>();
|
||||
img.color = new Color(0.2f, 0.6f, 1f, 0.8f);
|
||||
|
||||
Button btn = floatBtn.AddComponent<Button>();
|
||||
|
||||
// 添加文本
|
||||
GameObject textObj = new GameObject("Text");
|
||||
textObj.transform.SetParent(floatBtn.transform, false);
|
||||
RectTransform textRect = textObj.AddComponent<RectTransform>();
|
||||
textRect.anchorMin = Vector2.zero;
|
||||
textRect.anchorMax = Vector2.one;
|
||||
textRect.sizeDelta = Vector2.zero;
|
||||
|
||||
TextMeshProUGUI text = textObj.AddComponent<TextMeshProUGUI>();
|
||||
text.text = "调试";
|
||||
text.fontSize = 36;
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.color = Color.white;
|
||||
|
||||
// 添加拖拽组件
|
||||
DraggableFloatingButton draggable = floatBtn.AddComponent<DraggableFloatingButton>();
|
||||
draggable.clampToScreen = true;
|
||||
draggable.snapToEdge = true;
|
||||
|
||||
return floatBtn;
|
||||
}
|
||||
|
||||
private static GameObject CreateParametersPage(Transform parent)
|
||||
{
|
||||
GameObject page = new GameObject("ParametersPage");
|
||||
page.transform.SetParent(parent, false);
|
||||
RectTransform rect = page.AddComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.sizeDelta = Vector2.zero;
|
||||
|
||||
ScrollRect scroll = page.AddComponent<ScrollRect>();
|
||||
scroll.horizontal = false;
|
||||
scroll.vertical = true;
|
||||
|
||||
GameObject viewport = new GameObject("Viewport");
|
||||
viewport.transform.SetParent(page.transform, false);
|
||||
RectTransform vpRect = viewport.AddComponent<RectTransform>();
|
||||
vpRect.anchorMin = Vector2.zero;
|
||||
vpRect.anchorMax = Vector2.one;
|
||||
vpRect.sizeDelta = Vector2.zero;
|
||||
viewport.AddComponent<Image>().color = new Color(0, 0, 0, 0.1f);
|
||||
viewport.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
GameObject content = new GameObject("Content");
|
||||
content.transform.SetParent(viewport.transform, false);
|
||||
RectTransform contentRect = content.AddComponent<RectTransform>();
|
||||
contentRect.anchorMin = new Vector2(0, 1);
|
||||
contentRect.anchorMax = new Vector2(1, 1);
|
||||
contentRect.pivot = new Vector2(0.5f, 1);
|
||||
contentRect.sizeDelta = new Vector2(0, 2000);
|
||||
|
||||
VerticalLayoutGroup layout = content.AddComponent<VerticalLayoutGroup>();
|
||||
layout.childControlWidth = true;
|
||||
layout.childControlHeight = true;
|
||||
layout.childForceExpandWidth = true;
|
||||
layout.childForceExpandHeight = false;
|
||||
layout.spacing = 20;
|
||||
layout.padding = new RectOffset(20, 20, 20, 20);
|
||||
|
||||
ContentSizeFitter fitter = content.AddComponent<ContentSizeFitter>();
|
||||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
scroll.viewport = vpRect;
|
||||
scroll.content = contentRect;
|
||||
|
||||
// 设备信息文本
|
||||
GameObject deviceInfo = CreateTextObject("DeviceInfo", content.transform, "设备信息");
|
||||
// 系统信息文本
|
||||
GameObject systemInfo = CreateTextObject("SystemInfo", content.transform, "系统信息");
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
private static GameObject CreateCustomButtonsPage(Transform parent)
|
||||
{
|
||||
GameObject page = new GameObject("CustomButtonsPage");
|
||||
page.transform.SetParent(parent, false);
|
||||
RectTransform rect = page.AddComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.sizeDelta = Vector2.zero;
|
||||
|
||||
ScrollRect scroll = page.AddComponent<ScrollRect>();
|
||||
|
||||
GameObject viewport = new GameObject("Viewport");
|
||||
viewport.transform.SetParent(page.transform, false);
|
||||
RectTransform vpRect = viewport.AddComponent<RectTransform>();
|
||||
vpRect.anchorMin = Vector2.zero;
|
||||
vpRect.anchorMax = Vector2.one;
|
||||
vpRect.sizeDelta = Vector2.zero;
|
||||
viewport.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
GameObject buttonContainer = new GameObject("ButtonContainer");
|
||||
buttonContainer.transform.SetParent(viewport.transform, false);
|
||||
RectTransform containerRect = buttonContainer.AddComponent<RectTransform>();
|
||||
containerRect.anchorMin = new Vector2(0, 1);
|
||||
containerRect.anchorMax = new Vector2(1, 1);
|
||||
containerRect.pivot = new Vector2(0.5f, 1);
|
||||
|
||||
GridLayoutGroup grid = buttonContainer.AddComponent<GridLayoutGroup>();
|
||||
grid.cellSize = new Vector2(300, 100);
|
||||
grid.spacing = new Vector2(20, 20);
|
||||
grid.padding = new RectOffset(20, 20, 20, 20);
|
||||
grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
|
||||
grid.constraintCount = 3;
|
||||
|
||||
ContentSizeFitter fitter = buttonContainer.AddComponent<ContentSizeFitter>();
|
||||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
scroll.viewport = vpRect;
|
||||
scroll.content = containerRect;
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
private static GameObject CreateToolbarPage(Transform parent)
|
||||
{
|
||||
GameObject page = new GameObject("ToolbarPage");
|
||||
page.transform.SetParent(parent, false);
|
||||
RectTransform rect = page.AddComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.sizeDelta = Vector2.zero;
|
||||
|
||||
// 时间调整面板
|
||||
GameObject timePanel = new GameObject("TimeAdjustPanel");
|
||||
timePanel.transform.SetParent(page.transform, false);
|
||||
RectTransform timePanelRect = timePanel.AddComponent<RectTransform>();
|
||||
timePanelRect.anchorMin = new Vector2(0, 0.7f);
|
||||
timePanelRect.anchorMax = new Vector2(1, 1);
|
||||
timePanelRect.offsetMin = new Vector2(20, 0);
|
||||
timePanelRect.offsetMax = new Vector2(-20, -20);
|
||||
|
||||
VerticalLayoutGroup vLayout = timePanel.AddComponent<VerticalLayoutGroup>();
|
||||
vLayout.spacing = 20;
|
||||
vLayout.padding = new RectOffset(20, 20, 20, 20);
|
||||
vLayout.childControlWidth = true;
|
||||
vLayout.childControlHeight = false;
|
||||
vLayout.childForceExpandWidth = true;
|
||||
|
||||
// 标题
|
||||
GameObject title = CreateTextObject("Title", timePanel.transform, "时间调整");
|
||||
title.GetComponent<TextMeshProUGUI>().fontSize = 36;
|
||||
title.GetComponent<TextMeshProUGUI>().alignment = TextAlignmentOptions.Center;
|
||||
title.GetComponent<RectTransform>().sizeDelta = new Vector2(0, 60);
|
||||
|
||||
// 滑块
|
||||
GameObject slider = CreateSlider("TimeAdjustSlider", timePanel.transform);
|
||||
slider.GetComponent<RectTransform>().sizeDelta = new Vector2(0, 60);
|
||||
|
||||
// 数值显示
|
||||
GameObject valueText = CreateTextObject("TimeAdjustValueText", timePanel.transform, "60 秒");
|
||||
valueText.GetComponent<TextMeshProUGUI>().fontSize = 32;
|
||||
valueText.GetComponent<TextMeshProUGUI>().alignment = TextAlignmentOptions.Center;
|
||||
valueText.GetComponent<RectTransform>().sizeDelta = new Vector2(0, 50);
|
||||
|
||||
// 按钮容器
|
||||
GameObject btnContainer = new GameObject("ButtonContainer");
|
||||
btnContainer.transform.SetParent(timePanel.transform, false);
|
||||
RectTransform btnRect = btnContainer.AddComponent<RectTransform>();
|
||||
btnRect.sizeDelta = new Vector2(0, 100);
|
||||
|
||||
HorizontalLayoutGroup hLayout = btnContainer.AddComponent<HorizontalLayoutGroup>();
|
||||
hLayout.spacing = 20;
|
||||
hLayout.childControlWidth = true;
|
||||
hLayout.childControlHeight = true;
|
||||
hLayout.childForceExpandWidth = true;
|
||||
hLayout.childForceExpandHeight = true;
|
||||
|
||||
GameObject increaseBtn = CreateButton("TimeIncreaseButton", btnContainer.transform, "+10秒");
|
||||
GameObject decreaseBtn = CreateButton("TimeDecreaseButton", btnContainer.transform, "-10秒");
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
private static GameObject CreateSettingsPage(Transform parent)
|
||||
{
|
||||
GameObject page = new GameObject("SettingsPage");
|
||||
page.transform.SetParent(parent, false);
|
||||
RectTransform rect = page.AddComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.sizeDelta = Vector2.zero;
|
||||
|
||||
// 分辨率设置面板
|
||||
GameObject resPanel = new GameObject("ResolutionPanel");
|
||||
resPanel.transform.SetParent(page.transform, false);
|
||||
RectTransform resPanelRect = resPanel.AddComponent<RectTransform>();
|
||||
resPanelRect.anchorMin = new Vector2(0, 0.6f);
|
||||
resPanelRect.anchorMax = new Vector2(1, 1);
|
||||
resPanelRect.offsetMin = new Vector2(20, 0);
|
||||
resPanelRect.offsetMax = new Vector2(-20, -20);
|
||||
|
||||
VerticalLayoutGroup vLayout = resPanel.AddComponent<VerticalLayoutGroup>();
|
||||
vLayout.spacing = 30;
|
||||
vLayout.padding = new RectOffset(40, 40, 40, 40);
|
||||
vLayout.childControlWidth = true;
|
||||
vLayout.childControlHeight = false;
|
||||
vLayout.childForceExpandWidth = true;
|
||||
|
||||
// 标题
|
||||
GameObject title = CreateTextObject("Title", resPanel.transform, "分辨率设置");
|
||||
title.GetComponent<TextMeshProUGUI>().fontSize = 36;
|
||||
title.GetComponent<TextMeshProUGUI>().alignment = TextAlignmentOptions.Center;
|
||||
title.GetComponent<RectTransform>().sizeDelta = new Vector2(0, 60);
|
||||
|
||||
// 输入字段容器
|
||||
GameObject inputContainer = new GameObject("InputContainer");
|
||||
inputContainer.transform.SetParent(resPanel.transform, false);
|
||||
RectTransform inputRect = inputContainer.AddComponent<RectTransform>();
|
||||
inputRect.sizeDelta = new Vector2(0, 120);
|
||||
|
||||
HorizontalLayoutGroup hLayout = inputContainer.AddComponent<HorizontalLayoutGroup>();
|
||||
hLayout.spacing = 20;
|
||||
hLayout.childControlWidth = true;
|
||||
hLayout.childControlHeight = true;
|
||||
hLayout.childForceExpandWidth = true;
|
||||
hLayout.childForceExpandHeight = true;
|
||||
|
||||
// 宽度输入
|
||||
GameObject widthInput = CreateInputField("WidthInputField", inputContainer.transform, "1080");
|
||||
widthInput.GetComponent<RectTransform>().sizeDelta = new Vector2(0, 100);
|
||||
|
||||
// x 分隔符
|
||||
GameObject separator = CreateTextObject("X", inputContainer.transform, "×");
|
||||
separator.GetComponent<TextMeshProUGUI>().fontSize = 48;
|
||||
separator.GetComponent<TextMeshProUGUI>().alignment = TextAlignmentOptions.Center;
|
||||
separator.GetComponent<RectTransform>().sizeDelta = new Vector2(80, 100);
|
||||
|
||||
// 高度输入
|
||||
GameObject heightInput = CreateInputField("HeightInputField", inputContainer.transform, "2340");
|
||||
heightInput.GetComponent<RectTransform>().sizeDelta = new Vector2(0, 100);
|
||||
|
||||
// 按钮容器
|
||||
GameObject btnContainer = new GameObject("ButtonContainer");
|
||||
btnContainer.transform.SetParent(resPanel.transform, false);
|
||||
RectTransform btnRect = btnContainer.AddComponent<RectTransform>();
|
||||
btnRect.sizeDelta = new Vector2(0, 100);
|
||||
|
||||
HorizontalLayoutGroup btnLayout = btnContainer.AddComponent<HorizontalLayoutGroup>();
|
||||
btnLayout.spacing = 20;
|
||||
btnLayout.childControlWidth = true;
|
||||
btnLayout.childControlHeight = true;
|
||||
btnLayout.childForceExpandWidth = true;
|
||||
btnLayout.childForceExpandHeight = true;
|
||||
|
||||
GameObject applyBtn = CreateButton("ApplyResolutionButton", btnContainer.transform, "应用");
|
||||
GameObject resetBtn = CreateButton("ResetResolutionButton", btnContainer.transform, "重置");
|
||||
|
||||
// 当前分辨率显示
|
||||
GameObject currentText = CreateTextObject("CurrentResolutionText", resPanel.transform, "当前: 1080×2340");
|
||||
currentText.GetComponent<TextMeshProUGUI>().fontSize = 28;
|
||||
currentText.GetComponent<TextMeshProUGUI>().alignment = TextAlignmentOptions.Center;
|
||||
currentText.GetComponent<RectTransform>().sizeDelta = new Vector2(0, 60);
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
private static GameObject CreateInputDialog(Transform parent)
|
||||
{
|
||||
GameObject dialog = new GameObject("InputDialog");
|
||||
dialog.transform.SetParent(parent, false);
|
||||
RectTransform rect = dialog.AddComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.sizeDelta = Vector2.zero;
|
||||
|
||||
// 半透明背景
|
||||
Image bg = dialog.AddComponent<Image>();
|
||||
bg.color = new Color(0, 0, 0, 0.8f);
|
||||
|
||||
// 面板
|
||||
GameObject panel = new GameObject("Panel");
|
||||
panel.transform.SetParent(dialog.transform, false);
|
||||
RectTransform panelRect = panel.AddComponent<RectTransform>();
|
||||
panelRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
panelRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
panelRect.sizeDelta = new Vector2(900, 500);
|
||||
panel.AddComponent<Image>().color = new Color(0.2f, 0.2f, 0.2f, 1f);
|
||||
|
||||
VerticalLayoutGroup vLayout = panel.AddComponent<VerticalLayoutGroup>();
|
||||
vLayout.spacing = 30;
|
||||
vLayout.padding = new RectOffset(40, 40, 40, 40);
|
||||
vLayout.childControlWidth = true;
|
||||
vLayout.childControlHeight = false;
|
||||
vLayout.childForceExpandWidth = true;
|
||||
|
||||
// 标题
|
||||
GameObject title = CreateTextObject("Title", panel.transform, "输入");
|
||||
title.GetComponent<TextMeshProUGUI>().fontSize = 40;
|
||||
title.GetComponent<TextMeshProUGUI>().alignment = TextAlignmentOptions.Center;
|
||||
title.GetComponent<RectTransform>().sizeDelta = new Vector2(0, 70);
|
||||
|
||||
// 输入框
|
||||
GameObject inputField = CreateInputField("InputField", panel.transform, "请输入...");
|
||||
inputField.GetComponent<RectTransform>().sizeDelta = new Vector2(0, 120);
|
||||
|
||||
// 按钮容器
|
||||
GameObject btnContainer = new GameObject("ButtonContainer");
|
||||
btnContainer.transform.SetParent(panel.transform, false);
|
||||
RectTransform btnRect = btnContainer.AddComponent<RectTransform>();
|
||||
btnRect.sizeDelta = new Vector2(0, 100);
|
||||
|
||||
HorizontalLayoutGroup hLayout = btnContainer.AddComponent<HorizontalLayoutGroup>();
|
||||
hLayout.spacing = 40;
|
||||
hLayout.childControlWidth = true;
|
||||
hLayout.childControlHeight = true;
|
||||
hLayout.childForceExpandWidth = true;
|
||||
hLayout.childForceExpandHeight = true;
|
||||
|
||||
GameObject confirmBtn = CreateButton("ConfirmButton", btnContainer.transform, "确定");
|
||||
confirmBtn.GetComponent<Image>().color = new Color(0.2f, 0.6f, 0.2f, 1f);
|
||||
|
||||
GameObject cancelBtn = CreateButton("CancelButton", btnContainer.transform, "取消");
|
||||
cancelBtn.GetComponent<Image>().color = new Color(0.6f, 0.2f, 0.2f, 1f);
|
||||
|
||||
dialog.SetActive(false);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// 获取TMP默认字体,如果没有则返回null
|
||||
/// </summary>
|
||||
private static TMP_FontAsset GetDefaultFont()
|
||||
{
|
||||
// 尝试获取TMP的默认字体
|
||||
TMP_FontAsset defaultFont = TMP_Settings.defaultFontAsset;
|
||||
|
||||
// 如果没有默认字体,尝试加载LiberationSans
|
||||
if (defaultFont == null)
|
||||
{
|
||||
defaultFont = Resources.Load<TMP_FontAsset>("Fonts & Materials/LiberationSans SDF");
|
||||
}
|
||||
|
||||
return defaultFont;
|
||||
}
|
||||
|
||||
private static GameObject CreateButton(string name, Transform parent, string text)
|
||||
{
|
||||
GameObject btn = new GameObject(name);
|
||||
btn.transform.SetParent(parent, false);
|
||||
RectTransform rect = btn.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(200, 80);
|
||||
|
||||
Image img = btn.AddComponent<Image>();
|
||||
img.color = new Color(0.3f, 0.3f, 0.3f, 1f);
|
||||
|
||||
Button button = btn.AddComponent<Button>();
|
||||
|
||||
GameObject textObj = new GameObject("Text");
|
||||
textObj.transform.SetParent(btn.transform, false);
|
||||
RectTransform textRect = textObj.AddComponent<RectTransform>();
|
||||
textRect.anchorMin = Vector2.zero;
|
||||
textRect.anchorMax = Vector2.one;
|
||||
textRect.sizeDelta = Vector2.zero;
|
||||
|
||||
TextMeshProUGUI tmp = textObj.AddComponent<TextMeshProUGUI>();
|
||||
tmp.text = text;
|
||||
tmp.fontSize = 32;
|
||||
tmp.alignment = TextAlignmentOptions.Center;
|
||||
tmp.color = Color.white;
|
||||
|
||||
// 设置默认字体
|
||||
TMP_FontAsset defaultFont = GetDefaultFont();
|
||||
if (defaultFont != null)
|
||||
{
|
||||
tmp.font = defaultFont;
|
||||
}
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
private static GameObject CreateTextObject(string name, Transform parent, string text)
|
||||
{
|
||||
GameObject textObj = new GameObject(name);
|
||||
textObj.transform.SetParent(parent, false);
|
||||
RectTransform rect = textObj.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(800, 200);
|
||||
|
||||
TextMeshProUGUI tmp = textObj.AddComponent<TextMeshProUGUI>();
|
||||
tmp.text = text;
|
||||
tmp.fontSize = 28;
|
||||
tmp.alignment = TextAlignmentOptions.TopLeft;
|
||||
tmp.color = Color.white;
|
||||
|
||||
// 设置默认字体
|
||||
TMP_FontAsset defaultFont = GetDefaultFont();
|
||||
if (defaultFont != null)
|
||||
{
|
||||
tmp.font = defaultFont;
|
||||
}
|
||||
|
||||
return textObj;
|
||||
}
|
||||
|
||||
private static GameObject CreateInputField(string name, Transform parent, string placeholder)
|
||||
{
|
||||
GameObject inputObj = new GameObject(name);
|
||||
inputObj.transform.SetParent(parent, false);
|
||||
RectTransform rect = inputObj.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(600, 80);
|
||||
|
||||
Image img = inputObj.AddComponent<Image>();
|
||||
img.color = new Color(0.15f, 0.15f, 0.15f, 1f);
|
||||
|
||||
TMP_InputField input = inputObj.AddComponent<TMP_InputField>();
|
||||
|
||||
GameObject textArea = new GameObject("TextArea");
|
||||
textArea.transform.SetParent(inputObj.transform, false);
|
||||
RectTransform taRect = textArea.AddComponent<RectTransform>();
|
||||
taRect.anchorMin = Vector2.zero;
|
||||
taRect.anchorMax = Vector2.one;
|
||||
taRect.sizeDelta = Vector2.zero;
|
||||
taRect.offsetMin = new Vector2(10, 10);
|
||||
taRect.offsetMax = new Vector2(-10, -10);
|
||||
|
||||
GameObject textObj = new GameObject("Text");
|
||||
textObj.transform.SetParent(textArea.transform, false);
|
||||
RectTransform textRect = textObj.AddComponent<RectTransform>();
|
||||
textRect.anchorMin = Vector2.zero;
|
||||
textRect.anchorMax = Vector2.one;
|
||||
textRect.sizeDelta = Vector2.zero;
|
||||
|
||||
TextMeshProUGUI text = textObj.AddComponent<TextMeshProUGUI>();
|
||||
text.fontSize = 32;
|
||||
text.color = Color.white;
|
||||
|
||||
// 设置默认字体
|
||||
TMP_FontAsset defaultFont = GetDefaultFont();
|
||||
if (defaultFont != null)
|
||||
{
|
||||
text.font = defaultFont;
|
||||
}
|
||||
|
||||
GameObject placeholderObj = new GameObject("Placeholder");
|
||||
placeholderObj.transform.SetParent(textArea.transform, false);
|
||||
RectTransform phRect = placeholderObj.AddComponent<RectTransform>();
|
||||
phRect.anchorMin = Vector2.zero;
|
||||
phRect.anchorMax = Vector2.one;
|
||||
phRect.sizeDelta = Vector2.zero;
|
||||
|
||||
TextMeshProUGUI phText = placeholderObj.AddComponent<TextMeshProUGUI>();
|
||||
phText.text = placeholder;
|
||||
phText.fontSize = 32;
|
||||
phText.color = new Color(1, 1, 1, 0.5f);
|
||||
|
||||
// 设置默认字体
|
||||
if (defaultFont != null)
|
||||
{
|
||||
phText.font = defaultFont;
|
||||
}
|
||||
|
||||
input.textViewport = taRect;
|
||||
input.textComponent = text;
|
||||
input.placeholder = phText;
|
||||
|
||||
return inputObj;
|
||||
}
|
||||
|
||||
private static GameObject CreateSlider(string name, Transform parent)
|
||||
{
|
||||
GameObject sliderObj = new GameObject(name);
|
||||
sliderObj.transform.SetParent(parent, false);
|
||||
RectTransform rect = sliderObj.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(800, 60);
|
||||
|
||||
Slider slider = sliderObj.AddComponent<Slider>();
|
||||
slider.minValue = 0;
|
||||
slider.maxValue = 300;
|
||||
slider.value = 60;
|
||||
|
||||
// Background
|
||||
GameObject bg = new GameObject("Background");
|
||||
bg.transform.SetParent(sliderObj.transform, false);
|
||||
RectTransform bgRect = bg.AddComponent<RectTransform>();
|
||||
bgRect.anchorMin = Vector2.zero;
|
||||
bgRect.anchorMax = Vector2.one;
|
||||
bgRect.sizeDelta = Vector2.zero;
|
||||
bg.AddComponent<Image>().color = new Color(0.2f, 0.2f, 0.2f, 1f);
|
||||
|
||||
// Fill Area
|
||||
GameObject fillArea = new GameObject("Fill Area");
|
||||
fillArea.transform.SetParent(sliderObj.transform, false);
|
||||
RectTransform faRect = fillArea.AddComponent<RectTransform>();
|
||||
faRect.anchorMin = Vector2.zero;
|
||||
faRect.anchorMax = Vector2.one;
|
||||
faRect.sizeDelta = Vector2.zero;
|
||||
|
||||
GameObject fill = new GameObject("Fill");
|
||||
fill.transform.SetParent(fillArea.transform, false);
|
||||
RectTransform fillRect = fill.AddComponent<RectTransform>();
|
||||
fillRect.anchorMin = Vector2.zero;
|
||||
fillRect.anchorMax = Vector2.one;
|
||||
fillRect.sizeDelta = Vector2.zero;
|
||||
fill.AddComponent<Image>().color = new Color(0.2f, 0.6f, 1f, 1f);
|
||||
|
||||
slider.fillRect = fillRect;
|
||||
|
||||
// Handle
|
||||
GameObject handleArea = new GameObject("Handle Slide Area");
|
||||
handleArea.transform.SetParent(sliderObj.transform, false);
|
||||
RectTransform haRect = handleArea.AddComponent<RectTransform>();
|
||||
haRect.anchorMin = Vector2.zero;
|
||||
haRect.anchorMax = Vector2.one;
|
||||
haRect.sizeDelta = Vector2.zero;
|
||||
|
||||
GameObject handle = new GameObject("Handle");
|
||||
handle.transform.SetParent(handleArea.transform, false);
|
||||
RectTransform handleRect = handle.AddComponent<RectTransform>();
|
||||
handleRect.sizeDelta = new Vector2(40, 60);
|
||||
handle.AddComponent<Image>().color = Color.white;
|
||||
|
||||
slider.handleRect = handleRect;
|
||||
slider.targetGraphic = handle.GetComponent<Image>();
|
||||
|
||||
return sliderObj;
|
||||
}
|
||||
|
||||
private static GameObject CreateCustomButtonPrefab()
|
||||
{
|
||||
// 创建一个隐藏的临时父对象来存放预制件模板
|
||||
GameObject tempParent = new GameObject("_TempPrefabHolder");
|
||||
tempParent.SetActive(false);
|
||||
Object.DontDestroyOnLoad(tempParent);
|
||||
|
||||
GameObject btn = CreateButton("CustomButton", tempParent.transform, "按钮");
|
||||
btn.GetComponent<RectTransform>().sizeDelta = new Vector2(280, 90);
|
||||
|
||||
// 确保预制件的TextMeshProUGUI也能被SetSDF设置
|
||||
// 通过将其标记到特殊层或添加特殊标签
|
||||
btn.tag = "Untagged"; // 保持默认,确保能被找到
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
private static GameObject CreateTabButtonPrefab()
|
||||
{
|
||||
// 使用同一个临时父对象
|
||||
GameObject tempParent = GameObject.Find("_TempPrefabHolder");
|
||||
if (tempParent == null)
|
||||
{
|
||||
tempParent = new GameObject("_TempPrefabHolder");
|
||||
tempParent.SetActive(false);
|
||||
Object.DontDestroyOnLoad(tempParent);
|
||||
}
|
||||
|
||||
GameObject btn = CreateButton("TabButton", tempParent.transform, "标签");
|
||||
btn.GetComponent<RectTransform>().sizeDelta = new Vector2(200, 80);
|
||||
|
||||
// 确保预制件的TextMeshProUGUI也能被SetSDF设置
|
||||
btn.tag = "Untagged"; // 保持默认,确保能被找到
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保场景中有EventSystem,如果没有则创建一个
|
||||
/// </summary>
|
||||
private static void EnsureEventSystem()
|
||||
{
|
||||
EventSystem eventSystem = Object.FindObjectOfType<EventSystem>();
|
||||
if (eventSystem == null)
|
||||
{
|
||||
GameObject eventSystemObj = new GameObject("EventSystem");
|
||||
eventSystem = eventSystemObj.AddComponent<EventSystem>();
|
||||
eventSystemObj.AddComponent<StandaloneInputModule>();
|
||||
Object.DontDestroyOnLoad(eventSystemObj);
|
||||
Debug.Log("[RuntimeUIGenerator] 已自动创建EventSystem");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[RuntimeUIGenerator] EventSystem已存在");
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetupReferences(UniversalDebugTool tool, Canvas canvas, GameObject mainWindow,
|
||||
GameObject floatingButton, GameObject parametersPage, GameObject customButtonsPage,
|
||||
GameObject toolbarPage, GameObject settingsPage, GameObject inputDialog)
|
||||
{
|
||||
// 使用反射设置私有字段
|
||||
var type = typeof(UniversalDebugTool);
|
||||
var flags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance;
|
||||
|
||||
// 主窗口
|
||||
type.GetField("mainWindow", flags)?.SetValue(tool, mainWindow.GetComponent<RectTransform>());
|
||||
type.GetField("canvas", flags)?.SetValue(tool, canvas);
|
||||
type.GetField("tabButtonContainer", flags)?.SetValue(tool, mainWindow.transform.Find("TabButtonContainer").GetComponent<RectTransform>());
|
||||
type.GetField("contentContainer", flags)?.SetValue(tool, mainWindow.transform.Find("ContentContainer").GetComponent<RectTransform>());
|
||||
type.GetField("closeButton", flags)?.SetValue(tool, mainWindow.transform.Find("CloseButton").GetComponent<Button>());
|
||||
|
||||
// 悬浮按钮
|
||||
type.GetField("floatingButton", flags)?.SetValue(tool, floatingButton);
|
||||
type.GetField("draggableComponent", flags)?.SetValue(tool, floatingButton.GetComponent<DraggableFloatingButton>());
|
||||
|
||||
// 参数查看页面
|
||||
type.GetField("parametersPage", flags)?.SetValue(tool, parametersPage);
|
||||
type.GetField("deviceInfoText", flags)?.SetValue(tool, parametersPage.transform.Find("Viewport/Content/DeviceInfo").GetComponent<TextMeshProUGUI>());
|
||||
type.GetField("systemInfoText", flags)?.SetValue(tool, parametersPage.transform.Find("Viewport/Content/SystemInfo").GetComponent<TextMeshProUGUI>());
|
||||
type.GetField("parametersScrollRect", flags)?.SetValue(tool, parametersPage.GetComponent<ScrollRect>());
|
||||
|
||||
// 自定义按钮页面
|
||||
type.GetField("customButtonsPage", flags)?.SetValue(tool, customButtonsPage);
|
||||
type.GetField("buttonContainer", flags)?.SetValue(tool, customButtonsPage.transform.Find("Viewport/ButtonContainer").GetComponent<RectTransform>());
|
||||
type.GetField("buttonsScrollRect", flags)?.SetValue(tool, customButtonsPage.GetComponent<ScrollRect>());
|
||||
|
||||
// 创建按钮预制件
|
||||
GameObject buttonPrefab = CreateCustomButtonPrefab();
|
||||
GameObject tabButtonPrefab = CreateTabButtonPrefab();
|
||||
type.GetField("buttonPrefab", flags)?.SetValue(tool, buttonPrefab);
|
||||
type.GetField("tabButtonPrefab", flags)?.SetValue(tool, tabButtonPrefab);
|
||||
|
||||
// 工具栏页面
|
||||
type.GetField("toolbarPage", flags)?.SetValue(tool, toolbarPage);
|
||||
Transform toolbarPanel = toolbarPage.transform.Find("TimeAdjustPanel");
|
||||
if (toolbarPanel != null)
|
||||
{
|
||||
type.GetField("timeAdjustSlider", flags)?.SetValue(tool, toolbarPanel.Find("TimeAdjustSlider")?.GetComponent<Slider>());
|
||||
type.GetField("timeAdjustValueText", flags)?.SetValue(tool, toolbarPanel.Find("TimeAdjustValueText")?.GetComponent<TextMeshProUGUI>());
|
||||
Transform btnContainer = toolbarPanel.Find("ButtonContainer");
|
||||
if (btnContainer != null)
|
||||
{
|
||||
type.GetField("timeIncreaseButton", flags)?.SetValue(tool, btnContainer.Find("TimeIncreaseButton")?.GetComponent<Button>());
|
||||
type.GetField("timeDecreaseButton", flags)?.SetValue(tool, btnContainer.Find("TimeDecreaseButton")?.GetComponent<Button>());
|
||||
}
|
||||
}
|
||||
|
||||
// 设置页面
|
||||
type.GetField("settingsPage", flags)?.SetValue(tool, settingsPage);
|
||||
Transform resPanel = settingsPage.transform.Find("ResolutionPanel");
|
||||
if (resPanel != null)
|
||||
{
|
||||
Transform inputContainer = resPanel.Find("InputContainer");
|
||||
if (inputContainer != null)
|
||||
{
|
||||
type.GetField("widthInputField", flags)?.SetValue(tool, inputContainer.Find("WidthInputField")?.GetComponent<TMP_InputField>());
|
||||
type.GetField("heightInputField", flags)?.SetValue(tool, inputContainer.Find("HeightInputField")?.GetComponent<TMP_InputField>());
|
||||
}
|
||||
|
||||
Transform btnContainer = resPanel.Find("ButtonContainer");
|
||||
if (btnContainer != null)
|
||||
{
|
||||
type.GetField("applyResolutionButton", flags)?.SetValue(tool, btnContainer.Find("ApplyResolutionButton")?.GetComponent<Button>());
|
||||
type.GetField("resetResolutionButton", flags)?.SetValue(tool, btnContainer.Find("ResetResolutionButton")?.GetComponent<Button>());
|
||||
}
|
||||
|
||||
type.GetField("currentResolutionText", flags)?.SetValue(tool, resPanel.Find("CurrentResolutionText")?.GetComponent<TextMeshProUGUI>());
|
||||
}
|
||||
|
||||
// 输入对话框
|
||||
type.GetField("inputDialog", flags)?.SetValue(tool, inputDialog);
|
||||
Transform dialogPanel = inputDialog.transform.Find("Panel");
|
||||
if (dialogPanel != null)
|
||||
{
|
||||
type.GetField("inputDialogTitle", flags)?.SetValue(tool, dialogPanel.Find("Title")?.GetComponent<TextMeshProUGUI>());
|
||||
type.GetField("inputDialogInputField", flags)?.SetValue(tool, dialogPanel.Find("InputField")?.GetComponent<TMP_InputField>());
|
||||
|
||||
Transform btnContainer = dialogPanel.Find("ButtonContainer");
|
||||
if (btnContainer != null)
|
||||
{
|
||||
type.GetField("inputDialogConfirmBtn", flags)?.SetValue(tool, btnContainer.Find("ConfirmButton")?.GetComponent<Button>());
|
||||
type.GetField("inputDialogCancelBtn", flags)?.SetValue(tool, btnContainer.Find("CancelButton")?.GetComponent<Button>());
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log("[RuntimeUIGenerator] 所有引用已设置完成");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ddf96f73f2918f64f908000cd6d4de2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -6,11 +6,17 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
/// <summary>
|
||||
/// 通用调试工具 - 支持多标签页的调试界面系统
|
||||
/// 默认分辨率: 1080x2340
|
||||
/// </summary>
|
||||
public class UniversalDebugTool : MonoBehaviour
|
||||
namespace MeowmentDebugTool
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用调试工具 - 支持多标签页的调试界面系统
|
||||
/// 默认分辨率: 1080x2340
|
||||
/// 使用方法:
|
||||
/// 1. 场景中放置 UniversalDebugTool 预制件
|
||||
/// 2. 在代码中调用 UniversalDebugTool.Init() 初始化
|
||||
/// 3. 未调用 Init() 前不会显示任何UI
|
||||
/// </summary>
|
||||
public class UniversalDebugTool : MonoBehaviour
|
||||
{
|
||||
#region 序列化字段
|
||||
[Header("主窗口设置")]
|
||||
@ -66,6 +72,7 @@ public class UniversalDebugTool : MonoBehaviour
|
||||
|
||||
#region 私有变量
|
||||
private static UniversalDebugTool instance;
|
||||
private static bool isInitialized = false;
|
||||
private Dictionary<string, GameObject> pages = new Dictionary<string, GameObject>();
|
||||
private Dictionary<string, Button> tabButtons = new Dictionary<string, Button>();
|
||||
private string currentPageKey = "";
|
||||
@ -76,6 +83,12 @@ public class UniversalDebugTool : MonoBehaviour
|
||||
|
||||
// 自定义按钮回调
|
||||
private static Action<Button, TMP_Text> customButtonCallback;
|
||||
|
||||
// Canvas层级设置
|
||||
private const int TOP_SORT_ORDER = 30000;
|
||||
|
||||
// 保存的SDF字体资源
|
||||
private static TMP_FontAsset savedFontAsset = null;
|
||||
#endregion
|
||||
|
||||
#region 单例
|
||||
@ -100,6 +113,7 @@ public class UniversalDebugTool : MonoBehaviour
|
||||
if (instance == null)
|
||||
{
|
||||
instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
else if (instance != this)
|
||||
{
|
||||
@ -108,17 +122,33 @@ public class UniversalDebugTool : MonoBehaviour
|
||||
}
|
||||
|
||||
currentCustomResolution = defaultResolution;
|
||||
InitializeDebugTool();
|
||||
|
||||
// 设置Canvas为最上层
|
||||
SetCanvasToTop();
|
||||
|
||||
// 未初始化前隐藏所有UI
|
||||
if (!isInitialized)
|
||||
{
|
||||
HideAllUI();
|
||||
}
|
||||
// 注意:如果isInitialized为true,不在Awake中调用InitializeDebugTool
|
||||
// 因为此时反射设置的字段可能还没生效,延迟到Start中调用
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
UpdateDeviceInfo();
|
||||
UpdateSystemInfo();
|
||||
LoadCustomButtons();
|
||||
|
||||
// 初始状态:显示主窗口,隐藏悬浮按钮
|
||||
ShowMainWindow();
|
||||
// 只有在已初始化但还没调用过InitializeDebugTool时才执行
|
||||
// 这避免了Awake和Init()的重复调用问题
|
||||
if (isInitialized && pages.Count == 0)
|
||||
{
|
||||
// 这是通过Init()创建的实例,需要在Start中完成初始化
|
||||
// 此时反射设置的字段已经生效
|
||||
InitializeDebugTool();
|
||||
UpdateDeviceInfo();
|
||||
UpdateSystemInfo();
|
||||
LoadCustomButtons();
|
||||
ShowMainWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
@ -131,6 +161,115 @@ public class UniversalDebugTool : MonoBehaviour
|
||||
#endregion
|
||||
|
||||
#region 初始化
|
||||
/// <summary>
|
||||
/// 初始化调试工具(自动创建UI)
|
||||
/// </summary>
|
||||
public static void Init()
|
||||
{
|
||||
if (isInitialized)
|
||||
{
|
||||
Debug.LogWarning("[MeowmentDebugTool] 已经初始化过了");
|
||||
return;
|
||||
}
|
||||
|
||||
isInitialized = true;
|
||||
Debug.Log("[MeowmentDebugTool] 开始初始化调试工具...");
|
||||
|
||||
// 如果场景中没有实例,运行时自动创建UI
|
||||
if (!InstanceExists)
|
||||
{
|
||||
Debug.Log("[MeowmentDebugTool] 运行时自动创建UI...");
|
||||
instance = RuntimeUIGenerator.CreateDebugToolUI();
|
||||
}
|
||||
|
||||
if (InstanceExists)
|
||||
{
|
||||
Instance.InitializeDebugTool();
|
||||
Instance.UpdateDeviceInfo();
|
||||
Instance.UpdateSystemInfo();
|
||||
Instance.LoadCustomButtons();
|
||||
Instance.ShowMainWindow();
|
||||
|
||||
Debug.Log("[MeowmentDebugTool] ✅ 初始化完成!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置所有TextMeshProUGUI的SDF字体
|
||||
/// </summary>
|
||||
/// <param name="fontAsset">TMP字体资源</param>
|
||||
public static void SetSDFFont(TMP_FontAsset fontAsset)
|
||||
{
|
||||
if (!InstanceExists)
|
||||
{
|
||||
Debug.LogWarning("[MeowmentDebugTool] 调试工具未初始化,无法设置字体");
|
||||
return;
|
||||
}
|
||||
|
||||
if (fontAsset == null)
|
||||
{
|
||||
Debug.LogWarning("[MeowmentDebugTool] 字体资源为空");
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
// 1. 获取调试工具Canvas下的所有TextMeshProUGUI组件(包括隐藏的)
|
||||
TextMeshProUGUI[] allTexts = Instance.GetComponentsInChildren<TextMeshProUGUI>(true);
|
||||
foreach (var text in allTexts)
|
||||
{
|
||||
text.font = fontAsset;
|
||||
count++;
|
||||
}
|
||||
|
||||
// 2. 查找预制件模板(_TempPrefabHolder)下的TextMeshProUGUI
|
||||
GameObject tempPrefabHolder = GameObject.Find("_TempPrefabHolder");
|
||||
if (tempPrefabHolder != null)
|
||||
{
|
||||
TextMeshProUGUI[] prefabTexts = tempPrefabHolder.GetComponentsInChildren<TextMeshProUGUI>(true);
|
||||
foreach (var text in prefabTexts)
|
||||
{
|
||||
text.font = fontAsset;
|
||||
count++;
|
||||
}
|
||||
Debug.Log($"[MeowmentDebugTool] 已设置预制件模板中的 {prefabTexts.Length} 个文本组件");
|
||||
}
|
||||
|
||||
Debug.Log($"[MeowmentDebugTool] 共将 {count} 个文本组件的字体设置为: {fontAsset.name}");
|
||||
|
||||
// 3. 保存字体资源,供后续创建的按钮使用
|
||||
savedFontAsset = fontAsset;
|
||||
Debug.Log("[MeowmentDebugTool] 字体资源已保存,后续创建的按钮将自动应用此字体");
|
||||
|
||||
// 4. 重新加载自定义按钮,使新字体应用到已存在的CustomButton
|
||||
Debug.Log("[MeowmentDebugTool] 重新加载自定义按钮以应用新字体...");
|
||||
Instance.ReloadCustomButtons();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Canvas为最上层
|
||||
/// </summary>
|
||||
private void SetCanvasToTop()
|
||||
{
|
||||
if (canvas != null)
|
||||
{
|
||||
canvas.sortingOrder = TOP_SORT_ORDER;
|
||||
canvas.overrideSorting = true;
|
||||
Debug.Log($"[MeowmentDebugTool] Canvas层级设置为: {TOP_SORT_ORDER}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏所有UI
|
||||
/// </summary>
|
||||
private void HideAllUI()
|
||||
{
|
||||
if (mainWindow != null)
|
||||
mainWindow.gameObject.SetActive(false);
|
||||
if (floatingButton != null)
|
||||
floatingButton.SetActive(false);
|
||||
}
|
||||
|
||||
private void InitializeDebugTool()
|
||||
{
|
||||
Debug.Log("🚀 初始化UniversalDebugTool...");
|
||||
@ -206,7 +345,10 @@ public class UniversalDebugTool : MonoBehaviour
|
||||
{
|
||||
if (tabButtonPrefab == null || tabButtonContainer == null)
|
||||
{
|
||||
Debug.LogError("❌ 标签按钮预制件或容器未设置!请重新生成预制件。");
|
||||
string errorDetails = $"tabButtonPrefab={(tabButtonPrefab == null ? "null" : "已设置")}, " +
|
||||
$"tabButtonContainer={(tabButtonContainer == null ? "null" : "已设置")}";
|
||||
Debug.LogWarning($"⚠️ [MeowmentDebugTool] 标签按钮预制件或容器未完全初始化:{errorDetails}");
|
||||
Debug.LogWarning("💡 这可能是Unity初始化顺序问题,如果标签正常显示则可以忽略此警告");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -418,6 +560,12 @@ public class UniversalDebugTool : MonoBehaviour
|
||||
if (buttonText != null)
|
||||
{
|
||||
buttonText.text = string.IsNullOrEmpty(attribute.DisplayName) ? method.Name : attribute.DisplayName;
|
||||
|
||||
// 应用保存的字体
|
||||
if (savedFontAsset != null)
|
||||
{
|
||||
buttonText.font = savedFontAsset;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置按钮颜色
|
||||
@ -437,6 +585,9 @@ public class UniversalDebugTool : MonoBehaviour
|
||||
// 调用回调
|
||||
customButtonCallback?.Invoke(button, buttonText);
|
||||
customButtonCallback = null;
|
||||
|
||||
// 点击按钮后关闭主窗口
|
||||
CloseDebugWindow();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -736,28 +887,43 @@ public class UniversalDebugTool : MonoBehaviour
|
||||
private void ShowMainWindow()
|
||||
{
|
||||
if (mainWindow != null)
|
||||
mainWindow.gameObject.SetActive(true);
|
||||
mainWindow.gameObject.SetActive(false);
|
||||
|
||||
if (floatingButton != null)
|
||||
floatingButton.SetActive(false);
|
||||
floatingButton.SetActive(true);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region 自定义特性
|
||||
/// <summary>
|
||||
/// 调试按钮特性 - 标记方法为调试按钮
|
||||
///
|
||||
/// 使用说明:
|
||||
/// 1. 在主项目中使用此特性时,请用条件编译包裹:
|
||||
///
|
||||
/// #if MEOWMENT_DEBUG_TOOL
|
||||
/// [DebugButton("我的调试按钮")]
|
||||
/// public static void MyDebugMethod()
|
||||
/// {
|
||||
/// Debug.Log("调试方法被执行");
|
||||
/// }
|
||||
/// #endif
|
||||
///
|
||||
/// 2. 这样当包被卸载时,代码不会报错,也不会影响主工程的正常运行
|
||||
/// 3. MEOWMENT_DEBUG_TOOL 宏会在包安装时自动定义,卸载时自动移除
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||
public class DebugButtonAttribute : Attribute
|
||||
{
|
||||
public string DisplayName { get; set; }
|
||||
public Color ButtonColor { get; set; }
|
||||
|
||||
public DebugButtonAttribute(string displayName = "", float r = 0.8f, float g = 0.8f, float b = 0.8f)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
ButtonColor = new Color(r, g, b, 1f);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region 自定义特性
|
||||
/// <summary>
|
||||
/// 调试按钮特性 - 标记方法为调试按钮
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||
public class DebugButtonAttribute : Attribute
|
||||
{
|
||||
public string DisplayName { get; set; }
|
||||
public Color ButtonColor { get; set; }
|
||||
|
||||
public DebugButtonAttribute(string displayName = "", float r = 0.8f, float g = 0.8f, float b = 0.8f)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
ButtonColor = new Color(r, g, b, 1f);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "com.bywaystudios.meowmentdebugtool",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"Unity.TextMeshPro"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3672213eff4fbe048ae6efde3d783518
|
||||
guid: de9c38beb5c3ed94e83a7a91fbde340d
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "com.bywaystudios.meowmentdebugtool",
|
||||
"displayName": "MeowmentDebugTool",
|
||||
"version": "0.1.6",
|
||||
"version": "0.2.2",
|
||||
"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": [
|
||||
{
|
||||
@ -1,232 +0,0 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 可拖动的悬浮按钮
|
||||
/// 点击打开调试工具,可以在屏幕上自由拖动
|
||||
/// </summary>
|
||||
public class DraggableFloatingButton : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||
{
|
||||
[Header("设置")]
|
||||
[Tooltip("是否限制在屏幕范围内")]
|
||||
public bool clampToScreen = true;
|
||||
|
||||
[Tooltip("与屏幕边缘的最小距离")]
|
||||
public float edgePadding = 20f;
|
||||
|
||||
[Header("自动吸附")]
|
||||
[Tooltip("是否自动吸附到屏幕边缘")]
|
||||
public bool snapToEdge = true;
|
||||
|
||||
[Tooltip("吸附动画时间")]
|
||||
public float snapDuration = 0.3f;
|
||||
|
||||
private RectTransform rectTransform;
|
||||
private Canvas canvas;
|
||||
private Vector2 dragOffset;
|
||||
private bool isDragging = false;
|
||||
private Vector2 targetPosition;
|
||||
private bool isSnapping = false;
|
||||
|
||||
// 用于区分点击和拖动
|
||||
private Vector2 dragStartPosition;
|
||||
private const float clickThreshold = 5f; // 小于这个距离算点击
|
||||
private bool wasDragging = false; // 标记刚才是否拖动过
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
canvas = GetComponentInParent<Canvas>();
|
||||
|
||||
if (canvas == null)
|
||||
{
|
||||
Debug.LogError("DraggableFloatingButton 必须在Canvas下!");
|
||||
}
|
||||
|
||||
targetPosition = rectTransform.anchoredPosition;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// 平滑移动到目标位置(吸附动画)
|
||||
if (isSnapping && !isDragging)
|
||||
{
|
||||
rectTransform.anchoredPosition = Vector2.Lerp(
|
||||
rectTransform.anchoredPosition,
|
||||
targetPosition,
|
||||
Time.deltaTime / snapDuration
|
||||
);
|
||||
|
||||
if (Vector2.Distance(rectTransform.anchoredPosition, targetPosition) < 0.5f)
|
||||
{
|
||||
rectTransform.anchoredPosition = targetPosition;
|
||||
isSnapping = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
isDragging = true;
|
||||
isSnapping = false;
|
||||
|
||||
// 记录起始位置,用于判断是点击还是拖动
|
||||
dragStartPosition = eventData.position;
|
||||
|
||||
// 计算拖动偏移
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
canvas.transform as RectTransform,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out Vector2 localPoint
|
||||
);
|
||||
|
||||
dragOffset = rectTransform.anchoredPosition - localPoint;
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!isDragging) return;
|
||||
|
||||
// 转换屏幕坐标到Canvas局部坐标
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
canvas.transform as RectTransform,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out Vector2 localPoint
|
||||
);
|
||||
|
||||
// 应用拖动偏移
|
||||
Vector2 newPosition = localPoint + dragOffset;
|
||||
|
||||
// 限制在屏幕范围内
|
||||
if (clampToScreen)
|
||||
{
|
||||
newPosition = ClampToScreen(newPosition);
|
||||
}
|
||||
|
||||
rectTransform.anchoredPosition = newPosition;
|
||||
}
|
||||
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
isDragging = false;
|
||||
|
||||
// 计算拖动距离
|
||||
float dragDistance = Vector2.Distance(dragStartPosition, eventData.position);
|
||||
|
||||
// 如果拖动距离超过阈值,说明是拖动而不是点击
|
||||
if (dragDistance > clickThreshold)
|
||||
{
|
||||
wasDragging = true;
|
||||
// 在下一帧重置标记
|
||||
StartCoroutine(ResetDragFlag());
|
||||
}
|
||||
|
||||
// 自动吸附到最近的边缘
|
||||
if (snapToEdge)
|
||||
{
|
||||
SnapToNearestEdge();
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator ResetDragFlag()
|
||||
{
|
||||
yield return null; // 等待一帧
|
||||
wasDragging = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否刚拖动过(用于Button点击事件判断)
|
||||
/// </summary>
|
||||
public bool GetWasDragging()
|
||||
{
|
||||
return wasDragging;
|
||||
}
|
||||
|
||||
private Vector2 ClampToScreen(Vector2 position)
|
||||
{
|
||||
RectTransform canvasRect = canvas.transform as RectTransform;
|
||||
Vector2 canvasSize = canvasRect.sizeDelta;
|
||||
Vector2 buttonSize = rectTransform.sizeDelta;
|
||||
|
||||
// 计算可移动范围
|
||||
float minX = -canvasSize.x / 2 + buttonSize.x / 2 + edgePadding;
|
||||
float maxX = canvasSize.x / 2 - buttonSize.x / 2 - edgePadding;
|
||||
float minY = -canvasSize.y / 2 + buttonSize.y / 2 + edgePadding;
|
||||
float maxY = canvasSize.y / 2 - buttonSize.y / 2 - edgePadding;
|
||||
|
||||
position.x = Mathf.Clamp(position.x, minX, maxX);
|
||||
position.y = Mathf.Clamp(position.y, minY, maxY);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
private void SnapToNearestEdge()
|
||||
{
|
||||
RectTransform canvasRect = canvas.transform as RectTransform;
|
||||
Vector2 canvasSize = canvasRect.sizeDelta;
|
||||
Vector2 currentPos = rectTransform.anchoredPosition;
|
||||
|
||||
// 计算到各个边缘的距离
|
||||
float distToLeft = Mathf.Abs(currentPos.x + canvasSize.x / 2);
|
||||
float distToRight = Mathf.Abs(currentPos.x - canvasSize.x / 2);
|
||||
float distToTop = Mathf.Abs(currentPos.y - canvasSize.y / 2);
|
||||
float distToBottom = Mathf.Abs(currentPos.y + canvasSize.y / 2);
|
||||
|
||||
// 找到最近的边
|
||||
float minDist = Mathf.Min(distToLeft, distToRight, distToTop, distToBottom);
|
||||
|
||||
Vector2 snapPosition = currentPos;
|
||||
Vector2 buttonSize = rectTransform.sizeDelta;
|
||||
|
||||
if (minDist == distToLeft)
|
||||
{
|
||||
// 吸附到左边
|
||||
snapPosition.x = -canvasSize.x / 2 + buttonSize.x / 2 + edgePadding;
|
||||
}
|
||||
else if (minDist == distToRight)
|
||||
{
|
||||
// 吸附到右边
|
||||
snapPosition.x = canvasSize.x / 2 - buttonSize.x / 2 - edgePadding;
|
||||
}
|
||||
else if (minDist == distToTop)
|
||||
{
|
||||
// 吸附到顶部
|
||||
snapPosition.y = canvasSize.y / 2 - buttonSize.y / 2 - edgePadding;
|
||||
}
|
||||
else if (minDist == distToBottom)
|
||||
{
|
||||
// 吸附到底部
|
||||
snapPosition.y = -canvasSize.y / 2 + buttonSize.y / 2 + edgePadding;
|
||||
}
|
||||
|
||||
// 确保在屏幕范围内
|
||||
snapPosition = ClampToScreen(snapPosition);
|
||||
|
||||
targetPosition = snapPosition;
|
||||
isSnapping = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置悬浮按钮位置
|
||||
/// </summary>
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
if (clampToScreen)
|
||||
{
|
||||
position = ClampToScreen(position);
|
||||
}
|
||||
rectTransform.anchoredPosition = position;
|
||||
targetPosition = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前位置
|
||||
/// </summary>
|
||||
public Vector2 GetPosition()
|
||||
{
|
||||
return rectTransform.anchoredPosition;
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "Hontbei.Meowmentdebugtool",
|
||||
"references": [
|
||||
"Unity.TextMeshPro"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": []
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"com.bywaystudios.meowmentdebugtool": "0.1.4",
|
||||
"com.unity.asset-store-validation": "0.6.0",
|
||||
"com.unity.collab-proxy": "2.10.2",
|
||||
"com.unity.editorcoroutines": "1.0.1",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"com.bywaystudios.meowmentdebugtool": {
|
||||
"version": "file:com.bywaystudios.meowmentdebugtool@0.1.4",
|
||||
"version": "file:com.bywaystudios.meowmentdebugtool",
|
||||
"depth": 0,
|
||||
"source": "embedded",
|
||||
"dependencies": {
|
||||
|
||||
@ -840,7 +840,8 @@ PlayerSettings:
|
||||
webGLMemoryGeometricGrowthStep: 0.2
|
||||
webGLMemoryGeometricGrowthCap: 96
|
||||
webGLPowerPreference: 2
|
||||
scriptingDefineSymbols: {}
|
||||
scriptingDefineSymbols:
|
||||
Standalone: MEOWMENT_DEBUG_TOOL
|
||||
additionalCompilerArguments: {}
|
||||
platformArchitecture: {}
|
||||
scriptingBackend: {}
|
||||
|
||||
BIN
com.bywaystudios.meowmentdebugtool-0.2.2.tgz
Normal file
BIN
com.bywaystudios.meowmentdebugtool-0.2.2.tgz
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user