using UnityEngine;
using System;
using System.Collections;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine.Events;
/// <summary>
///強(qiáng)制設(shè)置Unity游戲窗口的長(zhǎng)寬比。你可以調(diào)整窗口的大小,他會(huì)強(qiáng)制保持一定比例
///通過(guò)攔截窗口大小調(diào)整事件(WindowProc回調(diào))并相應(yīng)地修改它們來(lái)實(shí)現(xiàn)的
///也可以用像素為窗口設(shè)置最小/最大寬度和高度
///長(zhǎng)寬比和最小/最大分辨率都與窗口區(qū)域有關(guān),標(biāo)題欄和邊框不包括在內(nèi)
///該腳本還將在應(yīng)用程序處于全屏狀態(tài)時(shí)強(qiáng)制設(shè)置長(zhǎng)寬比。當(dāng)你切換到全屏,
///應(yīng)用程序?qū)⒆詣?dòng)設(shè)置為當(dāng)前顯示器上可能的最大分辨率,而仍然保持固定比。如果顯示器沒(méi)有相同的寬高比,則會(huì)在左/右或上/下添加黑條
///確保你在PlayerSetting中設(shè)置了“Resizable Window”,否則無(wú)法調(diào)整大小
///如果取消不支持的長(zhǎng)寬比在PlayerSetting中設(shè)置“Supported Aspect Rations”
///注意:因?yàn)槭褂昧薟inAPI,所以只能在Windows上工作。在Windows 10上測(cè)試過(guò)
/// </summary>
public class AspectRatioController : MonoBehaviour
{
/// <summary>
/// 每當(dāng)窗口分辨率改變或用戶切換全屏?xí)r,都會(huì)觸發(fā)此事件
/// 參數(shù)是新的寬度、高度和全屏狀態(tài)(true表示全屏)
/// </summary>
public ResolutionChangedEvent resolutionChangedEvent;
[Serializable]
public class ResolutionChangedEvent : UnityEvent<int, int, bool> { }
// 如果為false,則阻止切換到全屏
[SerializeField]
private bool allowFullscreen = true;
// 長(zhǎng)寬比的寬度和高度
[SerializeField]
private float aspectRatioWidth = 16;
[SerializeField]
private float aspectRatioHeight = 9;
// 最小值和最大值的窗口寬度/高度像素
[SerializeField]
private int minWidthPixel = 512;
[SerializeField]
private int minHeightPixel = 512;
[SerializeField]
private int maxWidthPixel = 2048;
[SerializeField]
private int maxHeightPixel = 2048;
// 當(dāng)前鎖定長(zhǎng)寬比。
private float aspect;
// 窗口的寬度和高度。不包括邊框和窗口標(biāo)題欄
// 當(dāng)調(diào)整窗口大小時(shí),就會(huì)設(shè)置這些值
private int setWidth = -1;
private int setHeight = -1;
// 最后一幀全屏狀態(tài)。
private bool wasFullscreenLastFrame;
// 是否初始化了AspectRatioController
// 一旦注冊(cè)了WindowProc回調(diào)函數(shù),就將其設(shè)置為true
private bool started;
// 顯示器的寬度和高度。這是窗口當(dāng)前打開(kāi)的監(jiān)視器
private int pixelHeightOfCurrentScreen;
private int pixelWidthOfCurrentScreen;
//一旦用戶請(qǐng)求終止applaction,則將其設(shè)置為true
private bool quitStarted;
// WinAPI相關(guān)定義
#region WINAPI
// 當(dāng)窗口調(diào)整時(shí),WM_SIZING消息通過(guò)WindowProc回調(diào)發(fā)送到窗口
private const int WM_SIZING = 0x214;
// WM大小調(diào)整消息的參數(shù)
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函數(shù)的指針
private const int GWLP_WNDPROC = -4;
// 委托設(shè)置為新的WindowProc回調(diào)函數(shù)
private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
private WndProcDelegate wndProcDelegate;
// 檢索調(diào)用線程的線程標(biāo)識(shí)符
[DllImport("kernel32.dll")]
private static extern uint GetCurrentThreadId();
// 檢索指定窗口所屬類的名稱
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
// 通過(guò)將句柄傳遞給每個(gè)窗口,依次傳遞給應(yīng)用程序定義的回調(diào)函數(shù),枚舉與線程關(guān)聯(lián)的所有非子窗口
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(uint dwThreadId, EnumWindowsProc lpEnumFunc, IntPtr lParam);
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
// 將消息信息傳遞給指定的窗口過(guò)程
[DllImport("user32.dll")]
private static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
// 檢索指定窗口的邊框的尺寸
// 尺寸是在屏幕坐標(biāo)中給出的,它是相對(duì)于屏幕左上角的
[DllImport("user32.dll", SetLastError = true)]
private static extern bool GetWindowRect(IntPtr hwnd, ref RECT lpRect);
//檢索窗口客戶區(qū)域的坐標(biāo)??蛻舳俗鴺?biāo)指定左上角
//以及客戶區(qū)的右下角。因?yàn)榭蛻魴C(jī)坐標(biāo)是相對(duì)于左上角的
//在窗口的客戶區(qū)域的角落,左上角的坐標(biāo)是(0,0)
[DllImport("user32.dll")]
private static extern bool GetClientRect(IntPtr hWnd, ref RECT lpRect);
// 更改指定窗口的屬性。該函數(shù)還將指定偏移量的32位(長(zhǎng))值設(shè)置到額外的窗口內(nèi)存中
[DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Auto)]
private static extern IntPtr SetWindowLong32(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
//更改指定窗口的屬性。該函數(shù)還在額外的窗口內(nèi)存中指定的偏移量處設(shè)置一個(gè)值
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Auto)]
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
//用于查找窗口句柄的Unity窗口類的名稱
private const string UNITY_WND_CLASSNAME = "UnityWndClass";
// Unity窗口的窗口句柄
private IntPtr unityHWnd;
// 指向舊WindowProc回調(diào)函數(shù)的指針
private IntPtr oldWndProcPtr;
// 指向我們自己的窗口回調(diào)函數(shù)的指針
private 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編輯器中注冊(cè)WindowProc回調(diào)函數(shù),它會(huì)指向Unity編輯器窗口,而不是Game視圖
#if !UNITY_EDITOR
//注冊(cè)回調(diào),然后應(yīng)用程序想要退出
Application.wantsToQuit += ApplicationWantsToQuit;
// 找到主Unity窗口的窗口句柄
EnumThreadWindows(GetCurrentThreadId(), (hWnd, lParam) =>
{
var classText = new StringBuilder(UNITY_WND_CLASSNAME.Length + 1);
GetClassName(hWnd, classText, classText.Capacity);
if (classText.ToString() == UNITY_WND_CLASSNAME)
{
unityHWnd = hWnd;
return false;
}
return true;
}, IntPtr.Zero);
// 將長(zhǎng)寬比應(yīng)用于當(dāng)前分辨率
SetAspectRatio(aspectRatioWidth, aspectRatioHeight, true);
// 保存當(dāng)前的全屏狀態(tài)
wasFullscreenLastFrame = Screen.fullScreen;
// Register (replace) WindowProc callback。每當(dāng)一個(gè)窗口事件被觸發(fā)時(shí),這個(gè)函數(shù)都會(huì)被調(diào)用
//例如調(diào)整大小或移動(dòng)窗口
//保存舊的WindowProc回調(diào)函數(shù),因?yàn)楸仨殢男禄卣{(diào)函數(shù)中調(diào)用它
wndProcDelegate = wndProc;
newWndProcPtr = Marshal.GetFunctionPointerForDelegate(wndProcDelegate);
oldWndProcPtr = SetWindowLong(unityHWnd, GWLP_WNDPROC, newWndProcPtr);
// 初始化完成
started = true;
#endif
}
/// <summary>
///將目標(biāo)長(zhǎng)寬比設(shè)置為給定的長(zhǎng)寬比。
/// </summary>
/// <param name="newAspectWidth">寬高比的新寬度</param>
/// <param name="newAspectHeight">縱橫比的新高度</param>
/// <param name="apply">true,當(dāng)前窗口分辨率將立即調(diào)整以匹配新的縱橫比 false,則只在下次手動(dòng)調(diào)整窗口大小時(shí)執(zhí)行此操作</param>
public void SetAspectRatio(float newAspectWidth, float newAspectHeight, bool apply)
{
//計(jì)算新的縱橫比
aspectRatioWidth = newAspectWidth;
aspectRatioHeight = newAspectHeight;
aspect = aspectRatioWidth / aspectRatioHeight;
// 調(diào)整分辨率以匹配長(zhǎng)寬比(觸發(fā)WindowProc回調(diào))
if (apply)
{
Screen.SetResolution(Screen.width, Mathf.RoundToInt(Screen.width / aspect), Screen.fullScreen);
}
}
/// <summary>
/// WindowProc回調(diào)。應(yīng)用程序定義的函數(shù),用來(lái)處理發(fā)送到窗口的消息
/// </summary>
/// <param name="msg">用于標(biāo)識(shí)事件的消息</param>
/// <param name="wParam">額外的信息信息。該參數(shù)的內(nèi)容取決于uMsg參數(shù)的值 </param>
/// <param name="lParam">其他消息的信息。該參數(shù)的內(nèi)容取決于uMsg參數(shù)的值 </param>
/// <returns></returns>
IntPtr wndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
// 檢查消息類型
// resize事件
if (msg == WM_SIZING)
{
// 獲取窗口大小結(jié)構(gòu)體
RECT rc = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT));
// 計(jì)算窗口邊框的寬度和高度
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);
// 在應(yīng)用寬高比之前刪除邊框(包括窗口標(biāo)題欄)
rc.Right -= borderWidth;
rc.Bottom -= borderHeight;
// 限制窗口大小
int newWidth = Mathf.Clamp(rc.Right - rc.Left, minWidthPixel, maxWidthPixel);
int newHeight = Mathf.Clamp(rc.Bottom - rc.Top, minHeightPixel, maxHeightPixel);
// 根據(jù)縱橫比和方向調(diào)整大小
switch (wParam.ToInt32())
{
case WMSZ_LEFT:
rc.Left = rc.Right - newWidth;
rc.Bottom = rc.Top + Mathf.RoundToInt(newWidth / aspect);
break;
case WMSZ_RIGHT:
rc.Right = rc.Left + newWidth;
rc.Bottom = rc.Top + Mathf.RoundToInt(newWidth / aspect);
break;
case WMSZ_TOP:
rc.Top = rc.Bottom - newHeight;
rc.Right = rc.Left + Mathf.RoundToInt(newHeight * aspect);
break;
case WMSZ_BOTTOM:
rc.Bottom = rc.Top + newHeight;
rc.Right = rc.Left + Mathf.RoundToInt(newHeight * aspect);
break;
case WMSZ_RIGHT + WMSZ_BOTTOM:
rc.Right = rc.Left + newWidth;
rc.Bottom = rc.Top + Mathf.RoundToInt(newWidth / aspect);
break;
case WMSZ_RIGHT + WMSZ_TOP:
rc.Right = rc.Left + newWidth;
rc.Top = rc.Bottom - Mathf.RoundToInt(newWidth / aspect);
break;
case WMSZ_LEFT + WMSZ_BOTTOM:
rc.Left = rc.Right - newWidth;
rc.Bottom = rc.Top + Mathf.RoundToInt(newWidth / aspect);
break;
case WMSZ_LEFT + WMSZ_TOP:
rc.Left = rc.Right - newWidth;
rc.Top = rc.Bottom - Mathf.RoundToInt(newWidth / aspect);
break;
}
// 保存實(shí)際分辨率,不包括邊界
setWidth = rc.Right - rc.Left;
setHeight = rc.Bottom - rc.Top;
// 添加邊界
rc.Right += borderWidth;
rc.Bottom += borderHeight;
// 觸發(fā)分辨率更改事件
resolutionChangedEvent.Invoke(setWidth, setHeight, Screen.fullScreen);
// 回寫更改的窗口參數(shù)
Marshal.StructureToPtr(rc, lParam, true);
}
// 調(diào)用原始的WindowProc函數(shù)
return CallWindowProc(oldWndProcPtr, hWnd, msg, wParam, lParam);
}
void Update()
{
// 如果不允許全屏,則阻止切換到全屏
if (!allowFullscreen && Screen.fullScreen)
{
Screen.fullScreen = false;
}
if (Screen.fullScreen && !wasFullscreenLastFrame)
{
//切換到全屏檢測(cè),設(shè)置為最大屏幕分辨率,同時(shí)保持長(zhǎng)寬比
int height;
int width;
//根據(jù)當(dāng)前長(zhǎng)寬比和顯示器的比例進(jìn)行比較,上下或左右添加黑邊
bool blackBarsLeftRight = aspect < (float)pixelWidthOfCurrentScreen / pixelHeightOfCurrentScreen;
if (blackBarsLeftRight)
{
height = pixelHeightOfCurrentScreen;
width = Mathf.RoundToInt(pixelHeightOfCurrentScreen * aspect);
}
else
{
width = pixelWidthOfCurrentScreen;
height = Mathf.RoundToInt(pixelWidthOfCurrentScreen / aspect);
}
Screen.SetResolution(width, height, true);
resolutionChangedEvent.Invoke(width, height, true);
}
else if (!Screen.fullScreen && wasFullscreenLastFrame)
{
// 從全屏切換到檢測(cè)到的窗口。設(shè)置上一個(gè)窗口的分辨率。
Screen.SetResolution(setWidth, setHeight, false);
resolutionChangedEvent.Invoke(setWidth, setHeight, false);
}
else if (!Screen.fullScreen && setWidth != -1 && setHeight != -1 && (Screen.width != setWidth || Screen.height != setHeight))
{
//根據(jù)高度設(shè)置寬度,因?yàn)锳ero Snap不會(huì)觸發(fā)WM_SIZING。
setHeight = Screen.height;
setWidth = Mathf.RoundToInt(Screen.height * aspect);
Screen.SetResolution(setWidth, setHeight, Screen.fullScreen);
resolutionChangedEvent.Invoke(setWidth, setHeight, Screen.fullScreen);
}
else if (!Screen.fullScreen)
{
// 保存當(dāng)前屏幕的分辨率
// 下次切換到全屏?xí)r,此分辨率將被設(shè)置為窗口分辨率
// 只有高度,如果需要,寬度將根據(jù)高度和長(zhǎng)寬比設(shè)置,以確保長(zhǎng)寬比保持在全屏模式
pixelHeightOfCurrentScreen = Screen.currentResolution.height;
pixelWidthOfCurrentScreen = Screen.currentResolution.width;
}
//保存下一幀的全屏狀態(tài)
wasFullscreenLastFrame = Screen.fullScreen;
// 當(dāng)游戲窗口調(diào)整大小時(shí),在編輯器中觸發(fā)分辨率改變事件。
#if UNITY_EDITOR
if (Screen.width != setWidth || Screen.height != setHeight)
{
setWidth = Screen.width;
setHeight = Screen.height;
resolutionChangedEvent.Invoke(setWidth, setHeight, Screen.fullScreen);
}
#endif
}
/// <summary>
/// 調(diào)用SetWindowLong32或SetWindowLongPtr64,取決于可執(zhí)行文件是32位還是64位。
/// 這樣,我們就可以同時(shí)構(gòu)建32位和64位的可執(zhí)行文件而不會(huì)遇到問(wèn)題。
/// </summary>
/// <param name="hWnd">The window handle.</param>
/// <param name="nIndex">要設(shè)置的值的從零開(kāi)始的偏移量</param>
/// <param name="dwNewLong">The replacement value.</param>
/// <returns>返回值是指定偏移量的前一個(gè)值。否則零.</returns>
private static IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
{
//32位系統(tǒng)
if (IntPtr.Size == 4)
{
return SetWindowLong32(hWnd, nIndex, dwNewLong);
}
return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
}
/// <summary>
/// 退出時(shí)調(diào)用。 返回false將中止并使應(yīng)用程序保持活動(dòng)。True會(huì)讓它退出。
/// </summary>
/// <returns></returns>
private bool ApplicationWantsToQuit()
{
//僅允許在應(yīng)用程序初始化后退出。
if (!started)
return false;
//延遲退出,clear up
if (!quitStarted)
{
StartCoroutine("DelayedQuit");
return false;
}
return true;
}
/// <summary>
/// 恢復(fù)舊的WindowProc回調(diào),然后退出。
/// </summary>
IEnumerator DelayedQuit()
{
// 重新設(shè)置舊的WindowProc回調(diào),如果檢測(cè)到WM_CLOSE,這將在新的回調(diào)本身中完成, 64位沒(méi)問(wèn)題,32位可能會(huì)造成閃退
SetWindowLong(unityHWnd, GWLP_WNDPROC, oldWndProcPtr);
yield return new WaitForEndOfFrame();
quitStarted = true;
Application.Quit();
}
}
Unity win平臺(tái) 調(diào)整窗口大小強(qiáng)制固定比例
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
相關(guān)閱讀更多精彩內(nèi)容
- https://blog.csdn.net/piglite/article/details/128122390?s...
- 1、前言 本文參考了Pytorch的安裝(Cuda+Cudnn+Anaconda)GPU版本,巨詳細(xì),安裝不成功 ...
- 版本:nacos:2.2.0springboot:2.6.13openfeign:3.1.6 只使用nacos-c...