Files
2026-08-26 11:01:56 +09:00

91 lines
3.2 KiB
C#

using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// OS 커서 위치를 Unity 화면 좌표로 변환한다.
///
/// 우리 창은 WS_EX_NOACTIVATE 라 포커스를 받지 않으므로 Unity 의 Mouse.position 을
/// 신뢰할 수 없다. 또 창 밖 좌표는 Unity 가 아예 모른다. 그래서 GetCursorPos 로
/// OS 커서를 직접 읽고 창 사각형 기준으로 환산한다.
///
/// 히트테스트와 시선 추적이 같은 변환을 쓰므로 여기 한 곳에만 둔다.
/// </summary>
public static class DesktopCursor
{
/// <summary>
/// 데스크톱 물리 좌표(Y 아래로) <-> Unity 화면 좌표(Y 위로) 변환에 필요한 정보.
/// 창 사각형을 매번 다시 묻지 않도록 한 번 만들어 재사용한다.
/// </summary>
public struct Mapping
{
public int Left, Top, Width, Height;
public float ScaleX, ScaleY;
public float ToScreenX(float desktopX) => (desktopX - Left) * ScaleX;
public float ToScreenY(float desktopY) => (Height - (desktopY - Top)) * ScaleY;
public Vector2 ToScreen(float desktopX, float desktopY)
=> new Vector2(ToScreenX(desktopX), ToScreenY(desktopY));
}
/// <summary>우리 창을 기준으로 한 좌표 변환 정보를 구한다.</summary>
public static bool TryGetMapping(System.IntPtr hwnd, out Mapping mapping)
{
mapping = default;
#if !UNITY_EDITOR && UNITY_STANDALONE_WIN
if (hwnd == System.IntPtr.Zero) return false;
if (!Win32.GetWindowRect(hwnd, out var rc)) return false;
if (rc.Width <= 0 || rc.Height <= 0) return false;
mapping = new Mapping
{
Left = rc.left,
Top = rc.top,
Width = rc.Width,
Height = rc.Height,
// 창 크기와 백버퍼 크기가 어긋날 경우를 대비해 비율로 환산한다.
ScaleX = Screen.width / (float)rc.Width,
ScaleY = Screen.height / (float)rc.Height,
};
return true;
#else
mapping = new Mapping
{
Left = 0, Top = 0, Width = Screen.width, Height = Screen.height,
ScaleX = 1f, ScaleY = 1f,
};
return true;
#endif
}
/// <summary>
/// 커서의 Unity 화면 좌표(좌하단 원점)를 구한다. 커서가 창 밖이면 false.
/// </summary>
public static bool TryGetScreenPosition(System.IntPtr hwnd, out Vector2 screenPos)
{
screenPos = default;
#if !UNITY_EDITOR && UNITY_STANDALONE_WIN
if (!Win32.GetCursorPos(out var pt)) return false;
if (!TryGetMapping(hwnd, out Mapping map)) return false;
// 창 밖이면 무효로 본다.
if (pt.x < map.Left || pt.y < map.Top ||
pt.x >= map.Left + map.Width || pt.y >= map.Top + map.Height) return false;
screenPos = map.ToScreen(pt.x, pt.y);
return true;
#else
// 에디터에서는 일반 마우스 입력으로 대체해 미리보기가 가능하게 한다.
var mouse = Mouse.current;
if (mouse == null) return false;
Vector2 p = mouse.position.ReadValue();
if (p.x < 0f || p.y < 0f || p.x >= Screen.width || p.y >= Screen.height) return false;
screenPos = p;
return true;
#endif
}
}