UIRoot是NGUI最根本和最重要的腳本,在實際UI開發過程中都是以UIRoot爲根的GameObject樹,那他的作用到底是什麼,先看下UIRoot的Inspection選項:
看到這個,大致可以猜到是跟UI界面縮放有關的,而且是基於高度放縮的。
Scaling Style參數
到底每個參數(Scaling Style, Manual Height ,Minimum Height 和MaximumHight)的作用和區別是什麼,在沒有其他先驗知識的情況下,只有去看代碼了。
- public enum Scaling
- {
- PixelPerfect,
- FixedSize,
- FixedSizeOnMobiles,
- }
Scaling Style參數的作用是制定UIRoot的縮放類型,如果是PixelPerfect,Minimum Height和Maximum Height才起作用,換而言之,如果Scaling Style選擇的是PixelPerfect就要對Minimum Height和Maximum Height進行設置。FixedSize和FixedSizeOnMobiles只跟Manual Height有關,區別在於後者(FixedSizeOnMobiles)只是針對IOS和Android上的判斷,也就是說只有IOS和Android平臺下FixedSizeOnMobiles才起作用。
那他們是怎麼縮放的呢?如果是PixelPerfect縮放類型,當屏幕的分辨率大於Maximum Height,則以Maximum Height 爲基礎縮放,反之,如果屏幕分辨率小於Minimum Height 則以Minimum Height爲基礎進行縮放。例如,如果屏幕高度爲1000,而設置的Maximum Height值爲800,則UI界面整體放大爲原來的1000/800=1.25倍。
如果Scaling Style指定爲FixedSize或FixedSizeOnMobiles,則縮放只以Manual Height爲參考,屏幕分辨率的高度值不同於此設置值時,則根據其比例(即Screen Height / Manual Height)對整棵UI樹的進行“等比”縮放(寬度的縮放比也是此比例值)。
如果Scaling Style指定爲FixedSize,UIWidget.height(以UIRoot默認進行高度縮放)是不會改變的(有關UIWidget的內容可以查看D.S.Qiu的另外一篇文章),不管實際屏幕分辨率的像素是多少,這看下Example 1 的 Anchor Stretch的背景圖片,高度始終都是800,即設置的的manualHeight:
也就是相當於UIRoot下的UIWidget的height參數一直都是實際的值,雖然在實際在顯示器顯示的高度不是UIWidget.height這個值,所以纔有了放縮的感覺。實際的放縮是根據Camera.pixelHeight(這個值和Screen.height的大小是一樣的)來的,也就是放縮比 = Camera.pixelHeight/UIRoot.manualHeight,或者是Screen.height/UIRoot.manualHeight。
也就是說,當Scaling Style 指定爲FixedSize,UIRoot的子對象高度的參數保持不變,至於顯示的縮放是根據Camera自動進行的,程序無需額外控制。更多詳細的分享可以參考另外一篇有關UIAnchor 和UIStretch的文章(猛點查看)。
增補於 2013/11/16 14:20
Scaling Style策略
(1)PixelPerfect和Minimum Height, Maximum Height
使用PixelPefect,只要是想達到UI圖片儘可能不縮放,保持原尺寸大小,這種在PC端這種可以調節界面大小的會使用比較多。
(2)FixedSize和Manul Height
FixedSize只要是希望UI界面儘可能和屏幕分辨率適配,如移動設備上,特別是手機上,屏幕就那麼小,UI界面一定要求全屏顯示,就要進行縮放。
對於Unity實際開發中屏幕自適應問題,oneRain在①中有更詳細的描述,這裏只介紹下,D.S.Qiu想到的一種自適應策略——“花草”填充法。填充法(這個是D.S.Qiu命名的,哈哈,自戀下)指的是用其他圖片區填充因爲固定比例放縮而出現鏤空黑邊的區域,當然也可能已經有遊戲是這麼做的,當然oneRain說的增加一個寬度縮放比例,長寬分別以儘可能接近屏幕長寬比的比例去縮放。
Scale的實現
雖然我們已經瞭解了Scale的作用區別以及策略,那到底是怎麼實現的呢?還是上代碼:
- void Update ()
- {
- if (mTrans != null)
- {
- float calcActiveHeight = activeHeight;
- if (calcActiveHeight > 0f )
- {
- float size = 2f / calcActiveHeight;
- Vector3 ls = mTrans.localScale;
- if (!(Mathf.Abs(ls.x - size) <= float.Epsilon) ||
- !(Mathf.Abs(ls.y - size) <= float.Epsilon) ||
- !(Mathf.Abs(ls.z - size) <= float.Epsilon))
- {
- mTrans.localScale = new Vector3(size, size, size);
- }
- }
- }
- }
可以看出Update函數中是根據activeHeight來調整UIRoot的transform的localScale來實現的,哈,竟可以這麼簡單。那麼,我們只要弄清楚activeHeight就可以了:
- public int activeHeight
- {
- get
- {
- int height = Mathf.Max(2, Screen.height);
- if (scalingStyle == Scaling.FixedSize) return manualHeight;
- #if UNITY_IPHONE || UNITY_ANDROID
- if (scalingStyle == Scaling.FixedSizeOnMobiles)
- return manualHeight;
- #endif
- if (height < minimumHeight) return minimumHeight;
- if (height > maximumHeight) return maximumHeight;
- return height;
- }
- }
可以看出activeHeight就是前面Scale Style的不同參數的具體實現,即得到縮放參考高度。
Orthographic Size和分辨率
在上面Update和activeHeight的函數中都出現了 “2” 這個常數,這個常數到底是怎麼來的。要想知道這個,就要明白Camera 設定爲Orthographic類型中的Size(即Orthographic Size)的含義,查看Unity文檔,就可以知道這個Size是Camera看到區域的一半,如果Size設置爲1,則Camera可以看到高度爲爲2的區域,然後我們知道照相機看到的區域是全畫在整個屏幕的,也就是說Size的值對應爲屏幕分辨率的一半。
如果屏幕寬度爲1000個像素,Size設置的值表示1000/2=500個像素。所以,我們通過整個關係計算UIRoot下的GameObject的實際對應屏幕的高度:從GameObject向上一直到UIRoot,將它們的loaclScal相乘得到的乘積除以Size乘以屏幕高度的一半,即(localScale*....localScale)/Size*Screen.height/2。
這可以解釋UIRoot的localStyle爲啥都是很小的小數,因爲這樣可以保證UIRoot的子節點都可以以原來的大小作爲localScale,比如一張圖片是20*20的,我們可以直接設置localScale爲(20,20,1)不用進行換算,直觀方便。(NGUI3.0(or 2.7)以後的版本已經不再使用localScale來表示UISprite ,UILable(UIWidget的子類)的大小了,而是在UIWidget的width和height來設置,這樣做的好處就是一個gameObject節點可以掛多個UISprite或UILabel了,而不會受localScale的衝突影響 2013/11/16增補)。
UIRoot細節
前面說道Update函數中有一個常數 2 ,表示Size是設置爲1的,這個可以從Start函數就可以知道:
- protected virtual void Start ()
- {
- UIOrthoCamera oc = GetComponentInChildren<UIOrthoCamera>();
- if (oc != null)
- {
- Debug.LogWarning("UIRoot should not be active at the same time as UIOrthoCamera. Disabling UIOrthoCamera.", oc);
- Camera cam = oc.gameObject.GetComponent<Camera>();
- oc.enabled = false;
- if (cam != null) cam.orthographicSize = 1f;
- }
- else Update();
- }
但是這裏似乎有點疏忽,這裏只移除UIOrthoCamera這個腳本(UIRoot腳本開始就言明這兩個腳本不能同時使用,所以要移除),並將cam的orthographicSize設置爲1f,但是我想如果沒有UIorthoCamera這個腳本的話,就不會重新設置Camera的orthographicSize值,這樣如果orthographicSize不是1的話,效果就不一樣了。剛開始我會以爲這是NGUI developer的一個Bug,但是如果都在Start設置orthographicSize爲1f,那這個參數就沒有意義了,讓使用者自己設置可以有更多效果,如“屏中屏”——將滿屏的UI縮放爲另外一個UI界面的一半大小,所以纔會說“似乎有點疏忽”。
下面看下效果圖:
orthographicSize=1
orthographicSize=2
很明顯可以orthographicSize=2時,圖片進行了縮小。當orthographicSize=1時背景圖片用了UIStretch腳本來實現滿屏效果,當orthographicSize爲2時卻沒有滿屏,這就說明代碼中UIRoot是以2爲屏幕寬度的,現在Camera的視野大小爲4,那映射到屏幕當然不會有“滿屏”的效果了(只會是屏幕寬度的一半),背景圖片在左上角是因爲使用UIAnchor腳本。
②和③中分別都介紹瞭如何設置Orthographic Size來達到像素和Unity中單位對應起來,都寫的很不錯,這也是我寫這篇博客的一個觸動。
UIRoot中還有兩類函數:GetPixelSizeAdjustment 和 Broadcast,前者是獲取當前分辨率的單個像素的大小,而後者其實就一個UIRoot的消息廣播函數,還有一個當前激活狀態下的UIRoot的隊列。至此D.S.Qiu已經將UIRoot腳本解析的淋漓盡致了,自然就剩下小結了。
『基於寬度放縮
UIRoot是基於高度放縮的,即放縮的比例是以高度爲參考的,所以UIRoot有一個manualHeight的參數。那麼對於橫版遊戲顯然不行,要是能實現基於寬度放縮。所以可以通過設置一個“manualWidth”的參數來做。比如我們項目中使用的是 1024 作爲UI的寬屏尺寸,通過換算設置manualHeight的值:
- int height = Mathf.Max(2, Screen.height);
- manualHeight = Screen.height * 1024 / Screen.width; //基於寬度的屏幕分辨率自適應
之前的同事用了很多方法都沒有搞定,其實根本就沒有那麼複雜,所以一旦知道原理之後,很多事情就變得簡單多了。
』
增補於 2013,12,23 22:14
小結:
一直都想把NGUI的內部機制徹底的弄明白,一直都沒付諸實踐,知道看②和③中的文章,D.S.Qiu發現還是有必要儘快整理下,切好最近項目沒什麼事情,也是週末。UIRoot這個腳本雖然很簡單,但確實NGUI整個體系的基石。更多NGUI文章點擊查看。
如果您對D.S.Qiu有任何建議或意見可以在文章後面評論,或者發郵件([email protected])交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉載請在文首註明出處:http://dsqiu.iteye.com/blog/1964679
更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風)
參考:
①oneRain: http://blog.csdn.net/onerain88/article/details/11713299
②風宇衝: http://blog.sina.com.cn/s/blog_471132920101fua3.html
③midashao:http://blog.csdn.net/midashao/article/details/8232341