完美实现背景透明与隐藏任务栏图标
在 Unity 中开发 Windows 桌面宠物或悬浮窗工具时,开发者通常会面临一个经典的“深水区”冲突:既想让游戏窗口背景完全透明,又想在 Windows 任务栏中隐藏该程序的运行图标。传统的做法是修改窗口扩展样式为 WS_EX_TOOLWINDOW,但这往往会破坏 Unity 与 Windows 桌面窗口管理器(DWM)的渲染链,导致透明通道失效,画面变成黑底。
要完美同时实现这两个需求,我们需要将“透明渲染”与“任务栏隐藏”这两个操作彻底剥离。
核心实现原理
解决方案的核心在于:保留标准的无边框透明窗口样式以确保 DWM 渲染正常,并直接调用 Windows Shell 的 COM 接口强制删除任务栏页签。
-
绕过启动黑屏(Splash Screen)限制:
免费版 Unity 强制显示启动 Logo,如果在 Logo 播放完毕前应用透明化 API,画面切换时透明度会被重置。利用协程配合UnityEngine.Rendering.SplashScreen.isFinished,我们可以精准挂起代码,在 Logo 播放完毕且渲染管线稳定后再执行 API 调用。 -
设置无边框与置顶透明:
利用GetActiveWindow获取当前窗口句柄,并通过DwmExtendFrameIntoClientArea将透明帧扩展到整个客户区。同时,使用SetWindowLong为窗口附加WS_EX_LAYERED属性,并结合SetLayeredWindowAttributes激活透明度渲染。利用SetWindowPos与HWND_TOPMOST参数确保窗口始终置顶。 -
COM 接口实现“图标消失术”:
引入 GUID 为56FDF342-FD6D-11d0-958A-006097C9A090的ITaskbarListCOM 接口。在初始化HrInit()后,直接调用DeleteTab(hWnd),从任务栏中强行抹除当前窗口的图标。
完整实现代码
以下是完整的 TransparentWindow.cs 脚本,您可以直接将其挂载到场景中永远不会被销毁的对象上:
// ***********************************************************************************
// FileName: TransparentWindow.cs
// Description: 实现窗口背景透明并利用 COM 接口隐藏任务栏图标
// *************************************************************************************
using System;
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.Rendering;
// ReSharper disable CheckNamespace
namespace OnyxCore.UnityCore.Runtime
{
public class TransparentWindow : MonoBehaviour
{
[DllImport("User32.dll")]
private static extern int MessageBox(IntPtr hWnd, string text, string caption, int type);
[DllImport("User32.dll")]
private static extern IntPtr GetActiveWindow();
[DllImport("User32.dll")]
private static extern IntPtr SetWindowLong(IntPtr hWnd, int index, int value);
[DllImport("User32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int uFlags);
[DllImport("User32.dll")]
private static extern int SetLayeredWindowAttributes(IntPtr hWnd, uint crKey, byte bAlpha, uint dwFlags);
// --- COM 接口:用于直接操作任务栏 (重点在这里) ---
[ComImport]
[Guid("56FDF344-FD6D-11d0-958A-006097C9A090")]
public class TaskbarList
{
}
[ComImport]
[Guid("56FDF342-FD6D-11d0-958A-006097C9A090")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITaskbarList
{
void HrInit();
void AddTab(IntPtr hwnd);
void DeleteTab(IntPtr hwnd);
void ActivateTab(IntPtr hwnd);
void SetActiveAlt(IntPtr hwnd);
}
private struct MARGINS
{
public int cxLeftWidth;
public int cxRightWidth;
public int cyTopHeight;
public int cyBottomHeight;
}
[DllImport("Dwmapi.dll")]
private static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins);
const int GWL_EXSTYLE = -20;
const int WS_EX_TRANSPARENT = 0x00000020;
const int WS_EX_LAYERED = 0x00080000;
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
const uint LWA_COLORKEY = 0x00000001;
// Start is called before the first frame update
private void Start()
{
if (!Application.isEditor)
{
Application.runInBackground = true;
// 依然需要等待 Logo 播放完毕
StartCoroutine(WaitSplashScreenAndApply());
}
}
IEnumerator WaitSplashScreenAndApply()
{
// 1. 等待 Unity 启动动画结束
while (!SplashScreen.isFinished)
{
yield return null;
}
// 2. 额外缓冲 0.5 秒,确保渲染管线稳定
yield return new WaitForSeconds(0.5f);
// 3. 执行透明化和隐藏任务栏图标
ApplyTransparentAndHideTaskbar();
}
void ApplyTransparentAndHideTaskbar()
{
var hWnd = GetActiveWindow();
var margins = new MARGINS { cxLeftWidth = -1 };
DwmExtendFrameIntoClientArea(hWnd, ref margins);
SetWindowLong(hWnd, GWL_EXSTYLE, WS_EX_LAYERED);
SetLayeredWindowAttributes(hWnd, 0, 0, LWA_COLORKEY);
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, 0);
try
{
ITaskbarList taskbar = (ITaskbarList)new TaskbarList();
taskbar.HrInit();
taskbar.DeleteTab(hWnd); // 任务栏图标消失术
}
catch (Exception e)
{
Debug.LogError("隐藏任务栏图标失败: " + e.Message);
}
}
}
}
必要的 Unity 引擎设置
要让上述代码完美生效,您必须配合以下 Unity 渲染设置:
- 摄像机 (Main Camera):将
Clear Flags设置为 Solid Color,并将Background颜色的 Alpha (A) 值设为 0(完全透明)。 - Player Settings:
Fullscreen Mode必须设为 Fullscreen Window。- 务必取消勾选
Use DXGI Flip Model Swapchain for D3D11,否则 DWM 透明层将直接失效。