unity ugui缩放 移动
本文旨在记录本人在工作中遇到的 UGUI 相关问题,以及对应的解决方案。
1. UGUI 基础参数介绍
在 UGUI 中,RectTransform
组件起着关键作用,以下是该组件的一些重要参数:
m_Rect.localPosition
:用于表示控件相对于父对象的本地位置。m_Rect.rect
:该属性为只读,m_Rect.rect.width
和m_Rect.rect.height
记录了 UGUI 控件的像素大小(缩放是否参与影响暂未确定)。m_Rect.pivot
:控件的轴心点,其值范围为 0 到 1,用于确定控件的对齐方式。m_Rect.sizeDelta
:可用于修改 UGUI 控件的大小,但该变量与 UGUI 自身的对齐方式(锚点位置和方式)有关。
2. UGUI 排版功能评价
初上手 UGUI 的排版功能时,会觉得它还不错。然而,与 Qt 等软件相比,UGUI 的对齐功能较为落后。若持续使用其排版功能,会发现其逻辑难以理解。
本文不讨论 UGUI 工作的原理及各个参数的详细影响,重点聚焦于以下两个问题:
- 如何将 UGUI 控件调整到指定大小。
- 如何将 UGUI 控件移动到指定位置。
3. RectTransform 组件与操作目标
上述两个问题的解决方案都与 RectTransform
组件相关。该组件的变量虽不多,但相互关联紧密。若未理解 UGUI 的设计原理,很难随心所欲地操作 UGUI 控件,这里的随心所欲操作指的是在移动或缩放 UGUI 控件时,能直接修改变量为期望的值,或传入参数达到期望效果,例如将控件移动到与另一个控件相同的位置(对齐)、使 A 控件的大小与 B 控件一致,或让控件跟随鼠标移动。
4. 基础内容说明
4.1 Position 和 localPosition
Position
和 localPosition
的值不同,且在测试范围内未发现明显规律。Position
变量几乎无法用于脚本的各种计算。
4.2 pivot 对 Position 和 localPosition 的影响
在控件位置不变的情况下,修改 pivot
会使 Position
和 localPosition
发生变化。并且,在 Unity 面板里修改 pivot
和在脚本里修改 pivot
表现出的行为不一致:
- 在 Unity 面板里修改:UGUI 控件在屏幕上的位置不变,但
Position
和localPosition
会改变。 - 在脚本里修改:
Position
和localPosition
不变,但 UGUI 控件在屏幕上的位置会改变。
4.3 rect 属性
rect
为只读属性,其下面的所有数据都无法修改。rect
的 width
和 height
属性记录了 UGUI 控件的像素大小(缩放是否参与影响暂未确定)。
4.4 sizeDelta 修改控件大小
可以通过 sizeDelta
修改 UGUI 控件的大小,但该变量与 UGUI 自身的对齐方式有关。若未理解对齐方式与 sizeDelta
的关系,简单地将 sizeDelta
改成 (100.0f, 100.0f)
,控件最终显示的大小可能并非 100×100。
5. 具体操作实现
5.1 缩放操作
需求是将 B 镶嵌到 A 的 Slot 里,Slot 有背景图,初始图片较小,镶嵌物大小不一,镶嵌完成后背景图需要缩放。
初始方案
最初的思路是,在 scale
都为 1 的状态下,rect
的 width
和 height
属性就是控件的大小,所以直接将 B 的大小赋值给 Slot,即修改 sizeDelta
为 width
和 height
的值。当所有 Slot 的锚点都在左上角时,该方案可行。但随着出现各种随父控件大小变化以及锚点左中右对齐的 Slot 后,此方案失效,将 B 的 width
和 height
直接赋值给 Slot 会出现奇怪的大小。
问题分析
经过调试发现,sizeDelta
的值受 anchor
变量和 offsetmax/min
这些变量的影响。虽然 sizeDelta
的值难以理解,但 sizeDelta
的增量是正确的。例如,将 sizeDelta
从 100×100 改为 200×200 时,虽然无法确定具体大小,但可以从输出信息中看到 sizeDelta
的增量为 100。
改进方案
将方案改为 sizeDelta
加上增量,具体代码如下:
RectTransform mNewNodeRect = newNode.GetComponent<RectTransform>();
RectTransform mSlotRect = mSlot.GetComponent<RectTransform>();
float mX = mNewNodeRect.rect.width - mSlotRect.rect.width;
float mY = mNewNodeRect.rect.height - mSlotRect.rect.height;
mSlotRect.sizeDelta = new Vector2(mSlotRect.sizeDelta.x + mX, mSlotRect.sizeDelta.y + mY);
通过这种方式,无需了解 UGUI 对齐相关的内容,即可实现准确的缩放。
5.2 移动操作
初始方案
最初,由于锚点都在左上角,移动操作很简单,直接在面板里修改 PosX
和 PosY
即可。但 PosX
和 PosY
受 pivot
影响,输出查看坐标数据后发现,LocalPosition
变量的值是 PosX
和 PosY
里的数据,因此采用 LocalPosition
。镶嵌方案是先将 Slot 调整到 B 的大小,然后将 Slot 的位置赋值给 B。在保证界面制作时锚点和 pivot
全部相同的情况下,该方案能正常使用一段时间。
问题分析
随着不同锚点和 pivot
的加入,原本直接赋值坐标的方案失效。测试发现,UI 控件坐标以 pivot
点为原点,若将两个不同对齐方式的 UGUI 控件设置为相同的 LocalPosition
(父控件相同的前提下),对齐的点是 pivot
点。
改进方案
需求是让两个控件的图片左上对齐,由于 pivot
是 0 到 1 的值,width * pivot.x
表示左边到 pivot
的距离。具体代码如下:
Vector3 mLeftTop = m_Rect.localPosition;
float mLeftOffset = m_Rect.rect.width * m_Rect.pivot.x;
float mTopOffset = m_Rect.rect.height * m_Rect.pivot.y;
mLeftTop.x -= mLeftOffset;
mLeftTop.y -= mTopOffset;
RectTransform mInsert = m_Insert.GetComponent<RectTransform>();
mLeftOffset = mInsert.rect.width * mInsert.pivot.x;
mTopOffset = mInsert.rect.height * mInsert.pivot.y;
Vector3 mfinalyPos = mLeftTop + new Vector3(mLeftOffset, mTopOffset, 0.0f);
mInsert.localPosition = mfinalyPos;
原理是先计算出 Slot 的左上角的点,然后将 B 设置到该点,再让 B 加上从左上点到 pivot
点的差值,从而实现两图以左上角对齐(其他对齐方式可自行推演)。
6. 总结
通过 sizeDelta
和 localPosition
可以跳过复杂的 UGUI 锚定,实现正确的对齐和缩放。
7. 个人感想
在做项目时,我们往往习惯按照自己的思维和理解来操作。若符合自己的思维,就觉得好用;不符合,就会抱怨设计不合理。这种观点不可取,既不尊重他人的劳动成果,学习时还会带有抵触情绪。虽然知道应该学习并满足 Unity 的设计要求,但 UGUI 的设计确实不太友好,实现常见的移动和缩放操作都需要先研究 UI 系统的设计,最终效果既不方便使用也不方便理解,与其他 UI 系统(如 Qt)相比,不够直观。
8. 补充说明
以上方案是在 A 是父对象,Slot 和 B 同级目录下,scale
为 1 的情况下进行的,不同目录情况可根据文中线索自行推导,且未测试缩放系数参与的情况。
9. bug 修正
pivot
变量用于确定控件自身的对齐方式。例如,将坐标填写为 0,0,0 时,控件会用 pivot
点去对齐该坐标。由于 pivot
以左下角为 0,0,当 pivot
为 0,0 时,控件会以左下角为对齐点对齐 0,0,0 坐标。
获取到 A 的坐标后,实际上获取的是 A 的 pivot
点,需要进行修正以满足需求。之前代码中计算的是左下角的坐标,而非左上角。不过,由于镶嵌背景和镶嵌物大小相同,左下对齐和左上对齐效果无差异。对于不等大的坐标排列,可自行计算高度差并进行相应调整。
通过以上方法,UGUI 的缩放和移动问题就得到了解决。希望这些内容能帮助大家跳过复杂的 UGUI 排版,实现简单的移动和缩放操作。你可以参考 Taikr 相关内容 进一步学习。