Unity基础窗口管理器:限制标题栏拖拽时Z序乒乓切换问题
Unity 带Z-order窗口管理器拖拽区域限制后的Z-order异常修复
问题描述
我在Unity中实现了一个带有Z-order的基础窗口管理器,原本运行正常,但将GUI.DragWindow的拖拽区域限制为窗口标题栏后,出现异常:点击B和C窗口的重叠区域时,首次点击B前置,再次点击C前置,循环往复。而使用全屏拖拽(允许从窗口任意位置拖拽)时,问题消失。

问题成因
原代码在调用GUI.Window前的DrawUpdate方法中,提前判断鼠标是否在窗口Rect内并直接消耗事件、调整Z-order。但当拖拽区域限制为标题栏时,点击窗口非标题栏的重叠区域时:
- 上层窗口的
DrawUpdate先捕获MouseDown事件并标记为已使用,调整Z-order将上层窗口前置; - 但
GUI.Window内部因点击位置不在拖拽区域,未真正处理该事件,导致事件被重新弹出(原代码中的Event.PopEvent逻辑); - 下层窗口的
DrawUpdate又捕获到这个弹出的MouseDown事件,再次调整Z-order将下层窗口前置,最终形成循环。
修复方案
将Z-order调整逻辑移到GUI.Window的回调内部,利用Unity GUI的事件处理顺序,只有当窗口真正响应了鼠标点击(或点击未被其他GUI元素消耗)时,才调整Z-order。
修改后的核心代码
public sealed class WindowManager { // 保留原有字段和方法,仅修改Draw方法和新增BringWindowToFront方法 public void Draw(Window window) { using var _ = new GUIColorScope(background: window.Background); var rect = GUI.Window(window.Id, window.Rect, DrawWindow, window.Title); var max = new Vector2(Screen.width, Screen.height) / (Screen.dpi / 96.0f) - rect.size; rect.position = Vector2.Min(Vector2.Max(rect.position, Vector2.zero), max); window.Rect = rect; return; void DrawWindow(int id) { var position = new Rect(new Vector2(window.Rect.width - 16, 4), new Vector2(12, 12)); if (GUI.Button(position, "\u00D7", Styles.Button)) { window.Visible = false; } window.Function(id); // 处理Z-order调整:仅当鼠标左键点击窗口内且未被其他GUI元素消耗时触发 if (Event.current.type == EventType.MouseDown && Event.current.button == 0) { var scaledMousePos = Event.current.mousePosition / Scale; if (window.Rect.Contains(scaledMousePos)) { // 事件未被按钮等元素消耗,说明点击的是窗口空白区域或标题栏 Event.current.Use(); BringWindowToFront(window); } } // 限制拖拽区域为标题栏 var dragRect = new Rect(Vector2.zero, new Vector2(window.Rect.width, 16)); GUI.DragWindow(dragRect); } } /// <summary> /// 将窗口移到Z-order最上层 /// </summary> private void BringWindowToFront(Window window) { GUI.BringWindowToFront(window.Id); LastActive = window; WindowsZOrder.Remove(window); WindowsZOrder.Insert(0, window); } // 删除原有的DrawUpdate方法 }
修复逻辑说明
- 移除提前事件消耗:删掉原
DrawUpdate中提前拦截并消耗MouseDown事件的逻辑,避免错误触发下层窗口的Z-order调整; - 回调内处理事件:在
GUI.Window的回调中检测鼠标点击,确保只有当窗口真正处理了事件(或点击未被按钮等元素消耗)时,才调整Z-order; - 精准区域判断:通过缩放后的鼠标位置判断是否在窗口范围内,保证高DPI下的坐标准确性;
- 统一前置逻辑:将Z-order调整逻辑封装到
BringWindowToFront方法中,避免重复代码。
测试验证
修改后,点击窗口的标题栏、内容空白区域都会将窗口前置,点击关闭按钮只会触发关闭操作而不会调整Z-order,重叠区域点击不会再出现循环前置的异常。
内容的提问来源于stack exchange,提问作者aybe
相关产品推荐
相关产品推荐

