ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 유니티 화면 비율 고정 처리...13
    일지 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);
    	}
    }

     

    생각보다 깔끔하게 구현이 된 것 같다.

     

    WinAPI를 이용한 크기 조절 처리

    댓글

Designed by Tistory.