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)系作者
- 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)氢惋,“玉大人洞翩,你說(shuō)我怎么就攤上這事稽犁。” “怎么了骚亿?”我有些...
- 文/不壞的土叔 我叫張陵已亥,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我来屠,道長(zhǎng)虑椎,這世上最難降的妖魔是什么? 我笑而不...
- 正文 為了忘掉前任的妖,我火速辦了婚禮绣檬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嫂粟。我一直安慰自己娇未,他們只是感情好,可當(dāng)我...
- 文/花漫 我一把揭開(kāi)白布星虹。 她就那樣靜靜地躺著零抬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宽涌。 梳的紋絲不亂的頭發(fā)上平夜,一...
- 那天,我揣著相機(jī)與錄音卸亮,去河邊找鬼忽妒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛兼贸,可吹牛的內(nèi)容都是我干的段直。 我是一名探鬼主播,決...
- 文/蒼蘭香墨 我猛地睜開(kāi)眼溶诞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鸯檬!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起螺垢,我...
- 序言:老撾萬(wàn)榮一對(duì)情侶失蹤喧务,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后枉圃,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體功茴,經(jīng)...
- 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
- 正文 我和宋清朗相戀三年讯蒲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了痊土。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
- 正文 年R本政府宣布衡载,位于F島的核電站,受9級(jí)特大地震影響隙袁,放射性物質(zhì)發(fā)生泄漏痰娱。R本人自食惡果不足惜,卻給世界環(huán)境...
- 文/蒙蒙 一菩收、第九天 我趴在偏房一處隱蔽的房頂上張望梨睁。 院中可真熱鬧,春花似錦娜饵、人聲如沸坡贺。這莊子的主人今日做“春日...
- 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)遍坟。三九已至,卻和暖如春晴股,著一層夾襖步出監(jiān)牢的瞬間愿伴,已是汗流浹背。 一陣腳步聲響...
- 正文 我出身青樓寂呛,卻偏偏與公主長(zhǎng)得像官帘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昧谊,可洞房花燭夜當(dāng)晚...
推薦閱讀更多精彩內(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...