149 lines
4.9 KiB
Markdown
149 lines
4.9 KiB
Markdown
# 拖动功能问题修复说明
|
||
|
||
## 问题描述
|
||
禁用调试工具的拖动功能后(`enableDragging = false`),发现其他UI上的棋子也无法拖动了。
|
||
|
||
## 问题原因
|
||
|
||
### Unity事件系统机制
|
||
在Unity的事件系统中,当一个GameObject实现了拖动接口(`IBeginDragHandler`, `IDragHandler`, `IEndDragHandler`)时:
|
||
|
||
1. **事件会被该对象"拦截"**:即使在方法内部直接 `return`,Unity也认为该对象已经处理了事件
|
||
2. **事件不会继续传递**:被拦截的事件不会传递给下层或其他UI元素
|
||
3. **影响范围广泛**:这会导致场景中所有在该对象下方的可拖动UI元素都无法接收拖动事件
|
||
|
||
### 之前的实现问题
|
||
```csharp
|
||
public void OnBeginDrag(PointerEventData eventData)
|
||
{
|
||
if (!enableDragging) return; // ❌ 直接返回,但事件已被拦截
|
||
// ... 正常的拖动逻辑
|
||
}
|
||
```
|
||
|
||
这种实现方式会导致:
|
||
- 调试工具的悬浮按钮拦截了所有拖动事件
|
||
- 其他UI元素(如棋子)无法接收到拖动事件
|
||
- 即使 `enableDragging = false`,问题依然存在
|
||
|
||
## 解决方案
|
||
|
||
### 核心思路
|
||
当 `enableDragging = false` 时,不应该简单地返回,而是应该将事件**主动传递**给其他可以处理该事件的UI元素。
|
||
|
||
### 实现细节
|
||
|
||
#### 1. 修改拖动事件处理方法
|
||
在 `OnBeginDrag`, `OnDrag`, `OnEndDrag` 中添加事件传递逻辑:
|
||
|
||
```csharp
|
||
public void OnBeginDrag(PointerEventData eventData)
|
||
{
|
||
if (!enableDragging)
|
||
{
|
||
// ✅ 将事件传递给父级或下层UI元素
|
||
PassEventToParent(eventData, ExecuteEvents.beginDragHandler);
|
||
return;
|
||
}
|
||
// ... 正常的拖动逻辑
|
||
}
|
||
```
|
||
|
||
#### 2. 实现事件传递方法
|
||
添加 `PassEventToParent` 方法,按以下顺序尝试传递事件:
|
||
|
||
```csharp
|
||
private void PassEventToParent<T>(PointerEventData eventData, ExecuteEvents.EventFunction<T> eventFunction)
|
||
where T : IEventSystemHandler
|
||
{
|
||
// 步骤1: 向上传递给父级对象
|
||
Transform parent = transform.parent;
|
||
while (parent != null)
|
||
{
|
||
if (ExecuteEvents.Execute(parent.gameObject, eventData, eventFunction))
|
||
{
|
||
return; // 父级处理了,停止传递
|
||
}
|
||
parent = parent.parent;
|
||
}
|
||
|
||
// 步骤2: 如果父级都没处理,传递给射线检测到的下一个对象
|
||
var raycastResults = new List<RaycastResult>();
|
||
EventSystem.current.RaycastAll(eventData, raycastResults);
|
||
|
||
foreach (var result in raycastResults)
|
||
{
|
||
if (result.gameObject == gameObject)
|
||
continue; // 跳过自己
|
||
|
||
if (ExecuteEvents.Execute(result.gameObject, eventData, eventFunction))
|
||
{
|
||
return; // 下层对象处理了,停止传递
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 工作原理
|
||
|
||
1. **事件拦截**:悬浮按钮首先接收到拖动事件
|
||
2. **条件判断**:检查 `enableDragging` 是否为 `false`
|
||
3. **向上传递**:尝试将事件传递给父级对象树
|
||
4. **射线检测**:如果父级未处理,通过射线检测找到下层UI元素
|
||
5. **事件执行**:让找到的对象执行相应的拖动事件处理
|
||
|
||
## 修改文件
|
||
- `Packages/com.bywaystudios.meowmentdebugtool/Runtime/DraggableFloatingButton.cs`
|
||
- 修改 `OnBeginDrag` 方法
|
||
- 修改 `OnDrag` 方法
|
||
- 修改 `OnEndDrag` 方法
|
||
- 新增 `PassEventToParent` 方法
|
||
|
||
## 测试建议
|
||
|
||
### 测试场景1:禁用拖动模式
|
||
1. 设置 `enableDragging = false`
|
||
2. 点击悬浮按钮 → 应该正常打开调试窗口
|
||
3. 尝试拖动其他UI上的棋子 → **应该能够正常拖动**
|
||
|
||
### 测试场景2:启用拖动模式
|
||
1. 设置 `enableDragging = true`
|
||
2. 拖动悬浮按钮 → 应该能够正常拖动
|
||
3. 点击悬浮按钮(不拖动)→ 应该打开调试窗口
|
||
|
||
### 测试场景3:多层UI叠加
|
||
1. 创建多个可拖动的UI元素,部分重叠
|
||
2. 确保悬浮按钮在最上层
|
||
3. 禁用悬浮按钮拖动后,测试下层UI元素能否正常拖动
|
||
|
||
## 技术要点
|
||
|
||
### Unity事件传递机制
|
||
- `ExecuteEvents.Execute()`:手动触发事件处理
|
||
- `EventSystem.RaycastAll()`:获取所有被射线击中的UI元素
|
||
- 事件处理器接口:`IEventSystemHandler`
|
||
|
||
### 泛型约束
|
||
使用泛型方法支持不同类型的事件处理:
|
||
```csharp
|
||
private void PassEventToParent<T>(...) where T : IEventSystemHandler
|
||
```
|
||
|
||
### 事件类型
|
||
- `ExecuteEvents.beginDragHandler` - 开始拖动
|
||
- `ExecuteEvents.dragHandler` - 拖动中
|
||
- `ExecuteEvents.endDragHandler` - 结束拖动
|
||
|
||
## 可能的副作用
|
||
|
||
### 性能考虑
|
||
- `RaycastAll` 会执行完整的射线检测,可能有轻微性能开销
|
||
- 仅在 `enableDragging = false` 时触发,实际影响很小
|
||
|
||
### 兼容性
|
||
- 需要确保场景中有 `EventSystem`
|
||
- 依赖 Unity 的 EventSystem 模块
|
||
|
||
## 总结
|
||
通过主动传递事件而不是简单地忽略事件,解决了禁用拖动后影响其他UI元素的问题。这是一个典型的UI事件系统问题,需要理解Unity的事件传递机制才能正确处理。
|