Changing window alpha in Unity3D

This is a small blog post about how to do semi-transparent windows in Unity.

Idea

To change a window's opacity we need to call SetLayeredWindowAttributes , and to do so we first need to give it a WS_EX_LAYERED extended window style using SetWindowLong.

That's all there is to it, except we'll obviously need some glue since these are WinAPI functions that do not have a .NET wrapper built-in.

Implementation

Honestly it's just a pile of user32.dll externs from PInvoke and then two functions that tie these together into something that is convenient to use from C#:

using System;
using System.Runtime.InteropServices;
 
public class WindowAlpha {
 
	private static IntPtr __defaultWindow = IntPtr.Zero;
	private static IntPtr getDefaultWindow() {
		if (__defaultWindow == IntPtr.Zero) {
			#if (UNITY_EDITOR || UNITY_STANDALONE)
			__defaultWindow = GetActiveWindow();
			#else
			__defaultWindow = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
			#endif
		}
		return __defaultWindow;
	}
 
	/// <summary>Retrieves opacity of a window</summary>
	/// <param name="hwnd">A window to apply to (optional - defaults to main window)</param>
	/// <returns>Window's 0...255 opacity, or -1 in case of error</returns>
	public static int Get(IntPtr hwnd = default) {
		if (hwnd == IntPtr.Zero) {
			hwnd = getDefaultWindow();
			if (hwnd == IntPtr.Zero) return -1;
		}
		var style = GetWindowLongPtr(hwnd, GWL_EXSTYLE).ToInt64();
		if ((style & WS_EX_LAYERED) == 0) return 255; // not semi-transparent
		uint crKey; byte bAlpha; uint dwFlags;
		if (!GetLayeredWindowAttributes(hwnd, out crKey, out bAlpha, out dwFlags)) return -1;
		return dwFlags == LWA_ALPHA ? bAlpha : 255;
	}
 
	/// <summary>Changes opacity of a window</summary>
	/// <param name="alpha">A value between 0 (transparent) to 255 (opaque)</param>
	/// <param name="hwnd">A window to apply to (optional - defaults to main window)</param>
	/// <returns>Whether the operation succeeded.</returns>
    public static bool Set(int alpha, IntPtr hwnd = default) {
		if (hwnd == IntPtr.Zero) {
			hwnd = getDefaultWindow();
			if (hwnd == IntPtr.Zero) return false;
		}
		if (hwnd == IntPtr.Zero) return false;
		var style = GetWindowLongPtr(hwnd, GWL_EXSTYLE).ToInt64();
		style |= WS_EX_LAYERED;
		SetWindowLongPtr(hwnd, GWL_EXSTYLE, (IntPtr)style);
		var alpha_b = (byte)(alpha < 0 ? 0 : (alpha > 255 ? 255 : alpha));
		return SetLayeredWindowAttributes(hwnd, 0, alpha_b, LWA_ALPHA);
	}
 
	[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
	private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
	private const int GWL_EXSTYLE = -20;
 
	private static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong) {
		if (IntPtr.Size == 8)
			return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
		else
			return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
	}
	private const int WS_EX_LAYERED = 0x00080000;
 
	[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
	private static extern int SetWindowLong32(IntPtr hWnd, int nIndex, int dwNewLong);
	[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
	private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
 
	[DllImport("user32.dll", SetLastError = true)] private static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
	[DllImport("user32.dll", SetLastError = true)] static extern bool GetLayeredWindowAttributes(IntPtr hwnd, out uint crKey, out byte bAlpha, out uint dwFlags);
	private const int LWA_ALPHA = 0x00000002;
 
	[DllImport("user32.dll")]
	private static extern IntPtr GetActiveWindow();
}

How to use this

After you have created a C# class in your project and pasted the above in it, you can call WindowAlpha.Set and WindowAlpha.Get:

using UnityEngine;
 
public class TestTransparency : MonoBehaviour {
	private void Update() {
        if (!Application.isEditor) {
            WindowAlpha.Set(Mathf.RoundToInt(255 * (0.6f + 0.4f * Mathf.Sin(Time.time))));
        }
    }
}

(which would give you slowly pulsating opacity seen in first video)

Use in "plain" C#

You can use this in non-Unity C# as well, though why would you? Form component has an Opacity field, and it's relatively uncommon to do C# applications with WinAPI rather than WinForms.

Use with transparency masks

In plain WinAPI, you could be using LWA_COLORKEY to define a transparent color for your window.

Unfortunately, this does not seem to work with Unity, causing the window to turn completely transparent, likely due to use of DirectX for rendering.

An extension I made can be used for a workaround - it can grab pixels from desktop into a Texture2D, which you can then draw as you please to a visually identical effect.

On use with Unity Editor

For some arcane reason Unity editor permanently ceases to render if you try to make its window semi-transparent (be it this code or other tools), so... don't.

On Mac & Linux

The code here is obviously for Windows.

For Mac/Linux you would have to implement native extensions in C++.

But that would be a thing for another post (or two).


Have fun!

Related posts:

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.