2024-7-22

UGUI的点位概念理解

anchoredPosition:

  • 和父类的锚点(Anchor)有关:anchoredPosition 是相对于父对象的锚点位置的。父对象的锚点定义了子对象的原点。

  • 和自己的轴心(Pivot)有关:子对象的轴心会影响到 anchoredPosition,因为它决定了子对象自身的原点位置。

  • 和父类的轴心无关:父对象的轴心位置不会影响子对象的 anchoredPosition。

localPosition:

  • 和父类的轴心有关:localPosition 是相对于父对象的轴心位置的。父对象的轴心决定了子对象的原点。

  • 和自己的轴心有关:子对象的轴心同样会影响 localPosition,因为它决定了子对象自身的原点位置。

  • 和父类的锚点无关:父对象的锚点位置不会影响子对象的 localPosition。

position:

  • 和世界坐标有关:position 是相对于世界坐标的位置,没有父类的概念。

  • 和自己的轴心有关:子对象的轴心会影响 position,因为它决定了子对象自身模型的原点位置。

  • 全局坐标系转换:由于positon的全局性,UGUI里面可以直接拿它对UI们进行统一坐标系的比较,但是使用它为参照进行坐标设置的时候,记得转换为对应的Rect UI坐标系,下面会详细介绍UGUI里面的坐标转换问题。

参考文章:unity知识点 专项一 localPosition与anchoredPosition(3D)的区别_unity anchoredposition-CSDN博客

全局坐标系转换到UI坐标系

void Start() {
    Debug.Log(transform.position); //子UI世界坐标
    RectTransform rect = transform.parent.GetComponent<RectTransform>(); //子UI父对象RectTransform
    Transform partransform = transform.parent.GetComponent<Transform>(); //子UI父对象Transform

    /*在锚点无论如何变化的情况下,下面四个Debug,输出结果完全相同!*/
    Debug.Log(rect.InverseTransformPoint(transform.position)); //转换对应世界坐标到对应父对象下局部坐标(RectTransform)
    Debug.Log(partransform.InverseTransformPoint(transform.position)); //转换对应世界坐标到对应父对象下局部坐标(Transform)
    Debug.Log(transform.localPosition); //子对象的局部坐标
    
    // 将世界坐标转换为屏幕坐标
    Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(null, (transform as RectTransform).position);
    // 创建一个变量来存储转换后的本地UI坐标
    Vector2 localPoint;
    // 将屏幕坐标转换为父RectTransform的本地坐标
    RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, screenPoint, null, out localPoint);
    Debug.Log(localPoint); //经过世界坐标系到屏幕坐标,然后屏幕坐标系切换到对应父类UI的Rect局部坐标系的局部坐标
}

锚点设置和父UI轴心相同

锚点设置和父UI轴心不同

通过比较我们可以得知,Unity官方目前提供的UGUI转换API,没有一个API会考虑到子UI本身锚点在父对象的哪里的问题(尽管RectTrasform本身按理说有记录有关信息),都是粗暴的以父坐标UI的轴心为标准进行局部坐标转换。

所以我们可以得知两个信息:

  1. 如果希望动态生成UI对象到指定父对象下的局部坐标,如果你采用的是anchoredPosition的设置方式,建议把子UI预设锚点居中,才能确保UI设置位置符合预想。但是如果你使用localPositon,你的坐标设置始终会以轴心为标志进行设置。

  2. UI里面统一坐标系的时候,可以尝试使用position,它可以自然的统一所有UI的坐标系,并方便的使用各类API进行转换或者进行位置比较。但是不建议直接使用positon设置坐标,建议还是化为canvas内的局部坐标来设置坐标。(下一节会提到)

Scale with screen size原理

Scale with screen size原理

Scale with screen size的原理十分简单,那就是不同分辨率下,根据我们预设的Reference Resoulution拿到缩放值,直接对Scale和positon进行整体缩放和位移,你可以修改分辨率然后观察canvas的scale直接看得出来。

也正因为如此,其中的子UI虽然本地坐标值保持不变,但是你拖出canvas就会发现,修改分辨率后,这个UI的位置和scale是会实际发生改变的,我称之这个模式下对于UI的缩放。

为了验证这个猜想,你在Scale With Screen Size模式下,你可以先把分辨率设置由1920/1080切换为3840/2160,你会发现UI不变,然后把某个UI拖出去Canvas,再切换会1920/1080,再把这个UI拖回来,你就会发现这UI比之前而言变大了,因为它没有参与缩放回1920/1080的过程!

可以理解为,缩放后的事实positon和scale发生了变化,但是localpositon,anchoredPosition和localscale不变,导致面板看上去不变。

动态生成UI需要注意的小坑

缩放

下面有一个需求,假如一个image left,一个image right,你希望生成一条image line在它们中间,你只需要拿到它们的中间位置和它们两的距离即可。

你之前可能会想,既然直接使用positon坐标系那么统一,如果可以确定轴心依然为世界坐标系的情况下,直接用positon计算left和right,然后设置line的中心位置和距离可以吗?

答案设置Line的点可以,但是设置距离不可以,但凡是动态生成UGUI的时候,只建议用positon来快速比较和转换坐标,不建议用positon来设置坐标或者获取绝对的像素值!特别是你采用的是Scale With Screen Size模式的时候。

比如,在3840/2160下,left和right的positoon会被自动放大一次,导致它们的positon绝对值本来就放大了,若以此为根据设置了Line的长度,然后line的长度依然会被模式自动再次放大一次,导致结果不符合预期。

所以正确做法,是把right和left化为Line所在父类的下的局部坐标,然后就这个局部坐标进行比较,赋值给Line的局部坐标。

此外,由于这是缩放问题,所以UI里的SetParent的时候也有一个暗坑,总结为UI使用setparent的时候第二个参数建议统统false,这里不重点讲解,可以查看下文:Unity 3D之UI设置父子关系setParent坑 - thissky - 博客园 (cnblogs.com)

Layout Group的刷新

如果某一帧对于Layout Group的父类下添加一个动态生成的UI,如果需要获取到这个生成的UI的位置,则要延迟到帧末尾在执行坐标转换,或者提前手动调用Canvas.ForceUpdateCanvases()进行刷新。

UpgraderUI.gameObject.SetActive(true); //首先必须SetActive显示所有Layout组件下的UGUI物体,然后刷新,才能让线条生成拿到变化后的坐标
Canvas.ForceUpdateCanvases(); //由于线条需要拿到新UI坐标,强制刷新一次UI

如果直接转换,由于Layout Group组件还没计算子节点的位置并重新设置对象,会得到一个不正确的值。

轴心概念

对于UI元素:

  • 轴心决定了该元素在其父元素中的定位方式:例如,一个UI元素的轴心在中心(0.5, 0.5)时,它的 anchoredPosition 和 localPosition 都会从该元素的中心计算。

对于精灵图(Sprites):

  • 轴心同样很重要:精灵图的轴心决定了图像的旋转和缩放中心,以及如何在世界空间或UI空间中定位。

对于一般的GameObject:

  • 轴心通常通过父物体和子物体来间接实现:GameObject没有明面上的轴心概念,但是如果需要调整轴心位置,通常会创建一个空的父物体,将实际的GameObject作为子物体,然后调整父物体的位置以间接改变子物体的轴心位置。

  • 当然,也可以选择在建模软件里面直接设置模型的轴心点,但是很多情况下,都会设置成居中的情况方便使用。

2024-7-23

Mesh切割算法

Unity Mesh切割算法详解 - 游戏开发阿博 - 博客园 (cnblogs.com)

unity3d - 网格切割算法 - 个人文章 - SegmentFault 思否

正交投影和透视投影

正交投影和透视投影的本质是拿到相机的观察空间里面的新坐标系,并拍扁为了屏幕坐标系的转换做准备

先平移很大的原因是为了方便计算,如果在相机坐标系的情况直接计算缩放(设置标准化立方体)难度颇大。

不要把子空间转移到父空间需要先缩放,再旋转后平移的思维放到正交投影和透视投影上导致纠结,它看起来不遵守这个规律,因为它们不是传统意义上的坐标系转换,因为它们主要是为了映射到屏幕坐标系设计的

缩放操作不会有逆运算,更多的是为了方便屏幕坐标系读取,约定的一个坐标系公式。

投影变换不仅仅是简单的坐标系变换,它更多的是一种为了映射到二维屏幕而设计的专门变换。在这个过程中,三维点经历从世界坐标系到相机坐标系的转换,然后通过投影矩阵映射到一个新的二维屏幕坐标系。这个变换与传统的坐标系转换(例如仅仅涉及平移和旋转)有本质的不同。

比如它的变化目标,NDC本身就是人为产物

2024-7-26

线性代数

什么是线性、非线性、齐次、非齐次_齐次和线性怎么理解-CSDN博客

(1 封私信 / 17 条消息) 如何理解方程组、矩阵和向量组的关系? - 知乎 (zhihu.com)

线性代数拾遗(一):线性方程组、向量方程和矩阵方程 (qq.com)

6.1.3 一阶线性方程的线性、齐次与通解公式_一阶线性齐次方程-CSDN博客

2024-7-30

广度优先遍历和深度优先遍历

算法 - 轻松掌握广度优先搜索入门 - 个人文章 - SegmentFault 思否

C# 广度优先遍历(BFS)算法实现_c# 广度优先算法-CSDN博客

编辑器扩展课外补充

编辑器拓展之一些也许有用的小知识 - 幻想社区 (fantsida.com)

UGUI源码

UGUI源码分析 | 张卫的博客 (zhangwei.press)

2024-8-21

Unity UGUI坐标系与鼠标屏幕坐标系的关联(悬浮窗跟随)

在Unity的 Screen Space - Overlay 模式下,Canvas的UGUI坐标系与鼠标获取的屏幕坐标系之间的关系非常直接,这种直接关联为UI开发提供了便利。下面是这种关系的具体解释和应用:

坐标系直接对应

屏幕坐标与UGUI坐标一致:在Screen Space - Overlay模式下,UGUI系统中UI元素的X和Y坐标直接对应于屏幕的像素坐标(如果把canvas下的直接UI锚点设置成左下角,就会发现坐标系和世界坐标系相同)。这意味着,使用Input.mousePosition获取的鼠标屏幕坐标(以像素为单位)可以直接用来设置UI元素的位置。

无需坐标转换:通常在处理UI与3D世界的交互时,需要将屏幕坐标转换为世界坐标或相机坐标。然而,在Overlay模式下,这一步骤不是必需的,因为UI元素的位置和世界坐标直接使用屏幕坐标,与鼠标坐标系统一致。

应用示例

跟随鼠标的UI元素:在游戏中,如果需要一个UI元素(如工具提示或信息框)跟随鼠标移动,可以直接将Input.mousePosition赋值给该UI元素的transform.position。这样,UI元素将在每一帧中更新位置,精确地跟随鼠标。

甚至由于基于世界坐标系,你可以不考虑父元素。

void Update() {
    myUI.transform.position = Input.mousePosition;
}

2024-9-12

UGUI使用layout group的组件,其子类会根据其父类的height和所有子类的height结合起来平均分配间隙,如果希望子类自己不要有空隙,父类height设置为0即可

参考提问:Trying to set spacing for verticalLayoutGroup - Unity Engine - Unity Discussions

2024-9-29

当试图在单例对象的方法本身里面调用自己的变量的时候,不要试图在改变instance的引用后直接调用,因为改变引用后,当前方法所在的instance已经失去引用了。

如:

public void Load() {
    bool load = SaveLoad.Instance.Load("SettingSaveData", out SettingSaveData loadData);
    if (load) {
        Instance = loadData;
        Screen.SetResolution(Width, Height,
            IsFullScreen);//全部是默认值,因为这时候的instance并不是新的引用地址
    }
}

2024-10-11

又犯蠢了来记一记

和9-29类似的问题:引用变量修改里面的元素所有相同地址对象一同修改,但是如果对一个引用直接赋值一个新的引用地址,等于切断了它和其他引用对象的联系。

2024-12-18

UGUI不支持Content Size Filter的父子类嵌套(隔了一层子类是允许的),但是有时候我们希望父类和子类都是长度或者宽度随着内部元素动态变化的。

所以,替代方案是

1.父节点放Horizontal Layout和Content Size Filter
2.勾选父节点control child size

这样可以间接实现类似Content Size Filter的效果。

参考文献:【Unity】 Text 文字的宽高自适应_parent has a type of layout group component. a chi-CSDN博客

UGUI嵌套布局组件的Control Child Size应用技巧 - qianxun0975 - 博客园

2025-3-13

活见鬼了,搞装修格子系统的时候发现射线检测死活不生效,后面发现是collider Unity不及时刷新,更新位置后手动开关一下obj即可