unity ugui缩放 移动

2016年09月28日 16:20 0 点赞 0 评论 更新于 2017-05-09 20:51
unity ugui缩放 移动

本文旨在记录本人在工作中遇到的 UGUI 相关问题,以及对应的解决方案。

1. UGUI 基础参数介绍

在 UGUI 中,RectTransform 组件起着关键作用,以下是该组件的一些重要参数:

  • m_Rect.localPosition:用于表示控件相对于父对象的本地位置。
  • m_Rect.rect:该属性为只读,m_Rect.rect.widthm_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

PositionlocalPosition 的值不同,且在测试范围内未发现明显规律。Position 变量几乎无法用于脚本的各种计算。

4.2 pivot 对 Position 和 localPosition 的影响

在控件位置不变的情况下,修改 pivot 会使 PositionlocalPosition 发生变化。并且,在 Unity 面板里修改 pivot 和在脚本里修改 pivot 表现出的行为不一致:

  • 在 Unity 面板里修改:UGUI 控件在屏幕上的位置不变,但 PositionlocalPosition 会改变。
  • 在脚本里修改:PositionlocalPosition 不变,但 UGUI 控件在屏幕上的位置会改变。

4.3 rect 属性

rect 为只读属性,其下面的所有数据都无法修改。rectwidthheight 属性记录了 UGUI 控件的像素大小(缩放是否参与影响暂未确定)。

4.4 sizeDelta 修改控件大小

可以通过 sizeDelta 修改 UGUI 控件的大小,但该变量与 UGUI 自身的对齐方式有关。若未理解对齐方式与 sizeDelta 的关系,简单地将 sizeDelta 改成 (100.0f, 100.0f),控件最终显示的大小可能并非 100×100。

5. 具体操作实现

5.1 缩放操作

需求是将 B 镶嵌到 A 的 Slot 里,Slot 有背景图,初始图片较小,镶嵌物大小不一,镶嵌完成后背景图需要缩放。

初始方案

最初的思路是,在 scale 都为 1 的状态下,rectwidthheight 属性就是控件的大小,所以直接将 B 的大小赋值给 Slot,即修改 sizeDeltawidthheight 的值。当所有 Slot 的锚点都在左上角时,该方案可行。但随着出现各种随父控件大小变化以及锚点左中右对齐的 Slot 后,此方案失效,将 B 的 widthheight 直接赋值给 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 移动操作

初始方案

最初,由于锚点都在左上角,移动操作很简单,直接在面板里修改 PosXPosY 即可。但 PosXPosYpivot 影响,输出查看坐标数据后发现,LocalPosition 变量的值是 PosXPosY 里的数据,因此采用 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. 总结

通过 sizeDeltalocalPosition 可以跳过复杂的 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 相关内容 进一步学习。

作者信息

孟子菇凉

孟子菇凉

共发布了 1189 篇文章