本文僅僅記錄自己在工作中踩到的ugui的坑。並講述如何填的坑。
干貨羅列在前,不願意看的,拿東西走人,自己研究:
- RectTransform m_Rect
- m_Rect.localPosition
- m_Rect.rect (m_Rect.rect.width m_Rect.rect.height)
- m_Rect.pivot
- m_Rect.sizeDelta
關於ugui的排版方面,剛上手的時候,覺得:哎喲!不錯,這個刁。
但是如果你使用過qt等軟件,其實ugui的對齊功能還是很落后的。
如果你繼續使用這個排版功能,你就會發現:什么啊這是,什么邏輯啊,完全沒懂啊。
關於ugui工作的原理以及各個參數不在本文討論范圍之內。什么這個參數改一下會如何,原點等,一概不研究,只說兩件事:
- 如何修改ugui控件到我指定的大小
- 如何移動ugui到指定位置上去
關於ugui的以上兩點,都在RectTransform這個組件里。而這個組件的變量雖然並不很多,但是互相關聯非常精密。如果沒有弄懂ugui是如何設計的,幾乎沒辦法隨心所欲的工作。
這里說的隨心所欲的工作指的是:當我想要移動或者縮放ugui控件的時候,能夠直接修改變量成我期望的值,或者傳入參數就能達到我期望的效果。
比方說。我期望控件移動到跟另外一個控件相同的位置上去(對齊)。或者我希望A控件改變大小和B一樣。又或者跟隨鼠標移動。
以上三個情況,如果你擺弄過ugui,其實就很容易暴露RectTransform里面的問題。
以下,用我的工作作為講解。告訴你如何通過簡單的技巧,讓你跳過理解ugui排版直接實現最簡單的移動和縮放。讓排版功能化繁為簡,返璞歸真
先說一些基本的內容。
- Position和localPosition值不一樣,而且沒有找出規律,在我測試到的范圍內,position這個變量幾乎不能用於腳本的各種計算。
- ugui在控件位置不發生變化的情況下,修改pivot,Position和localPosition會變。
- pivot這個變量,在unity面板里修改和在腳本里修改表現出的行為不一致。
- 在unity面板里修改,表現出的行為是ugui空間在屏幕上的位置沒有發生變化,而Position和localPosition有改變;
- 在腳本里修改pivot,Position和localPosition不會改變,而ugui空間在屏幕上的位置會改變
- rect只讀,整個結構下面的所有數據僅僅是只讀,沒找到方法修改。但是rect的widht和height屬性記錄的是ugui控件的像素大小(縮放是否參與影響暫時還沒進展到這步)
- 可以通過sizeDelta修改ugui控件大小。但是這個變量和ugui自身的對齊方式有關(錨點位置和方式),如果你沒有弄懂對齊方式跟sizeDelta的關系,只是簡單的將sizeDelta改成(100.0f,100.0f)出現的大小可能不會是100×100.
有了以上基本內容,就可以開始講解了。
我要做一個鑲嵌。將B鑲嵌到A的Slot里去。Slot有背景圖,圖片初始很小,鑲嵌物大小不一,所以當鑲嵌完成,背景圖需要縮放。B需要修改坐標和A的Slot相同。
先說縮放。
會影響縮放的,主要是anchor參數。這個參數在不同值的時候,對縮放的影響是不一樣的。因為Slot有上有下,有些靠左,有些靠右,有些居中,所以anchor參數各式各樣。
思路其實很簡單。因為在scanl都為1的狀態下(別的狀態還沒測試),rect的widht和height屬性就是控件的大小。所以將B的大小直接給Slot即可。
於是將sizeDelta修改為widht和height的值。完成。
最開始的時候所有slot的錨點都是在左上角,該方案還行。可是后面出現了各種隨父控件大小變化以及錨點左中右對齊的slot之后,這個方案就失效了,將B的widht和height直接給slot,出現了各種奇怪的大小。
這才發現收anchor變量和offsetmax/min這些變量影響。
調試了很久,輸出sizeDelta信息查看之后才發現,雖然sizeDelta的值很難理解,但是sizeDelta的增量卻是正確的。比方說,我將sizeDelta改成100×100和200×200大小我是確定不了。但是從100到200,sizeDelta的輸出信息可以看到增量了100。
於是逐將上面的方案改為:
sizeDelta+增量即可。
代碼片段
1 RectTransform mNewNodeRect = newNode.GetComponent<RectTransform>(); 2 RectTransform mSlotRect = mSlot.GetComponent<RectTransform>(); 3 float mX = mNewNodeRect.rect.width - mSlotRect.rect.width; 4 float mY = mNewNodeRect.rect.height - mSlotRect.rect.height; 5 mSlotRect.sizeDelta = new Vector2(mSlotRect.sizeDelta.x + mX, mSlotRect.sizeDelta.y + mY);
至此,縮放搞定。你不需要知道任何ugui對齊相關的東西,就可以實現准確的縮放。
然后是移動。
最開始做的時候,因為錨點全都在左上角。所以移動的時候很簡單,直接在面板里修改PosX和PosY就好。
但是因為PosX和Posy受pivot影響,所以輸出查看坐標數據,發現Position和LocalPosition兩個變量,LocalPosition變量的值是PosX和PosY里的數據,於是采用了LocalPosition。
於是鑲嵌方案就是:
先將slot拉到B的大小,然后再將slot的位置給B。
因為從外部保證了界面制作的時候,錨點和pivot全部一樣,所以這個方案成功用了一段時間。
后來,隨着各種其他方式的錨點的加入,又有不同的pivot的加入。原本的坐標直接給值的方案就不行了。於是又測試。
這次測試發現,ui控件坐標,是以pivot點為原點。如果你將兩個不同對齊方式的ugui控件設置成一樣的LocalPosition(父控件相同的前提下),那么對齊的點,是pivot點。
因為我的slot背景圖已經拉伸到和B一樣大了。所以我需要的是讓兩個控件的圖片左上對齊。
而pivot是一個0~1的值。0,就在最左上,1就在右下。正好是寬高。所以widht*pivot.x正好就是左邊到pivot的距離。
於是修改方案就出來了。
1 Vector3 mLeftTop = m_Rect.localPosition; 2 float mLeftOffset = m_Rect.rect.width * m_Rect.pivot.x; 3 float mTopOffset = m_Rect.rect.height * m_Rect.pivot.y; 4 mLeftTop.x -= mLeftOffset; 5 mLeftTop.y -= mTopOffset; 6 7 RectTransform mInsert = m_Insert.GetComponent<RectTransform>(); 8 mLeftOffset = mInsert.rect.width * mInsert.pivot.x; 9 mTopOffset = mInsert.rect.height * mInsert.pivot.y; 10 Vector3 mfinalyPos = mLeftTop + new Vector3(mLeftOffset, mTopOffset, 0.0f); 11 mInsert.localPosition = mfinalyPos;
原理就是,先計算出slot的左上角的點。然后再將B設置到這個點上,然后B再自己加上從左上點到pivot點的差值。就可以讓兩圖以左上角對齊了(其他對齊方式自行推演)。
一個sizeDelta,一個localPosition就可以跳過復雜的ugui的錨定實現正確的對齊和縮放。
至此,ugui的縮放和移動就大功告成。
這里不得不談下自己的感想:
我們做東西的時候,都是習慣先按照自己的思維和理解來做。如果符合自己的思維,則好用。不符合,“這tm什么設計,腦子有屎”。用這樣的觀點來做事,不太好。一是不尊重別人的勞動成果。二是學習東西始終帶抵觸情緒。
雖然我知道我應該去學習和滿足unity的設計要求。但是這樣的ugui設計,真的是腦子有屎。強大到只有程序才會懂的ui,你做鳥的編輯界面啊。直接給程序說明文檔就好了。想做個ui上最常見的移動和縮放,我還得先研究一下ui系統怎么設計的。而且最終出來的效果是既不方便使用,也不方便理解,也趕不上別的ui系統(有用過qt做編程的就會懂得qt的編輯器和ui對齊方式多么直觀)。我只想說:這tm什么設計,腦子有屎。
這個ugui,一點都不符合unity的風格。
--------------------------------------------------
特別補充一點,以上方案,是在A是父對象,slot和B同級目錄下,scan為1的情況下進行的,不同目錄情況,請自信根據文中線索推導。另外縮放系數參與的情況下未進行測試
------------------------2016 09 26 11:33--------------------------
在已經有以上知識內容的前提下,修正一個bug。
因為pivot這個變量是用於鉚釘控件自身的對齊方式。舉例:如果我把坐標填寫成0.0.0點。空間將會用pivot的點去對。而pivot是以左下角為0.0,所以如果pivot為0.0的時候,你會看到控件以左下角為對齊點去對0.0.0點坐標。
提這個的原因是因為,獲取到A的坐標之后,其實是獲取的A的pivot點。需要通過修正來滿足自己的需求。本文上面的
float mLeftOffset = m_Rect.rect.width * m_Rect.pivot.x;
float mTopOffset = m_Rect.rect.height * m_Rect.pivot.y; mLeftTop.x -= mLeftOffset; mLeftTop.y -= mTopOffset;
這段修正代碼,是在計算左下點的坐標,而不是左上。
提到這里我覺得就夠了,坐標點算偏移不是難事,各位自己算下自己期望的坐標偏移就好。我也要自己檢查下為什么之前用這個算法算出來竟然是期望的左上。
------------------------2016 09 26 19:01--------------------------
好吧,我查了下我自己的bug,我這個算法,其實是左下角對齊的方式。但是呢,因為我的鑲嵌背景和鑲嵌物等大,所以鑲嵌上去之后,左下對齊和左上對齊就沒有任何差別。
如果是不等大的坐標排列,可以自己計算一下高度差,或加或減一下就好了。