本文直接以实战去理解锚点坐标,围绕着将一个UI移动到另一个UI位置的需求进行说明。
(anchoredPosition)UI锚点坐标,它是UI物体的中心点坐标,以UI物体锚点为中心的坐标系得来,UI锚点坐标受锚点(Anchors Min、Max)和 中心点Pivot影响。
针对锚点坐标详细说明
可直接查看RectTransform组件的Pos X和Pos Y得知锚点情况,但是只有这9种锚点分布才允许直接查看。
其他7种锚点布局情况则是
剩下3个锚点布局情况对锚点的影响一样,都是以父物体的中心点为锚点计算锚点坐标。
上面3个都是和下面这个锚点布局一样,都是能直接拿到PosX PosY是(-133,62)的
实战例子
Debug.Log("btn2.anchoredPosition:" + btn2.anchoredPosition);
Debug.Log("hand.anchoredPosition:" + hand.anchoredPosition);
//不能正常移动,除非2个物体都在相同的父节点以及锚点相同
hand.anchoredPosition = btn2.anchoredPosition;
需求是将hand物体移动到btn2物体上,如上图实战会失败。
原因:锚点不相同(虽然父物体一样)
解决:保证锚点相同、父物体相同
//1.保证父节点相同
hand.parent = btn2.parent;
//2.保证锚点相同
hand.anchorMin = btn2.anchorMin;
hand.anchorMax = btn2.anchorMax;
//能正常移动
hand.anchoredPosition = btn2.anchoredPosition;
缺点:当存在父物体是一个布局组件时(或父物体需操控子物体位置时),hand的位置会被控制无法正常设置到btn2位置。
当我们继续使用【保证锚点相同、父物体相同】来移动hand物体时,会因为布局组件而改变了hand物体位置。
解决思路1:给hand物体加LayoutElement组件忽略布局组件的影响。
解决思路2:不进行hand物体转到btn1物体的父物体下,hand物体始终在Canvas根节点下,然后去获取btn1物体在Canvas根节点下的锚点坐标(反过来理解就是逻辑上将btn1物体放到了Canvas根节点然后取锚点坐标,并且锚点坐标是相对Canvas坐标系中心点的坐标)
//1.关键点btn1.position已经正确赋值后才进行如下
//比如 btn1在布局组件之下,需要先进行一次强制刷新来保证btn1位置正常
//LayoutRebuilder.ForceRebuildLayoutImmediate(layoutRectTransform);
//2.保证hand物体的锚点布局是以父物体中心点为坐标系(因为下面转锚点的空间是以父物体中心点的)
hand.anchorMin = new Vector2(0.5f, 0.5f);
hand.anchorMax = new Vector2(0.5f, 0.5f);
//3.将btn1世界坐标转屏幕坐标
Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(Camera.main, btn1.position);
//4.屏幕坐标转相对于localRect(即Canvas)中心点的锚点坐标
Vector2 localPos = Vector2.zero;
RectTransform handParentRectTransform = hand.parent.GetComponent<RectTransform>();
RectTransformUtility.ScreenPointToLocalPointInRectangle(handParentRectTransform, screenPos, Camera.main, out localPos);
hand.anchoredPosition = localPos;
这步操作很关键:LayoutRebuilder.ForceRebuildLayoutImmediate(layoutRectTransform);
需要将所有父节点的布局组件强制刷新一遍来保证UI的世界坐标正常
public void ForceRebuildLayoutImmediate(Transform transform)
{
LayoutGroup[] layoutGroups = transform.GetComponentsInParent<LayoutGroup>();
if (layoutGroups != null && layoutGroups.Length > 0)
{
foreach (var v in layoutGroups)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(v.GetComponent<RectTransform>());
}
}
}
其次就是RectTransformUtility.ScreenPointToLocalPointInRectangle是将屏幕坐标转到以第一个参数RectTransform为中心的锚点坐标,实战则是获取到btn1的屏幕坐标后将其转到以hand物体的父物体(Canvas) 为中心的锚点坐标,然后我们就能直接用该坐标赋值给hand物体,实现将hand物体移动到btn1物体位置效果。
题外:
世界坐标,能实现需求将hand移动到btn1的位置,但如果要加偏移量之类的世界坐标不直观理解
//此前还需保证布局组件相关的刷新,与锚点坐标情况是一样的
hand.position = btn1.position;
注意事项
为什么UGUI获取布局组件下的UI坐标(无论是锚点坐标还是世界坐标等等)都可能出现非常大的误差,甚至说是完全不正确。
因为布局组件有延迟性, 你必须使用LayoutRebuilder.ForceRebuildLayoutImmediate(layoutRectTransform); 在获取坐标之前进行强制刷新布局组件,保证布局组件下的UI坐标是正常的。