Files
Main/Assets/Launcher/ExternalLibs/ScreenLimit/AspectRatioController.cs
2025-01-25 04:38:09 +08:00

356 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#if UNITY_STANDALONE_WIN
using System;
using System.Runtime.InteropServices;
using UnityEngine;
using System.Diagnostics;
using AOT;
using UnityEngine.Gonbest.MagicCube;
using System.Collections;
/// <summary>
///强制设置Unity游戏窗口的长宽比。你可以调整窗口的大小他会强制保持一定比例
///通过拦截窗口大小调整事件(WindowProc回调)并相应地修改它们来实现的
///也可以用像素为窗口设置最小/最大宽度和高度
///长宽比和最小/最大分辨率都与窗口区域有关,标题栏和边框不包括在内
///该脚本还将在应用程序处于全屏状态时强制设置长宽比。当你切换到全屏,
///应用程序将自动设置为当前显示器上可能的最大分辨率,而仍然保持固定比。如果显示器没有相同的宽高比,则会在左/右或上/下添加黑条
///确保你在PlayerSetting中设置了“Resizable Window”否则无法调整大小
///如果取消不支持的长宽比在PlayerSetting中设置“Supported Aspect Rations”
///注意:因为使用了WinAPI所以只能在Windows上工作。在Windows 10上测试过
/// </summary>
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<AspectRatioController>();
if (script == null)
{
script = go.AddComponent<AspectRatioController>();
}
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;
/// <summary>
/// WinAPI矩形定义。
/// </summary>
[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;
}
/// <summary>
/// WindowProc回调。应用程序定义的函数用来处理发送到窗口的消息
/// </summary>
/// <param name="msg">用于标识事件的消息</param>
/// <param name="wParam">额外的信息信息。该参数的内容取决于uMsg参数的值 </param>
/// <param name="lParam">其他消息的信息。该参数的内容取决于uMsg参数的值 </param>
/// <returns></returns>
[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);
}
/// <summary>
/// 调用SetWindowLong32或SetWindowLongPtr64取决于可执行文件是32位还是64位。
/// 这样我们就可以同时构建32位和64位的可执行文件而不会遇到问题。
/// </summary>
/// <param name="hWnd">The window handle.</param>
/// <param name="nIndex">要设置的值的从零开始的偏移量</param>
/// <param name="dwNewLong">The replacement value.</param>
/// <returns>返回值是指定偏移量的前一个值。否则零.</returns>
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