You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity基础窗口管理器:限制标题栏拖拽时Z序乒乓切换问题

Unity 带Z-order窗口管理器拖拽区域限制后的Z-order异常修复

问题描述

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

窗口重叠示意图

问题成因

原代码在调用GUI.Window前的DrawUpdate方法中,提前判断鼠标是否在窗口Rect内并直接消耗事件、调整Z-order。但当拖拽区域限制为标题栏时,点击窗口非标题栏的重叠区域时:

  1. 上层窗口的DrawUpdate先捕获MouseDown事件并标记为已使用,调整Z-order将上层窗口前置;
  2. 但GUI.Window内部因点击位置不在拖拽区域,未真正处理该事件,导致事件被重新弹出(原代码中的Event.PopEvent逻辑);
  3. 下层窗口的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方法
}

修复逻辑说明

  1. 移除提前事件消耗:删掉原DrawUpdate中提前拦截并消耗MouseDown事件的逻辑,避免错误触发下层窗口的Z-order调整;
  2. 回调内处理事件:在GUI.Window的回调中检测鼠标点击,确保只有当窗口真正处理了事件(或点击未被按钮等元素消耗)时,才调整Z-order;
  3. 精准区域判断:通过缩放后的鼠标位置判断是否在窗口范围内,保证高DPI下的坐标准确性;
  4. 统一前置逻辑:将Z-order调整逻辑封装到BringWindowToFront方法中,避免重复代码。

测试验证

修改后,点击窗口的标题栏、内容空白区域都会将窗口前置,点击关闭按钮只会触发关闭操作而不会调整Z-order,重叠区域点击不会再出现循环前置的异常。

内容的提问来源于stack exchange,提问作者aybe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 08:50:56