평소 즐겨 보는 고차비님이 좋아하는 상어를 컴퓨터에서 항상 보실 수 있는 프로그램을 제작하기로 하였다.
우선, 방송 중이나 일반적으로 컴퓨터를 사용할 때에도 프로그램을 사용할 수 있도록 위의 썸네일과 같은 투명 어플리케이션을 만들기로 한다.
윈도우 어플리케이션을 투명하게 만들려면 윈도우 스타일을 수정해야 한다. 하지만, 유니티에서는 관련 함수를 지원하지 않는다.
"User32.dll"는 윈도우 USER 구성 요소를 구현한다. 윈도우 구성 요소는 창이나 메뉴 같은 윈도우 사용자 인터페이스의 표준 요소들을 생성하고 다룬다. "USer32.dll" 내부의 함수는 창 생성이나 관리, 그리고 창 메세지 받기 등을 수행하는 기능을 제공한다.
사용 방법
1. DllImport Attribute를 사용하기 위해 System.Runtime.InteropServices를 임포트 한다.
2.DllImport Attribute 이하에 필요한 함수는 다음 예와 같이 선언한다.[DllImport("User32.dll")] public static extern IntPtr GetActiveWindow();
코드
[DllImport("User32.dll")] public static extern IntPtr GetActiveWindow(); [DllImport("User32.dll", EntryPoint = "FindWindowA")] public static extern IntPtr FindWindow(string className, string windowName); [DllImport("User32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("User32.dll")] public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex); [DllImport("User32.dll")] public static extern uint GetWindowLong(IntPtr hWnd, int nIndex); [DllImport("User32.dll")] public static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, uint dwNewLong); [DllImport("User32.dll")] public static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong); [DllImport("User32.dll", EntryPoint = "SetWindowPos", SetLastError = true)] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); [DllImport("User32.dll")] public static extern int SetLayeredWindowAttributes(IntPtr hWnd, uint crKey, byte bAlpha, uint dwFlags); [DllImport("Dwmapi.dll")] public static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins);
더 많은 설정이 있지만, 본 프로젝트에서 사용되었던 것만 나열한다.
이름 | 종류 | 값 | 설명 |
---|---|---|---|
STYLE | GWL | -16 | 윈도우 스타일 |
EXSTYLE | GWL | -20 | 확장 스타일 |
POPUP | WS | 0x80000000 | 팝업 윈도우로 생성 |
VISIBLE | WS | 0x10000000 | 윈도우를 만들자 마자 화면에 출력 |
LAYERED | WS_EX | 0x00080000 | 레이어드 윈도우로 생성 |
TRANSPARENT | WS_EX | 0x00000020 | 투명 윈도우로 생성 |
TOPMOST | WS_EX | 0x00000008 | 최상위 윈도우로 생성 |
NOMOVE | SWP | 0x0002 | 윈도우를 이동하지 않음 |
NOSIZE | SWP | 0x0001 | 윈도우의 크기를 변경하지 않음 |
NOOWNERZORDER | SWP | 0x0200 | 소유 윈도우의 Z순서를 변경하지 않음 |
SHOWWINDOW | SWP | 0x0040 | 윈도우를 나타냄 |
NOACTIVATE | SWP | 0x0010 | 윈도우를 활성화하지 않음 |
TOPMOST | SWP | NOMOVE | NOSIZE | NOOWNERZORDER | 이동 불가, 사이즈 변경 불가, Z순서 변경 불가 |
ALPHA | LWA | 0x00000002 | 지정한 Alpha 값으로 윈도우를 투명하게 만듬 |
본 프로젝트에서는 구조체로 구분하여 선언하였다.
코드
public struct GWL { public const int STYLE = -16; public const int EXSTYLE = -20; } public struct WS { public const uint POPUP = 0x80000000; public const uint VISIBLE = 0x10000000; } public struct WS_EX { public const uint LAYERED = 0x00080000; public const uint TRANSPARENT = 0x00000020; public const uint TOPMOST = 0x00000008; } public struct SWP { public const uint NOMOVE = 0x0002; public const uint NOSIZE = 0x0001; public const uint NOOWNERZORDER = 0x0200; public const uint SHOWWINDOW = 0x0040; public const uint NOACTIVATE = 0x0010; public const uint TOPMOST = NOMOVE | NOSIZE | NOOWNERZORDER; } public struct LWA { pulic const uint ALPHA = 0x00000002; }
using UnityEngine;
using System;
using System.Runtime.InteropServices;
public class Window {
public struct MARGINS {
public int cxLeftWidth;
public int cxRightWidth;
public int cyTopHeight;
public int cyBottomHeight;
}
#region Win API
[DllImport("User32.dll")]
public static extern IntPtr GetActiveWindow();
[DllImport("User32.dll", EntryPoint = "FindWindowA")]
public static extern IntPtr FindWindow(string className, string windowName);
[DllImport("User32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("User32.dll")]
public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex);
[DllImport("User32.dll")]
public static extern uint GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("User32.dll")]
public static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, uint dwNewLong);
[DllImport("User32.dll")]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
[DllImport("User32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[DllImport("User32.dll")]
public static extern int SetLayeredWindowAttributes(IntPtr hWnd, uint crKey, byte bAlpha, uint dwFlags);
[DllImport("Dwmapi.dll")]
public static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins);
#endregion
#region Const Value Struct
public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
public static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
public static readonly IntPtr HWND_TOP = new IntPtr(0);
public static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
public struct GWL
{
public const int STYLE = -16;
public const int EXSTYLE = -20;
}
public struct WS
{
public const uint POPUP = 0x80000000;
public const uint VISIBLE = 0x10000000;
}
public struct WS_EX
{
public const uint LAYERED = 0x00080000;
public const uint TRANSPARENT = 0x00000020;
public const uint TOPMOST = 0x00000008;
}
public struct SWP
{
public const uint NOMOVE = 0x0002;
public const uint NOSIZE = 0x0001;
public const uint NOOWNERZORDER = 0x0200;
public const uint SHOWWINDOW = 0x0040;
public const uint NOACTIVATE = 0x0010;
public const uint TOPMOST = NOMOVE | NOSIZE | NOOWNERZORDER;
}
public struct LWA
{
public const uint ALPHA = 0x00000002;
}
#endregion
}
public class TransparentWindow : MonoBehaviour
{
protected IntPtr hWnd;
protected uint oldWindowLong;
protected void Awake()
{
hWnd = Window.GetActiveWindow();
oldWindowLong = Window.GetWindowLong(hWnd, Window.GWL.EXSTYLE);
oldWindowLong = oldWindowLong | Window.WS_EX.LAYERED | Window.WS_EX.TRANSPARENT;
Window.SetWindowLong(hWnd, Window.GWL.EXSTYLE, oldWindowLong);
Window.SetWindowPos(hWnd, Window.HWND_TOPMOST, 0, 0, 0, 0, Window.SWP.TOPMOST);
Window.MARGINS margins = new Window.MARGINS { cxLeftWidth = -1 };
Window.DwmExtendFrameIntoClientArea(hWnd, ref margins);
}
}
실제 어떤 기능을 하는지는 더 공부하여 추가가 필요하지만, 2019이상의 버전에서 "Use DXGI Flip Model Swapchain for D3D11" 옵션을 꺼주어야 투명 윈도우가 적용된다.
다른 윈도우로 포커스가 옮겨갈 때, 어플리케이션이 내려가는 이슈가 있다. 이 때, "Visible In Background" 옵션을 켜주면 된다.
2번과 동일하게 다른 포커스로 옮겨갔을 때도 계쏙해서 동작하도록 하기 위해서 "Run In Background" 옵션을 켜주어야 한다.