天天看點

NGUI的UIScrollview的實作原理

記上次面試被一個主程說,你連NGUI底層探索的欲望都沒有,你還說你對程式設計感興趣

想想也是,人家代碼擺在那給你看你連看都不看,還說自己對學習技術有熱忱。

而且當初确實是好奇UIScrollview怎麼實作的,是以趁今天剛好做這個需求就看看NGUI底層是怎麼實作的。

一、基本結構 首先實作一個UIScrollview基本的結構是

NGUI的UIScrollview的實作原理

ScrollviewPanel     -Grid            -Item

首先在ScrollviewPanel這個控件下挂上UIScrollview這個腳本。unity就會自動幫你挂上UIPanel和Rigidbody這兩個component。 這裡很重要的是UIScrollview和UIPanel這兩個類,這兩個就是為什麼能隻顯示固定視窗界面的關鍵。這個之後再講。

接着是Grid,Grid上會挂UIGrid這個腳本。這個腳本是負責把它下面挂的所有item有序的排列好,Grid就像一個有多格的收納盒,每次層的大小是一樣的。

最後是Item,Item上挂自己的腳本控制自己的UI擺放。放個BoxCollider作為碰撞(點選)觸發,再放一個UIDragScrollview,在Scrollview這一欄上拖入ScrollviewPanel這個控件(沒有拖動的話在UI打開的時候系統也會找最近的帶有UIScrollview的父節點)。

二、原理 原理比較複雜,涉及到好幾塊,我們拆分一下,從小的開始。

NGUI的UIScrollview的實作原理

以上面UI為例

1.首先是每一個item,item上面挂自己控制的腳本,比如删除自己或者其他的什麼處理,處理完後要告訴UIScrollview和UIGrid要重新整理清單。

2.接着要把中間的多個Item放到一個大框框裡,并規定好格子大小,有序的擺放。 這個用UIGrid實作。 UIGrid 代碼邏輯相對簡單,在UIGrid裡,周遊所有item,最頂端item的坐标為(0,0)。根據擺放的方向(橫向或豎向),如上圖是豎向,根據設定好的Cell Width和Cell Height。計算好下一個的坐标就行了。

2.接下來是UIScrollview,首先按上面說的,ScrollviewPanel上面要先挂上UIPanel,這個簡單來說是作為渲染用的,是以當你有這麼一塊需要特殊處理的子產品需要渲染當然需要UIPanel專門控制。 而且在UIPanel上有個Clipping功能。這個功能是用作裁剪用,

NGUI的UIScrollview的實作原理

也就是中間這一塊紅色區域,當超框時要做什麼處理,是淡化還是直接裁掉還是不處理,

NGUI的UIScrollview的實作原理

這些東西就在這裡設定。 這裡就需要好好講講。為什麼可以讓某些item顯示某些不顯示,某些item隻顯示一部分。 這裡就涉及到UIPanel這個比較底層的東西。

首先我們看UIPanel的LateUpdate函數。

NGUI的UIScrollview的實作原理

這個每幀更新的函數做了什麼操作。這裡看到主要就兩個 UpdateSelf()和LateUpdatePanel(). 首先看UpdateSelf()

NGUI的UIScrollview的實作原理

這裡可以看到。首先各種Update,具體就不說了,說一個UpdateWidget(),為什麼要UpdateWidget呢。 這是因為NGUI是根據UIWidget來作為渲染機關,一個UIWidget就一個渲染(這樣不就巨多渲染?!是以NGUI會合并Drawcall,這個之後講)

NGUI的UIScrollview的實作原理

這也是為什麼基本的顯示機關向UIlabel和UISprite都繼承UIWidget。 這時我們在看看UIWidget腳本,看看它的OnStart()函數。

NGUI的UIScrollview的實作原理

隻做CreatePanel()操作

NGUI的UIScrollview的實作原理

這裡最重要的是

NGUI的UIScrollview的實作原理

這裡說明CreatePanel其實不是真的去Create一個Panel,而是找父節點或者自己節點上的UIPanel,去把自己這個Widget添加到Panel的WidgetList裡。

這就是為什麼UIPanel被稱作Randerer渲染器的原因,一個UIWidget是沒有辦法渲染的,所有的控件是通過找到UIPanel,再在UIPanel那去渲染。 這也是為什麼需要UpdateWidget的原因。

是以回到上面的UpdateSelf()。更新完Widgets後。接着到FillAllDrawcalls()這個函數。 這個函數很重要,就是上面說的如果這麼多widget那Drawcall不就要爆了。這個函數就是合并可以合并的drawcall。 (這函數代碼太多,截斷講吧) 具體怎麼合并的呢。 1.調SortWidgets()排序Widget。

NGUI的UIScrollview的實作原理

這裡可以看到,先比較Depth大小。大的往前排。 如果Depth一樣看Material 如果material一樣不改變排位, 如果不一樣,如果有上一個Material不為空退一位,如果自己不為空進一位。 如果都為空就按執行個體的id排位。 其實目的就是,把material按前後拍到一起,再按Depth拍到一起。

2.接下來看這個

NGUI的UIScrollview的實作原理

這段代碼是就是負責把drawcall合并。 首先擷取上個環節的widgetList,擷取每個widget。 評斷這個widget的Material,Texture,Shader是否和上一個widget的這些屬性是否一樣。 如果一樣的話就不調SubmitDrawcall這個函數。 接着看這些屬性是否為空,如果為空就去Create一個新的Drawcall。 如果不為空就改變這個Drawcall的Depth從哪到哪 把這些資料寫到記憶體。 循環判斷。 這樣就把用一樣的Material,Texture,Shader的widget生成的drawcall合并成一個。

接着看SubmitDrawcall這個函數。

NGUI的UIScrollview的實作原理

這裡我們關注drawCalls.Add(dc);這行代碼,這裡把這個drawcall加到這個drawcallList裡。 這裡就把UIPanel的LateUpdate()裡的UpdateSelf()基本看完。

接下來就是第二部分LateUpdatePanel()

NGUI的UIScrollview的實作原理
NGUI的UIScrollview的實作原理

定位到裡面

NGUI的UIScrollview的實作原理

看到不管是什麼渲染隊列方式,都是調用UpdateDrawcalls().是以直接看裡面。

這裡面代碼又比較多。主要看這裡

NGUI的UIScrollview的實作原理

簡單來說就是各種計算算出這個transform的位置旋轉縮放在顯示屏中的各種屬性資料和這個Drawcall的渲染方式隊列裁剪方式裁剪範圍等這些資料。

這樣就完成了每幀重新整理每個widget的transform和drawcall資料重新整理。

接下來就要到UIDrawcall.cs裡看看渲染。 具體不多說了。我們看為什麼那讓一些一些widget隻顯示一部分。也就是解答一開始的問題

為什麼可以讓某些item顯示某些不顯示,某些item隻顯示一部分。

首先我們會看到在之前調用的SubmitDrawcall()中有dc.Set()這個函數。這裡會看到最後會到UIDrawcall的CreateMaterial()這個函數裡。

NGUI的UIScrollview的實作原理

在這裡進行判斷,根據設定好的alpha裁剪還是soft裁剪來選擇不同的shader。 而由于這裡不同shader會産生不同的動态材質mDynamicMat。

NGUI的UIScrollview的實作原理

也是由于這個不同的動态材質使得下一步的渲染裁剪得到實作。

NGUI的UIScrollview的實作原理

我們找到這個函數。(忽略箭頭。。。) 這裡可以看到下面,當這個widget是需要裁剪的時候會計算上面的動态材質的裁剪坐标和大小,如果是SoftClip再計算強度。

這裡就是為什麼可以做到 某些item顯示某些不顯示,某些item隻顯示一部分。的所有原因了!!!

要了解底層真是不容易啊