NGUI锚定系统:UIAnchor\UIRect
目录:NGUI源码学习
一、UIAnchor:非UIRect对象的坐标适配。
NGUI的Anchor锚点:
public enum Side { BottomLeft, Left, TopLeft, Top, TopRight, Right, BottomRight, Bottom, Center, }
锚定坐标的计算:mRect{x = xMin, y = yMin, width = xMax - xMin, height = yMax - yMin}.
1.计算出mRect,获取mRect的中心点坐标。
2.根据side值获取对象将使用的基础锚定坐标v。
3.根据pixelOffset、relativeOffset等预设的偏移信息修正坐标v。
4.坐标转换,赋值。该功能无法修改渲染对象的大小。
//只截取核心代码段 float cx = (mRect.xMin + mRect.xMax) * 0.5f;//锚定目标的中心点,mRect锚定目标的信息。 float cy = (mRect.yMin + mRect.yMax) * 0.5f; Vector3 v = new Vector3(cx, cy, 0f);//当前对象将被设置的坐标。 if (side != Side.Center) { if (side == Side.Right || side == Side.TopRight || side == Side.BottomRight) v.x = mRect.xMax; else if (side == Side.Top || side == Side.Center || side == Side.Bottom) v.x = cx; else v.x = mRect.xMin; if (side == Side.Top || side == Side.TopRight || side == Side.TopLeft) v.y = mRect.yMax; else if (side == Side.Left || side == Side.Center || side == Side.Right) v.y = cy; else v.y = mRect.yMin; } float width = mRect.width; float height = mRect.height; v.x += pixelOffset.x + relativeOffset.x * width;//偏移值,pixelOffset绝对像素,relativeOffset相对比例 v.y += pixelOffset.y + relativeOffset.y * height; v.x = Mathf.Round(v.x); v.y = Mathf.Round(v.y); //坐标转换 if (pc != null) { v = pc.cachedTransform.TransformPoint(v); } else if (container != null) { Transform t = container.transform.parent; if (t != null) v = t.TransformPoint(v); } v.z = mTrans.position.z; if (mTrans.position != v) mTrans.position = v;//坐标设置
举个例子:在下面的图片中,小图锚定到大图中,铆钉信息如下:
小图坐标计算:这边的tf坐标根据Side的枚举值得出。
X = tf.x + relativeOffset.x * width + pixelOffset.x = -640 + 0.2*1280 + 100 = -284
Y = tf.y + relativeOffset.y * height+ pixelOffset.y =360 +(-0.2)*720 + (-100 )= 116
二、UIRect:渲染对象基类。
1、AnchorPoint:锚定功能。
保存了锚点相关的target\relative\absolute等核心属性,以及封装了部分公共方法。
2、重要属性。
- leftAnchor/rightAnchor/bottomAnchor/topAnchor:四个角的锚点。
默认left\bottom的relative为0,top/right的relative为1.
Inspector中设置的是absolute值。
- mUpdateFrame:当前更新帧,避免重复更新。
- mUpdateAnchors:标记当前帧是否要更新锚点。
- cachedGameObject/cachedTransform/parent/root:缓存相关节点。
3.重要方法。
- UpdateAnchorsInternal():更新锚点信息,包括更新锚点target的rect,该方法在Mono的Update方法执行。而NGUI的渲染逻辑在LateUpdate执行,确保了在渲染时获取到最新的锚点信息及最终的渲染范围。
- GetSides:虚方法,用于获取四条边相对于某个Transform的坐标,具体实现在子类(UIWidget,UILabel,UIPanel)。例如UIWidget实际取到的是:
|
/// <summary> /// Get the sides of the rectangle relative to the specified transform. /// The order is left, top, right, bottom. /// </summary> public override Vector3[] GetSides (Transform relativeTo) { Vector2 offset = pivotOffset; float x0 = -offset.x * mWidth; float y0 = -offset.y * mHeight; float x1 = x0 + mWidth; float y1 = y0 + mHeight; float cx = (x0 + x1) * 0.5f; float cy = (y0 + y1) * 0.5f; Transform trans = cachedTransform; mCorners[0] = trans.TransformPoint(x0, cy, 0f); mCorners[1] = trans.TransformPoint(cx, y1, 0f); mCorners[2] = trans.TransformPoint(x1, cy, 0f); mCorners[3] = trans.TransformPoint(cx, y0, 0f); if (relativeTo != null) { for (int i = 0; i < 4; ++i) mCorners[i] = relativeTo.InverseTransformPoint(mCorners[i]); } return mCorners; }
- OnAnchor:虚方法,核心方法。定义如何根据锚点信息更新自身位置及大小,具体实现在子类(UIWidget,UILabel,UIPanel),在UpdateAnchorsInternal里调用。以UIWidget为例:
protected override void OnAnchor () { float lt, bt, rt, tt; Transform trans = cachedTransform; Transform parent = trans.parent; Vector3 pos = trans.localPosition; Vector2 pvt = pivotOffset; // Attempt to fast-path if all anchors match if (leftAnchor.target == bottomAnchor.target && leftAnchor.target == rightAnchor.target && leftAnchor.target == topAnchor.target) { Vector3[] sides = leftAnchor.GetSides(parent); if (sides != null) { lt = NGUIMath.Lerp(sides[0].x, sides[2].x, leftAnchor.relative) + leftAnchor.absolute; rt = NGUIMath.Lerp(sides[0].x, sides[2].x, rightAnchor.relative) + rightAnchor.absolute; bt = NGUIMath.Lerp(sides[3].y, sides[1].y, bottomAnchor.relative) + bottomAnchor.absolute; tt = NGUIMath.Lerp(sides[3].y, sides[1].y, topAnchor.relative) + topAnchor.absolute; } } // Calculate the new position, width and height Vector3 newPos = new Vector3(Mathf.Lerp(lt, rt, pvt.x), Mathf.Lerp(bt, tt, pvt.y), pos.z); newPos.x = Mathf.Round(newPos.x); newPos.y = Mathf.Round(newPos.y); int w = Mathf.FloorToInt(rt - lt + 0.5f); int h = Mathf.FloorToInt(tt - bt + 0.5f); // Maintain the aspect ratio if requested and possible if (keepAspectRatio != AspectRatioSource.Free && aspectRatio != 0f) { if (keepAspectRatio == AspectRatioSource.BasedOnHeight) { w = Mathf.RoundToInt(h * aspectRatio); } else h = Mathf.RoundToInt(w / aspectRatio); } // Don't let the width and height get too small if (w < minWidth) w = minWidth; if (h < minHeight) h = minHeight; // Update the position if it has changed if (Vector3.SqrMagnitude(pos - newPos) > 0.001f) { cachedTransform.localPosition = newPos; if (mIsInFront) mChanged = true; } // Update the width and height if it has changed if (mWidth != w || mHeight != h) { mWidth = w; mHeight = h; if (mIsInFront) mChanged = true; if (autoResizeBoxCollider) ResizeCollider(); } }
4.锚点具体步骤:
- 获取对应的锚点对象target的渲染区域sides。例如下面的Texture的Sides的4个点。
- 通过leftAnchor/rightAnchor/bottomAnchor/topAnchor确定四个边的坐标,例如左边坐标计算是
lt = NGUIMath.Lerp(sides[0].x, sides[2].x, leftAnchor.relative) + leftAnchor.absolute; static public float Lerp (float from, float to, float factor) { return from * (1f - factor) + to * factor; }
- 这边leftAnchor.relative=0是在UIRectEditor.DrawAnchorTransform设置的,同理rightAnchor.relative= 1。
3.通过上一步计算的四条边和Pivot计算最终的显示坐标。
Vector3 newPos = new Vector3(Mathf.Lerp(lt, rt, pvt.x), Mathf.Lerp(bt, tt, pvt.y), pos.z);
4.通过4条边计算渲染区域的大小。
int w = Mathf.FloorToInt(rt - lt + 0.5f);
int h = Mathf.FloorToInt(tt - bt + 0.5f);
5.通过前面的知识点,NGUI通过矩形的4个点确定最终的渲染区域(大小及坐标)。锚点系统通过动态修改矩形的四条边,从而实现对最终渲染矩形坐标、大小的修改。
一直想把之前工作、学习时记录的文档整理到博客上,一方面温故而知新,一方面和大家一起学习 -程序小白