Implementação de Janelas Transparentes no Unity para Windows

Este artigo explora técnicas para tornar uma janela do Unity transparente no Windows, permitindo a visualização de elementos da área de trabalho por trás da aplicação. Os métodos descritos incluem a remoção da borda da janela, a configuração de transparência, a manutenção da janela sempre no topo (always-on-top) e a permissão para que eventos de mouse passem através da janela, características úteis para criar widgets de desktop, overlays ou aplicações de HUD.

Método 1: Utilizando um Shader de Chroma Key

Esta abordagem envolve o uso de um shader customizado para tornar pixels de uma cor específica transparentes. Apesar de funcional, ela pode gerar bordas serrilhadas (aliasing) na interface.

Pré-requisitos: Um material usando o shader customizado deve ser atribuído a uma câmera. A cor sólida de fundo da câmera deve corrseponder à chave de transparência definida no shader.

Shader Customizado para Transparência

O shader abaixo implementa a lógica de transparência baseada em uma cor-chave. Pixels com cores muito próximas à cor-chave se tornam transparentes.

Shader "Custom/TransparentKeyShader" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _KeyColor ("Color Key", Color) = (0,0,0,1)
        _Threshold ("Threshold", Float) = 0.01
    }
    SubShader {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _KeyColor;
            float _Threshold;

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                fixed4 col = tex2D(_MainTex, i.uv);
                float diff = distance(col.rgb, _KeyColor.rgb);
                if (diff < _Threshold) {
                    col.a = 0.0;
                }
                return col;
            }
            ENDCG
        }
    }
}

Script de Configuração da Janela (Versão Básica)

Este script C# modifica as propriedades nativas da janela usando a API do Windows.

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

public class WindowTransparentBase : MonoBehaviour
{
    [SerializeField]
    private Material keyingMaterial;

    [StructLayout(LayoutKind.Sequential)]
    public struct MARGINS {
        public int leftWidth;
        public int rightWidth;
        public int topHeight;
        public int bottomHeight;
    }

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

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

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

    private const int GWL_STYLE = -16;
    private const uint WS_POPUP = 0x80000000;
    private const uint WS_VISIBLE = 0x10000000;

    void Start()
    {
        #if !UNITY_EDITOR
        IntPtr hwnd = GetActiveWindow();
        SetWindowLong(hwnd, GWL_STYLE, WS_POPUP | WS_VISIBLE);

        MARGINS margins = new MARGINS { leftWidth = -1 };
        DwmExtendFrameIntoClientArea(hwnd, ref margins);
        #endif
    }

    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        Graphics.Blit(src, dest, keyingMaterial);
    }
}

Método 2: Solução Completa com DwmExtendFrameIntoClientArea

Esta é a abordagem recomendada, pois fornece bordas suaves e um resultado visual superior. Requer que a câmera principal seja configurada com um fundo de cor sólida preta.

Configuração da Câmera: No Inspetor da Câmera, defina Clear Flags para Solid Color e selecione a cor preta (#000000).

Script Modular de Controle de Janela

O código abaixo foi refatorado para ser mais modular e legível, separando as responsabilidades de configurar a transparência, a posição no topo e a interação do mouse.

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

public class DesktopWidgetController : MonoBehaviour
{
    [Header("Window Settings")]
    public Vector2 windowPosition = new Vector2(10, 10);
    public Vector2 windowSize = new Vector2(400, 300);

    private IntPtr windowHandle;

    #region Win32 API Constants
    private const int GWL_STYLE = -16;
    private const int GWL_EXSTYLE = -20;
    private const int WS_CAPTION = 0x00C00000;
    private const int WS_BORDER = 0x00800000;
    private const uint WS_POPUP = 0x80000000;
    private const uint WS_EX_LAYERED = 0x00080000;
    private const uint WS_EX_TRANSPARENT = 0x00000020;
    private const uint SWP_SHOWWINDOW = 0x0040;
    #endregion

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

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

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

    [DllImport("user32.dll")]
    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);

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

    [StructLayout(LayoutKind.Sequential)]
    public struct MARGINS {
        public int cxLeftWidth;
        public int cxRightWidth;
        public int cyTopHeight;
        public int cyBottomHeight;
    }
    #endregion

    void Start()
    {
        Screen.fullScreen = false;
        windowHandle = GetActiveWindow();

        ConfigureTransparentWindow();
        SetAlwaysOnTop();
        EnableClickThrough();
    }

    private void ConfigureTransparentWindow()
    {
        // Remove bordas
        uint currentStyle = GetWindowLong(windowHandle, GWL_STYLE);
        SetWindowLong(windowHandle, GWL_STYLE, currentStyle & ~WS_BORDER & ~WS_CAPTION);

        // Extende a área do cliente para a borda da janela, tornando-a transparente
        MARGINS margins = new MARGINS { cxLeftWidth = -1 };
        DwmExtendFrameIntoClientArea(windowHandle, ref margins);
    }

    private void SetAlwaysOnTop()
    {
        // HWND_TOPMOST = -1
        SetWindowPos(windowHandle, (IntPtr)(-1),
            (int)windowPosition.x, (int)windowPosition.y,
            (int)windowSize.x, (int)windowSize.y,
            SWP_SHOWWINDOW);
    }

    private void EnableClickThrough()
    {
        uint extendedStyle = GetWindowLong(windowHandle, GWL_EXSTYLE);
        SetWindowLong(windowHandle, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT | WS_EX_LAYERED);
    }

    // Função para desativar a passagem de eventos do mouse, se necessário
    public void DisableClickThrough()
    {
        uint extendedStyle = GetWindowLong(windowHandle, GWL_EXSTYLE);
        SetWindowLong(windowHandle, GWL_EXSTYLE, extendedStyle & ~WS_EX_TRANSPARENT);
    }
}

Eliminando o Piscar da Janela na Inicialização

Para evitar que a janela padrão com bordas apareça por um instante antes de se tornar transparente, é possível redimensioná-la para 0 pixels ao fechar a aplicação. Na próxima execução, a janela iniciará invisível até que o script a configure.

    private void OnApplicationQuit()
    {
        // Redimensiona a janela para zero pixels ao sair
        SetWindowPos(windowHandle, IntPtr.Zero, 0, 0, 0, 0, SWP_SHOWWINDOW);
    }

Método 3: Controlando a Opacidade da Janela

Este método utiliza a função SetLayeredWindowAttributes para controlar a opacidade geral da janela ou para definir uma cor-chave que se tornará completamente transparente.

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

public class WindowOpacityController : MonoBehaviour
{
    [Range(0, 255)]
    public byte windowAlpha = 100; // 0=Invisível, 255=Opaco

    private IntPtr windowHandle;

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

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

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

    private const int GWL_EXSTYLE = -20;
    private const uint WS_EX_LAYERED = 0x80000;
    private const uint LWA_ALPHA = 0x2;
    private const uint LWA_COLORKEY = 0x1;

    void Start()
    {
        windowHandle = GetForegroundWindow();
        SetWindowLong(windowHandle, GWL_EXSTYLE, (int)WS_EX_LAYERED);
        // Aplica opacidade a toda a janela
        SetLayeredWindowAttributes(windowHandle, 0, windowAlpha, LWA_ALPHA);
    }
}

Dica Extra: Removendo a Borda sem Código

Para qualquer aplicação Unity compilada como .exe, é possível remover a borda da janela através de um argumento de linha de comando, sem necessidade de scripts.

  1. Abra o Prompt de Comando (cmd).
  2. Navegue até o diretório que contém o executável do jogo.
  3. Execute o comando: SeuJogo.exe -popupwindow

Este comando fará com que a aplicação inicie no modo janela sem borda. Outros argumentos úteis incluem -nolog para suprimir a geração do arquivo de log. Mais informações podem ser encontradas na documentação oficial do Unity sobre argumentos de linha de comando.

Tags: Unity Windows API DwmExtendFrameIntoClientArea Transparência de Janela Desktop Widget

Publicado em 6-20 19:28