【Unity3D】UGUI的anchoredPosition锚点坐标

发布于:2025-02-11 ⋅ 阅读:(10) ⋅ 点赞:(0)

本文直接以实战去理解锚点坐标,围绕着将一个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坐标是正常的。