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

基于Windows API透明窗口的Sprite拖拽功能实现问题

Unity透明悬浮窗口Sprite拖拽失效的解决方法

问题核心

通过Windows API实现Unity透明悬浮窗口后,常规的Sprite拖拽逻辑失效,原因主要集中在窗口鼠标输入拦截、坐标系差异和碰撞检测适配三个方面。

关键修正点

  1. 移除导致窗口忽略鼠标输入的WS_EX_TRANSPARENT样式
  2. 替换OnGUI事件为Unity内置的OnMouseDown/OnMouseUp(更适配物体碰撞器检测)
  3. 修正屏幕坐标系与世界坐标系的转换逻辑
  4. 针对2D/3D Sprite匹配对应的刚体与碰撞器组件

修改后的拖拽交互脚本

using UnityEngine;

public class MouseInteractions : MonoBehaviour
{
    private Vector3 offset;
    private bool dragging;
    private Rigidbody2D rb2D; // 2D Sprite使用此组件,3D物体改用Rigidbody

    void Awake()
    {
        // 自动获取对应类型的刚体组件
        rb2D = GetComponent<Rigidbody2D>();
        if (rb2D == null)
        {
            GetComponent<Rigidbody>().isKinematic = false;
        }
    }

    void Update()
    {
        if (dragging)
        {
            // 转换鼠标位置到世界坐标,保持Z轴与物体一致避免偏移
            Vector3 mouseWorldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            mouseWorldPos.z = transform.position.z;
            transform.position = mouseWorldPos + offset;
        }
    }

    void OnMouseDown()
    {
        // 激活拖拽时禁用物理模拟
        if (rb2D != null)
        {
            rb2D.isKinematic = true;
        }
        else
        {
            GetComponent<Rigidbody>().isKinematic = true;
        }
        // 计算物体与鼠标的位置偏移量
        Vector3 mouseWorldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        mouseWorldPos.z = transform.position.z;
        offset = transform.position - mouseWorldPos;
        dragging = true;
    }

    void OnMouseUp()
    {
        // 结束拖拽时恢复物理模拟
        dragging = false;
        if (rb2D != null)
        {
            rb2D.isKinematic = false;
        }
        else
        {
            GetComponent<Rigidbody>().isKinematic = false;
        }
    }
}

修改后的窗口设置脚本

using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class WindowSetting : MonoBehaviour
{
    IntPtr window;

    const int GWL_EXSTYLE = -20;
    const uint WS_EX_LAYERED = 0x00080000;
    const uint LWA_COLORKEY = 0x00000001;

    static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
    const uint SWP_NOSIZE = 0x0001;
    const uint SWP_NOMOVE = 0x0002;
    const uint SWP_NOACTIVATE = 0x0010;

    private struct MARGINS
    {
        public int cxLeftWidth;
        public int cxRightWidth;
        public int cyTopHeight;
        public int cyBottomHeight;
    }

    [DllImport("user32.dll")]
    private static extern IntPtr GetActiveWindow();

    [DllImport("Dwmapi.dll")]
    private static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins);

    [DllImport("user32.dll")]
    private static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

    [DllImport("user32.dll")]
    private static extern int SetLayeredWindowAttributes(IntPtr hWnd, uint crKey, byte bAlpha, uint dwFlags);

    void Start()
    {
#if !UNITY_EDITOR
        window = GetActiveWindow();

        // 扩展窗口边框到客户区
        MARGINS margins = new MARGINS() { cxLeftWidth = -1 };
        DwmExtendFrameIntoClientArea(window, ref margins);

        // 设置分层窗口样式,移除透明鼠标穿透属性(避免无法接收鼠标事件)
        SetWindowLong(window, GWL_EXSTYLE, WS_EX_LAYERED);

        // 仅通过颜色键实现透明,保持窗口可接收鼠标输入
        SetLayeredWindowAttributes(window, crKey: 0, bAlpha: 255, LWA_COLORKEY);

        // 窗口置顶,不改变大小位置,不抢占焦点
        SetWindowPos(window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);

        Application.runInBackground = true;
#endif
    }
}

额外注意事项

  • 确保Sprite对象添加了对应碰撞器:2D Sprite加BoxCollider2D,3D物体加BoxCollider,否则OnMouseDown事件不会触发
  • 透明色设置为0(黑色),需保证Sprite本身不包含黑色像素,避免被误透明
  • 若使用正交相机,无需额外处理Z轴;透视相机需确保物体与相机的距离稳定

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:34:53