일지
유니티 화면 비율 고정 처리...13
niamdank
2021. 10. 12. 18:43
유니티 함수의 문제점 해결
기존 유니티 함수가 가진 가운데로 정렬되는 문제를 해결하기 위해 WinAPI를 이용해 새롭게 구현한다.
ResolutionController.cs
using System;
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;
public class ResolutionController : MonoBehaviour
{
// 가로 고정 비율
public float AspectX { get; set; } = 16;
// 세로 고정 비율
public float AspectY { get; set; } = 9;
// 화면 크기 조정 할 횟수
public int RefreshCount { get; set; } = 3;
// 크기 조정 시 부드럽게 처리할지 여부
public bool SmoothRefresh { get; set; } = true;
// 크기 조절 모드
public ResizeMode Mode { get; set; } = ResizeMode.MoveWindow;
public Text m_debugText;
public Text m_debugWidthText;
public Text m_debugHeightText;
public Text m_debugResolutionText;
#region ENUMERATIONS
private enum Cursors
{
IDC_ARROW = 32512,
IDC_SIZENESW = 32643,
IDC_SIZENS = 32645,
IDC_SIZENWSE = 32642,
IDC_SIZEWE = 32644,
}
private enum UpdateState
{
Waiting,
Changing,
Updating,
}
public enum ResizeMode
{
UnityDefault,
MoveWindow,
}
#endregion
#region WINAPI_DLL
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public Int32 x;
public Int32 y;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
private const int VK_LBUTTON = 0x01;
private const ushort KEY_HOLD = 0x8000;
private const uint WS_OVERLAPPED = 0x00000000;
private const uint WS_CAPTION = 0x00C00000;
private const uint WS_SYSMENU = 0x00080000;
private const uint WS_THICKFRAME = 0x00040000;
private const uint WS_MINIMIZEBOX = 0x00020000;
private const uint WS_MAXIMIZEBOX = 0x00010000;
private const uint WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU
| WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
[DllImport("user32.dll")]
private static extern IntPtr GetCursor();
[DllImport("user32.dll")]
private static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
[DllImport("user32.dll")]
private static extern ushort GetAsyncKeyState(ushort vKey);
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern bool AdjustWindowRect(ref RECT lpRect, uint dwStyle, bool bMenu);
[DllImport("user32.dll")]
private static extern bool MoveWindow(IntPtr hwnd, int x, int y, int nWidth, int nHeight, bool bRepaint);
#endregion
#region WINAPI_VARIABLES
private IntPtr CursorNESW { get; set; }
private IntPtr CursorNS { get; set; }
private IntPtr CursorNWSE { get; set; }
private IntPtr CursorWE { get; set; }
#endregion
private float m_aspectRatio;
private int m_screenSizeX;
private int m_screenSizeY;
private UpdateState m_updateState;
private IntPtr m_hwnd;
private RECT m_wndRect;
private void Start()
{
m_aspectRatio = AspectX / AspectY;
m_screenSizeX = Screen.width;
m_screenSizeY = Screen.height;
Initialize();
}
/// <summary>
/// 초기화 하며 필요한 값을 미리 가져온다.
/// </summary>
private void Initialize()
{
CursorNESW = LoadCursor(IntPtr.Zero, (int)Cursors.IDC_SIZENESW);
CursorNS = LoadCursor(IntPtr.Zero, (int)Cursors.IDC_SIZENS);
CursorNWSE = LoadCursor(IntPtr.Zero, (int)Cursors.IDC_SIZENWSE);
CursorWE = LoadCursor(IntPtr.Zero, (int)Cursors.IDC_SIZEWE);
m_hwnd = FindWindow(null, "FixedResolution_DetectByCursorIcon");
m_updateState = UpdateState.Waiting;
m_debugText.text = $"Update State : {m_updateState}";
m_debugWidthText.text = $"Width : {Screen.width}";
m_debugHeightText.text = $"Height : {Screen.height}";
m_debugResolutionText.text = $"Resolution : {(float)Screen.width / Screen.height}";
}
private void Update()
{
IntPtr hCursor = GetCursor();
if (m_updateState == UpdateState.Waiting && IsChanging(hCursor) && IsMouseButtonClicked())
{
m_updateState = UpdateState.Changing;
m_debugText.text = $"Update State : {m_updateState}";
m_debugWidthText.text = $"Width : {Screen.width}";
m_debugHeightText.text = $"Height : {Screen.height}";
m_debugResolutionText.text = $"Resolution : {(float)Screen.width / Screen.height}";
}
else if (m_updateState == UpdateState.Changing && !IsMouseButtonClicked())
{
GetWindowRect(m_hwnd, out m_wndRect);
StartCoroutine(SetFixedResolution());
}
}
/// <summary>
/// 주어진 비율로 화면 비율을 고정한다.
/// </summary>
private IEnumerator SetFixedResolution()
{
m_updateState = UpdateState.Updating;
m_debugText.text = $"Update State : {m_updateState}";
m_debugWidthText.text = $"Width : {Screen.width}";
m_debugHeightText.text = $"Height : {Screen.height}";
m_debugResolutionText.text = $"Resolution : {(float)Screen.width / Screen.height}";
int newScreenSizeX = Screen.width;
int newScreenSizeY = Screen.height;
// 대각선으로 비율을 바꾼 경우 적절한 방향을 선택해 비율을 맞춘다.
if (newScreenSizeX != m_screenSizeX && newScreenSizeY != m_screenSizeY)
{
if (Mathf.Abs(newScreenSizeX - m_screenSizeX) > Mathf.Abs(newScreenSizeY - m_screenSizeY))
{
newScreenSizeY = Mathf.FloorToInt(newScreenSizeX / m_aspectRatio);
}
else
{
newScreenSizeX = Mathf.FloorToInt(newScreenSizeY * m_aspectRatio);
}
}
else if (newScreenSizeX != m_screenSizeX)
{
newScreenSizeY = Mathf.FloorToInt(newScreenSizeX / m_aspectRatio);
}
else
{
newScreenSizeX = Mathf.FloorToInt(newScreenSizeY * m_aspectRatio);
}
// 주어진 반복 횟수만큼 화면 비율을 조정한다.
for (int i = 1; i <= RefreshCount; i++)
{
int tempScreenSizeX = newScreenSizeX;
int tempScreenSizeY = newScreenSizeY;
if (SmoothRefresh)
{
tempScreenSizeX = Mathf.RoundToInt(Mathf.Lerp(Screen.width, newScreenSizeX, i / (float)RefreshCount));
tempScreenSizeY = Mathf.RoundToInt(Mathf.Lerp(Screen.height, newScreenSizeY, i / (float)RefreshCount));
}
if (Mode == ResizeMode.UnityDefault)
{
Screen.SetResolution(tempScreenSizeX, tempScreenSizeY, false);
}
else if (Mode == ResizeMode.MoveWindow)
{
ChangeWindowSize(tempScreenSizeX, tempScreenSizeY);
}
yield return null;
}
m_screenSizeX = newScreenSizeX;
m_screenSizeY = newScreenSizeY;
m_updateState = UpdateState.Waiting;
m_debugText.text = $"Update State : {m_updateState}";
m_debugWidthText.text = $"Width : {Screen.width}";
m_debugHeightText.text = $"Height : {Screen.height}";
m_debugResolutionText.text = $"Resolution : {(float)Screen.width / Screen.height}";
}
private bool IsChanging(IntPtr hCursor)
{
if (hCursor == CursorNESW) return true;
if (hCursor == CursorNS) return true;
if (hCursor == CursorNWSE) return true;
if (hCursor == CursorWE) return true;
return false;
}
private bool IsMouseButtonClicked()
{
return (GetAsyncKeyState(VK_LBUTTON) & KEY_HOLD) != 0;
}
private void ChangeWindowSize(int width, int height)
{
m_wndRect.Right = m_wndRect.Left + width;
m_wndRect.Bottom = m_wndRect.Top + height;
RECT sizeRect = new RECT();
sizeRect.Left = 0;
sizeRect.Top = 0;
sizeRect.Right = width;
sizeRect.Bottom = height;
AdjustWindowRect(ref sizeRect, WS_OVERLAPPEDWINDOW, false);
MoveWindow(m_hwnd, m_wndRect.Left, m_wndRect.Top, sizeRect.Right - sizeRect.Left, sizeRect.Bottom - sizeRect.Top, true);
}
}
생각보다 깔끔하게 구현이 된 것 같다.
