在工作中用到Canvas Scaler組件時酌予,只使用到了UIScaleMode為ScaleWithScreenSize選項的情況磺箕。今天把這塊的源碼仔細看了一下,并且參考了這篇GAD的文章抛虫,終于弄懂了一些松靡,下面分享一下自己的學習心得杭抠。
tips:建議讀者也閱讀一下上文提到的文章旨剥,因為這篇文章沒有分析過程削祈。
CanvasScaler是一個控制Canvas縮放的組件,共分為以下四種模式:
- Constant Pixel Size 固定像素大小望几,此時使用ScaleFactor值直接控制Canvas的Scale值肛搬。
- Scale With Screen Size 按照屏幕大小進行縮放弱睦,會根據(jù)Resolution大小與當前屏幕大小以及三種縮放策略控制Canvas的Scale值罢艾。
- Constant Physical Size 固定物理大小,此時根據(jù)設(shè)置的物理單位直接控制Canvas的Scale值笛洛。
- World 當Canvas的RenderMode為WorldSpace時會顯示這種模式夏志,此時CanvasScaler不能控制Canvas的縮放。
在詳細說明四種模式之前苛让,我想先提出一個概念:UGUI單位(或簡稱UI單位)沟蔑。UI單位指的是你在設(shè)置UI組件大小時,所使用的單位狱杰。
例如下圖中瘦材,我會把960和640的單位稱為UI單位,我認為有這樣一個名詞對于理解接下來的內(nèi)容有很大幫助仿畸。
有了上面的定義之后食棕,我們可以得到:當UI組件的根Canvas的Scale為1時,1UI單位的大小為1像素颁湖。
Const Pixel Size 固定像素大小
在這種模式下宣蠕,我們的UI組件在不同大小的屏幕上例隆,會占用相同數(shù)量的像素甥捺。當ScaleFactor的值為x時,一個(100,100)的Image镀层,在任何屏幕上镰禾,都會占用100x*100x 個像素,即1UI單位等于x個像素唱逢。
在采用這種模式的情況下吴侦,同樣的UI在手機上會比在電腦上小很多,因為手機屏幕的像素密度要遠遠大于電腦(即手機屏幕的DPI大于電腦屏幕)
Constant Physical Size 固定物理大小
在這種模式下坞古,我們的UI組件在不同大小的屏幕上备韧,會占用相同大小的物理長度。假如我們設(shè)置PhysicalUnit的值為Inches痪枫,則我們的Canvas的Scale值會被設(shè)置為96(我電腦屏幕的DPI為96)织堂,此時1UI單位等于96個像素(即1英寸)。
舉例來說奶陈,當你有一張你手掌的圖片易阳,你希望在任意的設(shè)備上,這張手掌圖片都可以和你的手掌完全重合吃粒,可以采用這種模式潦俺。
這種模式下的兩個參數(shù):Fallback Screen DPI和DefaultSpriteDPI,是Unity無法取得屏幕的DPI值時的默認值和Sprite DPI默認值。當無法取得當前設(shè)備屏幕時事示,使用FallbackScreenDPI早像,而Sprite DPI是描述你使用圖片的DPI,對于這個值我現(xiàn)在不能很好的理解肖爵,目前我認為這個值與FallbackScreenDPI設(shè)置為相同的比較好扎酷,如果有錯誤的話還希望告知。
實現(xiàn)代碼如下:
protected virtual void HandleConstantPhysicalSize()
{
float currentDpi = Screen.dpi;
float dpi = (currentDpi == 0 ? m_FallbackScreenDPI : currentDpi);
float targetDPI = 1;
switch (m_PhysicalUnit)
{
case Unit.Centimeters: targetDPI = 2.54f; break;
case Unit.Millimeters: targetDPI = 25.4f; break;
case Unit.Inches: targetDPI = 1; break;
case Unit.Points: targetDPI = 72; break;
case Unit.Picas: targetDPI = 6; break;
}
SetScaleFactor(dpi / targetDPI);
SetReferencePixelsPerUnit(m_ReferencePixelsPerUnit * targetDPI / m_DefaultSpriteDPI);
}
Scale With Screen Size 根據(jù)屏幕大小縮放
在這種模式下遏匆,Canvas的Scale值會根據(jù)屏幕的實際大小與預設(shè)屏幕大小的比例進行縮放法挨,縮放的策略分為三種:
- 根據(jù)長寬的Match值進行調(diào)整
- 使用長寬中最小的比例進行縮放
- 使用長寬中最大的比例進行縮放
具體的實現(xiàn)代碼如下:
protected virtual void HandleScaleWithScreenSize()
{
Vector2 screenSize = new Vector2(Screen.width, Screen.height);
float scaleFactor = 0;
switch (m_ScreenMatchMode)
{
case ScreenMatchMode.MatchWidthOrHeight:
{
// We take the log of the relative width and height before taking the average.
// Then we transform it back in the original space.
// the reason to transform in and out of logarithmic space is to have better behavior.
// If one axis has twice resolution and the other has half, it should even out if widthOrHeight value is at 0.5.
// In normal space the average would be (0.5 + 2) / 2 = 1.25
// In logarithmic space the average is (-1 + 1) / 2 = 0
float logWidth = Mathf.Log(screenSize.x / m_ReferenceResolution.x, kLogBase);
float logHeight = Mathf.Log(screenSize.y / m_ReferenceResolution.y, kLogBase);
float logWeightedAverage = Mathf.Lerp(logWidth, logHeight, m_MatchWidthOrHeight);
scaleFactor = Mathf.Pow(kLogBase, logWeightedAverage);
break;
}
case ScreenMatchMode.Expand:
{
scaleFactor = Mathf.Min(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
break;
}
case ScreenMatchMode.Shrink:
{
scaleFactor = Mathf.Max(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
break;
}
}
SetScaleFactor(scaleFactor);
SetReferencePixelsPerUnit(m_ReferencePixelsPerUnit);
}
World 世界模式
World模式下不能設(shè)置Canvas的Scale值,此時能設(shè)置的一個值為Dynamic Pixels Per Unit幅聘。這個值目前看來是控制Text在世界UI下時顯示的清晰度的凡纳,該值越大,相同Text顯示的越清晰帝蒿。應(yīng)該說Unity在這里的實現(xiàn)比較詭異的(在我的理解中不應(yīng)該放在Canvas Scaler里面)荐糜。
另外,所有的模式下都有一個可設(shè)置的值叫做Reference Pixels Per Unit葛超。這個值是用來描述在NativeSize下1UI單位暴氏,應(yīng)該對應(yīng)多少Sprite中的像素的。
例如 Reference Pixels Per Unit 設(shè)置為200 則一個100x100的圖片放入Image組件绣张,點擊SetNativeSize之后答渔,得到的長寬是(200,200)
具體代碼如下:
public float pixelsPerUnit
{
get
{
float spritePixelsPerUnit = 100;
if (sprite)
spritePixelsPerUnit = sprite.pixelsPerUnit;
float referencePixelsPerUnit = 100;
if (canvas)
referencePixelsPerUnit = canvas.referencePixelsPerUnit;
return spritePixelsPerUnit / referencePixelsPerUnit;
}
}
public override void SetNativeSize()
{
if (overrideSprite != null)
{
float w = overrideSprite.rect.width / pixelsPerUnit;
float h = overrideSprite.rect.height / pixelsPerUnit;
rectTransform.anchorMax = rectTransform.anchorMin;
rectTransform.sizeDelta = new Vector2(w, h);
SetAllDirty();
}
}