對 UIRect 的一點理解
UIRect,一個繼承 MonoBehaviour 的抽象類,主要實現了錨點功能。
2.1 UIRect 簡單介紹
/// UIRect.cs /// <summary> /// Helper function that returns the distance to the camera‘s directional vector hitting the panel‘s plane. /// </summary> protected float cameraRayDistance { get { if (anchorCamera == null) return 0f; #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 if (!mCam.isOrthoGraphic) #else if (!mCam.orthographic) #endif { Transform t = cachedTransform; Transform ct = mCam.transform; Plane p = new Plane(t.rotation * Vector3.back, t.position); Ray ray = new Ray(ct.position, ct.rotation * Vector3.forward); float dist; if (p.Raycast(ray, out dist)) return dist; } return Mathf.Lerp(mCam.nearClipPlane, mCam.farClipPlane, 0.5f); } }
代碼一,cameraRayDistance屬性
創建一個和自己平行的面,我們只需要一條法線和一個點。點比較容易,自己的位置就行了,那麽為什麽 t.rotation * Vector3.back 就是自己的法線呢?其實對於 NGUI 來說 Vector3.back 或者 Vector3.forward 就是默認情況下面板的法線(面板沒有做任何的旋轉操作),如果面板經歷過一系列旋轉操作,只需要讓 Vector3.back 或者 Vector3.forward 經歷同樣的旋轉操作就能獲取到此時自己的法線向量(t.rotation * Vector3.back就是這個意思)。
代碼二展示了計算上下左右四個邊位置的錨點值的一個相對比較復雜的情況,代碼有點多,原理還是比較簡單的,就是先計算出上下左右四個邊的世界坐標,再轉換成相對參照物 relativeTo 的局部坐標。
/// NGUITools.cs /// <summary> /// Get sides relative to the specified camera. The order is left, top, right, bottom. /// </summary> static public Vector3[] GetSides (this Camera cam, float depth, Transform relativeTo) { #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 if (cam.isOrthoGraphic) #else if (cam.orthographic) #endif { float os = cam.orthographicSize; //攝像機可視區域左邊位置 float x0 = -os; //攝像機可視區域右邊位置 float x1 = os; //攝像機可視區域頂部位置 float y0 = -os; //攝像機可視區域底部位置 float y1 = os; Rect rect = cam.rect; Vector2 size = screenSize; float aspect = size.x / size.y; aspect *= rect.width / rect.height; //通過設備真實分辨率和攝像機視口區域大小來矯正攝像機可視區域左邊和右邊的位置 x0 *= aspect; x1 *= aspect; // We want to ignore the scale, as scale doesn‘t affect the camera‘s view region in Unity Transform t = cam.transform; Quaternion rot = t.rotation; Vector3 pos = t.position; int w = Mathf.RoundToInt(size.x); int h = Mathf.RoundToInt(size.y); if ((w & 1) == 1) pos.x -= 1f / size.x; if ((h & 1) == 1) pos.y += 1f / size.y; //計算上下左右四個邊的世界坐標 mSides[0] = rot * (new Vector3(x0, 0f, depth)) + pos; mSides[1] = rot * (new Vector3(0f, y1, depth)) + pos; mSides[2] = rot * (new Vector3(x1, 0f, depth)) + pos; mSides[3] = rot * (new Vector3(0f, y0, depth)) + pos; } else { //暫時不研究這段代碼 mSides[0] = cam.ViewportToWorldPoint(new Vector3(0f, 0.5f, depth)); mSides[1] = cam.ViewportToWorldPoint(new Vector3(0.5f, 1f, depth)); mSides[2] = cam.ViewportToWorldPoint(new Vector3(1f, 0.5f, depth)); mSides[3] = cam.ViewportToWorldPoint(new Vector3(0.5f, 0f, depth)); } //有參照物的話,計算上下左右四個邊相對參照物的的局部坐標 if (relativeTo != null) { for (int i = 0; i < 4; ++i) mSides[i] = relativeTo.InverseTransformPoint(mSides[i]); } return mSides; }
代碼二,獲取錨點上下左右四個邊的具體位置
圖一,NGUI 官方示例 Tutorial 7
圖二展示了 Label - Title 的組件設置,Widget 欄有對齊方式,高度寬度信息,Anchors 欄有錨點信息,NGUI 的錨點是設置節點上下左右四個邊相對於父節點的位置,譬如 Label - Title 就設置了它的底邊距離 Label - Content 的頂邊 40px,它的頂邊距離 Label - Content 的頂邊 80px(40px + 自己的高度),若是 Label - Content 的內容變化導致了高度變化, 自動向上下兩邊放縮,Label - Content 頂部位置產生變化,Label - Title 也就跟著調整自己的位置了。
圖二,Label - Title 組件設置
圖二展示了 Sprite - Background 的組件設置,可以看出它設置了自己的上下左右四個邊分別距離 Label - Title 上下左右四個邊 40px。
圖三,Sprite - Background的組件設置
其實我一度認為錨點只是一個點而已,按照對齊方式它可以是中心點,左側中點,左上角等等,設置也只是設置它相對父節點的局部位置。當然我也認為 NGUI 的錨點寫的有些繁雜,不過 NGUI 這樣實現自然有它的理由(不然也沒法實現上面說的類似功能了),我們就不談優點了,來談下這樣實現有什麽問題,其實是有一個不可避免的小問題的,那就是錨點設置的是節點四個邊的位置,它們的值會與節點高度值和寬度值互相影響,雖然這是實現自適應必須的,卻也不是所有情況都需要。譬如 Label - Title,如果我設置它的底部距離 Label - Content 的頂部 40px,它的頂部距離 Label - Content 的頂部 70px,這樣 Label - Title 的高度就自動變成 30px;如果我直接修改了 Label - Title 的高度為 50px,對齊方式是居中對齊,那麽它會向上下兩邊各延伸 5px,那麽它的底部距離 Label - Content 的頂部就自動變成了 35px,它的頂部距離 Label - Content 的頂部就自動變成了 85px。
對 UIRect 的一點理解