356 lines
14 KiB
C#
356 lines
14 KiB
C#
|
#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
|