#if UNITY_STANDALONE_WIN using System; using System.Runtime.InteropServices; using UnityEngine; using System.Diagnostics; using AOT; using UnityEngine.Gonbest.MagicCube; using System.Collections; /// ///强制设置Unity游戏窗口的长宽比。你可以调整窗口的大小,他会强制保持一定比例 ///通过拦截窗口大小调整事件(WindowProc回调)并相应地修改它们来实现的 ///也可以用像素为窗口设置最小/最大宽度和高度 ///长宽比和最小/最大分辨率都与窗口区域有关,标题栏和边框不包括在内 ///该脚本还将在应用程序处于全屏状态时强制设置长宽比。当你切换到全屏, ///应用程序将自动设置为当前显示器上可能的最大分辨率,而仍然保持固定比。如果显示器没有相同的宽高比,则会在左/右或上/下添加黑条 ///确保你在PlayerSetting中设置了“Resizable Window”,否则无法调整大小 ///如果取消不支持的长宽比在PlayerSetting中设置“Supported Aspect Rations” ///注意:因为使用了WinAPI,所以只能在Windows上工作。在Windows 10上测试过 /// public class AspectRatioController : MonoBehaviour { public static void RunScript() { var go = GameObject.Find("[AspectRatioController]"); if (go == null) { go = new GameObject("[AspectRatioController]"); GameObject.DontDestroyOnLoad(go); } var script = go.GetComponent(); if (script == null) { script = go.AddComponent(); } go.SetActive(true); script.enabled = true; } // 最小值和最大值的窗口宽度/高度像素 private static int minWidthPixel = 256; private static int minHeightPixel = 256; // WinAPI相关定义 #region WINAPI // 当窗口调整时,WM_SIZING消息通过WindowProc回调发送到窗口 private const int WM_SIZING = 0x214; // WM大小调整消息的参数 private const int WMSZ_LEFT = 1; private const int WMSZ_RIGHT = 2; private const int WMSZ_TOP = 3; private const int WMSZ_BOTTOM = 6; // 获取指向WindowProc函数的指针 private const int GWLP_WNDPROC = -4; // 委托设置为新的WindowProc回调函数 private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); private WndProcDelegate wndProcDelegate; // 将消息信息传递给指定的窗口过程 [DllImport("user32.dll")] private static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); // 检索指定窗口的边框的尺寸 // 尺寸是在屏幕坐标中给出的,它是相对于屏幕左上角的 [DllImport("user32.dll", SetLastError = true)] private static extern bool GetWindowRect(IntPtr hwnd, ref RECT lpRect); //检索窗口客户区域的坐标。客户端坐标指定左上角 //以及客户区的右下角。因为客户机坐标是相对于左上角的 //在窗口的客户区域的角落,左上角的坐标是(0,0) [DllImport("user32.dll")] private static extern bool GetClientRect(IntPtr hWnd, ref RECT lpRect); // 更改指定窗口的属性。该函数还将指定偏移量的32位(长)值设置到额外的窗口内存中 [DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Auto)] private static extern IntPtr SetWindowLong32(IntPtr hWnd, int nIndex, IntPtr dwNewLong); //更改指定窗口的属性。该函数还在额外的窗口内存中指定的偏移量处设置一个值 [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Auto)] private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong); public delegate bool WNDENUMPROC(IntPtr hwnd, uint lParam); [DllImport("user32.dll", SetLastError = true)] public static extern bool EnumWindows(WNDENUMPROC lpEnumFunc, uint lParam); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr GetParent(IntPtr hWnd); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, ref uint lpdwProcessId); [DllImport("kernel32.dll")] public static extern void SetLastError(uint dwErrCode); [DllImport("user32.dll")] static extern bool CloseWindow(IntPtr hwnd); // Unity窗口的窗口句柄 private static IntPtr unityHWnd; // 指向旧WindowProc回调函数的指针 private static IntPtr oldWndProcPtr; // 指向我们自己的窗口回调函数的指针 private static IntPtr newWndProcPtr; /// /// WinAPI矩形定义。 /// [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; } #endregion void Start() { // 不要在Unity编辑器中注册WindowProc回调函数,它会指向Unity编辑器窗口,而不是Game视图 // 找到主Unity窗口的窗口句柄 unityHWnd = GetProcessWnd(); if (unityHWnd == IntPtr.Zero) { return; } // Register (replace) WindowProc callback。每当一个窗口事件被触发时,这个函数都会被调用 //例如调整大小或移动窗口 //保存旧的WindowProc回调函数,因为必须从新回调函数中调用它 wndProcDelegate = wndProc; newWndProcPtr = Marshal.GetFunctionPointerForDelegate(wndProcDelegate); oldWndProcPtr = SetWindowLong(unityHWnd, GWLP_WNDPROC, newWndProcPtr); Application.wantsToQuit += () => { if (AppPersistData.WantExitResult) { if (Screen.width != 1600 || Screen.height != 800) { CloseWindow(unityHWnd); //退出时设置分辨率,用以下次启动 Screen.SetResolution(1600, 800, false); StartCoroutine(StartQuitCoroutine()); return false; } } return true; }; Application.quitting += () => { SetWindowLong(unityHWnd, GWLP_WNDPROC, oldWndProcPtr); }; } private IEnumerator StartQuitCoroutine() { yield return new WaitForEndOfFrame(); Application.Quit(); } static IntPtr findeHwnd = IntPtr.Zero; [MonoPInvokeCallback(typeof(WNDENUMPROC))] static bool OnWindowEnum(IntPtr hwnd, uint lParam) { uint id = 0; if (GetParent(hwnd) == IntPtr.Zero) { GetWindowThreadProcessId(hwnd, ref id); if (id == lParam) // 找到进程对应的主窗口句柄 { findeHwnd = hwnd; // 把句柄缓存起来 SetLastError(0); // 设置无错误 return false; // 返回 false 以终止枚举窗口 } } return true; } public static IntPtr GetProcessWnd() { IntPtr ptrWnd = IntPtr.Zero; uint pid = (uint)Process.GetCurrentProcess().Id; // 当前进程 ID bool bResult = EnumWindows(OnWindowEnum, pid); ptrWnd = findeHwnd; return (!bResult && Marshal.GetLastWin32Error() == 0) ? ptrWnd : IntPtr.Zero; } /// /// WindowProc回调。应用程序定义的函数,用来处理发送到窗口的消息 /// /// 用于标识事件的消息 /// 额外的信息信息。该参数的内容取决于uMsg参数的值 /// 其他消息的信息。该参数的内容取决于uMsg参数的值 /// [MonoPInvokeCallback(typeof(WndProcDelegate))] static IntPtr wndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { // 检查消息类型 // resize事件 if (msg == WM_SIZING) { // 获取窗口大小结构体 RECT rc = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT)); // 计算窗口边框的宽度和高度 RECT windowRect = new RECT(); GetWindowRect(unityHWnd, ref windowRect); RECT clientRect = new RECT(); GetClientRect(unityHWnd, ref clientRect); int borderWidth = windowRect.Right - windowRect.Left - (clientRect.Right - clientRect.Left); int borderHeight = windowRect.Bottom - windowRect.Top - (clientRect.Bottom - clientRect.Top); // 在应用宽高比之前删除边框(包括窗口标题栏) rc.Right -= borderWidth; rc.Bottom -= borderHeight; // 限制窗口大小 int newWidth = rc.Right - rc.Left; if (newWidth < minWidthPixel) { newWidth = minWidthPixel; } int newHeight = rc.Bottom - rc.Top; if (newHeight < minHeightPixel) { newHeight = minHeightPixel; } // 根据纵横比和方向调整大小 switch (wParam.ToInt32()) { case WMSZ_LEFT: if (newWidth < newHeight) { //高度配合缩小 newHeight = newWidth; } else if (newWidth > newHeight * 2) { //高度配合放大 newHeight = newWidth / 2; } rc.Left = rc.Right - newWidth; rc.Bottom = rc.Top + newHeight; break; case WMSZ_RIGHT: if (newWidth < newHeight) { //高度配合缩小 newHeight = newWidth; } else if (newWidth > newHeight * 2) { //高度配合放大 newHeight = newWidth / 2; } rc.Right = rc.Left + newWidth; rc.Bottom = rc.Top + newHeight; break; case WMSZ_TOP: if (newWidth < newHeight) { //宽度配合放大 newWidth = newHeight; } else if (newWidth > newHeight * 2) { //宽度配合缩小 newWidth = newHeight * 2; } rc.Top = rc.Bottom - newHeight; rc.Right = rc.Left + newWidth; break; case WMSZ_BOTTOM: if (newWidth < newHeight) { //宽度配合放大 newWidth = newHeight; } else if (newWidth > newHeight * 2) { //宽度配合缩小 newWidth = newHeight * 2; } rc.Bottom = rc.Top + newHeight; rc.Right = rc.Left + newWidth; break; case WMSZ_RIGHT + WMSZ_BOTTOM: if (newWidth < newHeight) { //宽度配合放大 newHeight = newWidth; } else if (newWidth > newHeight * 2) { //宽度配合缩小 newHeight = newWidth / 2; } rc.Right = rc.Left + newWidth; rc.Bottom = rc.Top + newHeight; break; case WMSZ_RIGHT + WMSZ_TOP: if (newWidth < newHeight) { //宽度配合放大 newHeight = newWidth; } else if (newWidth > newHeight * 2) { //宽度配合缩小 newHeight = newWidth / 2; } rc.Right = rc.Left + newWidth; rc.Top = rc.Bottom - newHeight; break; case WMSZ_LEFT + WMSZ_BOTTOM: if (newWidth < newHeight) { //宽度配合放大 newHeight = newWidth; } else if (newWidth > newHeight * 2) { //宽度配合缩小 newHeight = newWidth / 2; } rc.Left = rc.Right - newWidth; rc.Bottom = rc.Top + newHeight; break; case WMSZ_LEFT + WMSZ_TOP: if (newWidth < newHeight) { //宽度配合放大 newHeight = newWidth; } else if (newWidth > newHeight * 2) { //宽度配合缩小 newHeight = newWidth / 2; } rc.Left = rc.Right - newWidth; rc.Top = rc.Bottom - newHeight; break; } // 添加边界 rc.Right += borderWidth; rc.Bottom += borderHeight; // 回写更改的窗口参数 Marshal.StructureToPtr(rc, lParam, true); } // 调用原始的WindowProc函数 return CallWindowProc(oldWndProcPtr, hWnd, msg, wParam, lParam); } /// /// 调用SetWindowLong32或SetWindowLongPtr64,取决于可执行文件是32位还是64位。 /// 这样,我们就可以同时构建32位和64位的可执行文件而不会遇到问题。 /// /// The window handle. /// 要设置的值的从零开始的偏移量 /// The replacement value. /// 返回值是指定偏移量的前一个值。否则零. private static IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong) { if (IntPtr.Size == 4) { //32位系统 return SetWindowLong32(hWnd, nIndex, dwNewLong); } return SetWindowLongPtr64(hWnd, nIndex, dwNewLong); } } #endif