Files
MyCharacterAgent/Assets/02_Scripts/Desktop/TransparentWindow.cs
2026-08-25 00:55:37 +09:00

149 lines
5.3 KiB
C#

using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Rendering.Universal;
#if !UNITY_EDITOR && UNITY_STANDALONE_WIN
using System;
#endif
/// <summary>
/// 창을 테두리 없는 최상단 투명 창으로 만든다.
/// 작업표시줄과 Alt+Tab 에서도 숨긴다.
/// 에디터에서는 아무것도 하지 않는다 (에디터 창이 사라지는 사고 방지).
/// </summary>
[RequireComponent(typeof(Camera))]
public class TransparentWindow : MonoBehaviour
{
[Header("창 동작")]
[Tooltip("창을 가상 데스크톱 전체로 확장. 클릭 통과가 동작하는 것을 확인한 뒤 켤 것")]
[SerializeField] bool spanVirtualDesktop = false;
[Tooltip("작업표시줄 / Alt+Tab 에서 숨김 (WS_EX_TOOLWINDOW)")]
[SerializeField] bool hideFromTaskbar = true;
[Header("검증용")]
[Tooltip("런타임 큐브 생성. 씬에 AlphaTestCube 가 있으면 그쪽이 우선한다")]
[SerializeField] bool spawnTestCube = false;
[Tooltip("URP 포스트프로세싱 비활성화")]
[SerializeField] bool forceDisablePostProcessing = true;
[Tooltip("포커스가 있을 때 Esc 로 종료. NOACTIVATE 를 켜면 동작하지 않으므로 GlobalExitHotkey 를 함께 쓸 것")]
[SerializeField] bool quitOnEscape = true;
Camera cam;
Transform testCube;
/// <summary>플레이어 창 핸들. 준비되기 전에는 Zero.</summary>
public System.IntPtr Hwnd { get; private set; } = System.IntPtr.Zero;
/// <summary>Win32 스타일 적용이 끝났는지.</summary>
public bool IsReady { get; private set; }
void Awake()
{
cam = GetComponent<Camera>();
// 알파 0 클리어 — 투명의 출발점
cam.clearFlags = CameraClearFlags.SolidColor;
cam.backgroundColor = new Color(0f, 0f, 0f, 0f);
if (forceDisablePostProcessing)
{
var urp = cam.GetUniversalAdditionalCameraData();
if (urp != null) urp.renderPostProcessing = false;
}
Application.runInBackground = true;
// 씬에 큐브가 있으면 그것을 쓰고, 없을 때만 런타임 생성
var inScene = GameObject.Find("AlphaTestCube");
if (inScene != null) testCube = inScene.transform;
else if (spawnTestCube) CreateTestCube();
}
void Start()
{
#if !UNITY_EDITOR && UNITY_STANDALONE_WIN
StartCoroutine(ApplyWindowStyle());
#else
Debug.Log("[TransparentWindow] 에디터에서는 Win32 적용을 건너뜁니다. 빌드해서 테스트하세요.");
#endif
}
void Update()
{
if (testCube != null) testCube.Rotate(new Vector3(30f, 45f, 15f) * Time.deltaTime);
// 포커스가 있을 때만 동작하는 보조 탈출구
if (quitOnEscape)
{
var kb = Keyboard.current;
if (kb != null && kb.escapeKey.wasPressedThisFrame) Application.Quit();
}
}
void CreateTestCube()
{
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.name = "AlphaTestCube";
cube.transform.position = transform.position + transform.forward * 5f;
testCube = cube.transform;
}
#if !UNITY_EDITOR && UNITY_STANDALONE_WIN
IEnumerator ApplyWindowStyle()
{
int x = 0, y = 0, w = Screen.width, h = Screen.height;
if (spanVirtualDesktop)
{
x = Win32.GetSystemMetrics(Win32.SM_XVIRTUALSCREEN);
y = Win32.GetSystemMetrics(Win32.SM_YVIRTUALSCREEN);
w = Win32.GetSystemMetrics(Win32.SM_CXVIRTUALSCREEN);
h = Win32.GetSystemMetrics(Win32.SM_CYVIRTUALSCREEN);
Screen.SetResolution(w, h, FullScreenMode.Windowed);
}
// SetResolution 은 프레임 끝에 반영되므로 스타일 적용 전에 기다린다
yield return null;
yield return new WaitForEndOfFrame();
yield return null;
IntPtr hwnd = Win32.GetActiveWindow();
if (hwnd == IntPtr.Zero) hwnd = Win32.FindWindowW(null, Application.productName);
if (hwnd == IntPtr.Zero)
{
Debug.LogError("[TransparentWindow] HWND 를 찾지 못했습니다.");
yield break;
}
// 타이틀바 / 테두리 제거
Win32.SetWindowLongW(hwnd, Win32.GWL_STYLE, Win32.WS_POPUP | Win32.WS_VISIBLE);
if (hideFromTaskbar)
{
uint ex = Win32.GetWindowLongW(hwnd, Win32.GWL_EXSTYLE);
ex = (ex | Win32.WS_EX_TOOLWINDOW) & ~Win32.WS_EX_APPWINDOW;
Win32.SetWindowLongW(hwnd, Win32.GWL_EXSTYLE, ex);
// TOOLWINDOW 변경은 hide/show 해야 작업표시줄에 반영된다
Win32.ShowWindow(hwnd, Win32.SW_HIDE);
Win32.ShowWindow(hwnd, Win32.SW_SHOW);
}
// 픽셀 단위 알파 투명
var margins = new Win32.MARGINS { cxLeftWidth = -1, cxRightWidth = -1, cyTopHeight = -1, cyBottomHeight = -1 };
int hr = Win32.DwmExtendFrameIntoClientArea(hwnd, ref margins);
Debug.Log($"[TransparentWindow] DwmExtendFrameIntoClientArea hr=0x{hr:X8} (0 이면 성공)");
Win32.SetWindowPos(hwnd, Win32.HWND_TOPMOST, x, y, w, h,
Win32.SWP_FRAMECHANGED | Win32.SWP_SHOWWINDOW);
Hwnd = hwnd;
IsReady = true;
Debug.Log($"[TransparentWindow] 적용 완료: {x},{y} {w}x{h}");
}
#endif
}