基于Windows API透明窗口的Sprite拖拽功能实现问题
Unity透明悬浮窗口Sprite拖拽失效的解决方法
问题核心
通过Windows API实现Unity透明悬浮窗口后,常规的Sprite拖拽逻辑失效,原因主要集中在窗口鼠标输入拦截、坐标系差异和碰撞检测适配三个方面。
关键修正点
- 移除导致窗口忽略鼠标输入的
WS_EX_TRANSPARENT样式 - 替换
OnGUI事件为Unity内置的OnMouseDown/OnMouseUp(更适配物体碰撞器检测) - 修正屏幕坐标系与世界坐标系的转换逻辑
- 针对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
相关产品推荐
相关产品推荐

