本文仅仅记录自己在工作中踩到的ugui的坑。并讲述如何填的坑。

  • RectTransform m_Rect
  • m_Rect.localPosition
  • m_Rect.rect  (m_Rect.rect.width  m_Rect.rect.height) 
  • m_Rect.pivot
  • m_Rect.sizeDelta

关于ugui的排版方面,刚上手的时候,觉得:哎哟!不错,这个刁。

但是如果你使用过qt等软件,其实ugui的对齐功能还是很落后的。

如果你继续使用这个排版功能,你就会发现:什么啊这是,什么逻辑啊,完全没懂啊。

关于ugui工作的原理以及各个参数不在本文讨论范围之内。什么这个参数改一下会如何,原点等,一概不研究,只说两件事:

  1. 如何修改ugui控件到我指定的大小
  2. 如何移动ugui到指定位置上去

关于ugui的以上两点,都在RectTransform这个组件里。而这个组件的变量虽然并不很多,但是互相关联非常精密。如果没有弄懂ugui是如何设计的,几乎没办法随心所欲的工作。

这里说的随心所欲的工作指的是:当我想要移动或者缩放ugui控件的时候,能够直接修改变量成我期望的值,或者传入参数就能达到我期望的效果。

比方说。我期望控件移动到跟另外一个控件相同的位置上去(对齐)。或者我希望A控件改变大小和B一样。又或者跟随鼠标移动。

以上三个情况,如果你摆弄过ugui,其实就很容易暴露RectTransform里面的问题。

以下,用我的工作作为讲解。告诉你如何通过简单的技巧,让你跳过理解http://www.taikr.comugui排版直接实现最简单的移动和缩放。让排版功能化繁为简,返璞归真

先说一些基本的内容。

  1. Position和localPosition值不一样,而且没有找出规律,在我测试到的范围内,position这个变量几乎不能用于脚本的各种计算。
  2. ugui在控件位置不发生变化的情况下,修改pivot,Position和localPosition会变。
  3. pivot这个变量,在unity面板里修改和在脚本里修改表现出的行为不一致。
    1. 在unity面板里修改,表现出的行为是ugui空间在屏幕上的位置没有发生变化,而Position和localPosition有改变;
    2. 在脚本里修改pivot,Position和localPosition不会改变,而ugui空间在屏幕上的位置会改变
  4. rect只读,整个结构下面的所有数据仅仅是只读,没找到方法修改。但是rect的widht和height属性记录的是ugui控件的像素大小(缩放是否参与影响暂时还没进展到这步)
  5. 可以通过sizeDelta修改ugui控件大小。但是这个变量和ugui自身的对齐方式有关(锚点位置和方式),如果你没有弄懂对齐方式跟sizeDelta的关系,只是简单的将sizeDelta改成(100.0f,100.0f)出现的大小可能不会是100×100.

有了以上基本内容,就可以开始讲解了。

我要做一个镶嵌。将B镶嵌到A的Slot里去。Slot有背景图,图片初始很小,镶嵌物大小不一,所以当镶嵌完成,背景图需要缩放。B需要修改坐标和A的Slot相同。

先说缩放。

会影响缩放的,主要是anchor参数。这个参数在不同值的时候,对缩放的影响是不一样的。因为Slot有上有下,有些靠左,有些靠右,有些居中,所以anchor参数各式各样。

思路其实很简单。因为在scanl都为1的状态下(别的状态还没测试),rect的widht和height属性就是控件的大小。所以将B的大小直接给Slot即可。

于是将sizeDelta修改为widht和height的值。完成。

最开始的时候所有slot的锚点都是在左上角,该方案还行。可是后面出现了各种随父控件大小变化以及锚点左中右对齐的slot之后,这个方案就失效了,将B的widht和height直接给slot,出现了各种奇怪的大小。

这才发现收anchor变量和offsetmax/min这些变量影响。

调试了很久,输出sizeDelta信息查看之后才发现,虽然sizeDelta的值很难理解,但是sizeDelta的增量却是正确的。比方说,我将sizeDelta改成100×100和200×200大小我是确定不了。但是从100到200,sizeDelta的输出信息可以看到增量了100。

于是逐将上面的方案改为:

sizeDelta+增量即可。

代码片段

1         RectTransform mNewNodeRect = newNode.GetComponent<RectTransform>();
2         RectTransform mSlotRect = mSlot.GetComponent<RectTransform>();
3         float mX = mNewNodeRect.rect.width - mSlotRect.rect.width;
4         float mY = mNewNodeRect.rect.height - mSlotRect.rect.height;
5         mSlotRect.sizeDelta = new Vector2(mSlotRect.sizeDelta.x + mX, mSlotRect.sizeDelta.y + mY);

至此,缩放搞定。你不需要知道任何ugui对齐相关的东西,就可以实现准确的缩放。

然后是移动。

最开始做的时候,因为锚点全都在左上角。所以移动的时候很简单,直接在面板里修改PosX和PosY就好。

但是因为PosX和Posy受pivot影响,所以输出查看坐标数据,发现Position和LocalPosition两个变量,LocalPosition变量的值是PosX和PosY里的数据,于是采用了LocalPosition。

于是镶嵌方案就是:

先将slot拉到B的大小,然后再将slot的位置给B。

因为从外部保证了界面制作的时候,锚点和pivot全部一样,所以这个方案成功用了一段时间。

后来,随着各种其他方式的锚点的加入,又有不同的pivot的加入。原本的坐标直接给值的方案就不行了。于是又测试。

这次测试发现,ui控件坐标,是以pivot点为原点。如果你将两个不同对齐方式的ugui控件设置成一样的LocalPosition(父控件相同的前提下),那么对齐的点,是pivot点。

因为我的slot背景图已经拉伸到和B一样大了。所以我需要的是让两个控件的图片左上对齐。

而pivot是一个0~1的值。0,就在最左上,1就在右下。正好是宽高。所以widht*pivot.x正好就是左边到pivot的距离。

于是修改方案就出来了。

 1         Vector3 mLeftTop = m_Rect.localPosition;
 2         float mLeftOffset = m_Rect.rect.width * m_Rect.pivot.x;
 3         float mTopOffset = m_Rect.rect.height * m_Rect.pivot.y;
 4         mLeftTop.x -= mLeftOffset;
 5         mLeftTop.y -= mTopOffset;
 6 
 7         RectTransform mInsert = m_Insert.GetComponent<RectTransform>();
 8         mLeftOffset = mInsert.rect.width * mInsert.pivot.x;
 9         mTopOffset = mInsert.rect.height * mInsert.pivot.y;
10         Vector3 mfinalyPos = mLeftTop + new Vector3(mLeftOffset, mTopOffset, 0.0f);
11         mInsert.localPosition = mfinalyPos;
原理就是,先计算出slot的左上角的点。然后再将B设置到这个点上,然后B再自己加上从左上点到pivot点的差值。就可以让两图以左上角对齐了(其他对齐方式自行推演)。

一个sizeDelta,一个localPosition就可以跳过复杂的ugui的锚定实现正确的对齐和缩放。

至此,ugui的缩放和移动就大功告成。

这里不得不谈下自己的感想:

我们做东西的时候,都是习惯先按照自己的思维和理解来做。如果符合自己的思维,则好用。不符合,“这tm什么设计,脑子有屎”。用这样的观点来做事,不太好。一是不尊重别人的劳动成果。二是学习东西始终带抵触情绪。

虽然我知道我应该去学习和满足unity的设计要求。但是这样的ugui设计,真的是脑子有屎。强大到只有程序才会懂的ui,你做鸟的编辑界面啊。直接给程序说明文档就好了。想做个ui上最常见的移动和缩放,我还得先研究一下ui系统怎么设计的。而且最终出来的效果是既不方便使用,也不方便理解,也赶不上别的ui系统(有用过qt做编程的就会懂得qt的编辑器和ui对齐方式多么直观)。我只想说:这tm什么设计,脑子有屎。

这个ugui,一点都不符合unity的风格。

特别补充一点,以上方案,是在A是父对象,slot和B同级目录下,scan为1的情况下进行的,不同目录情况,请自信根据文中线索推导。另外缩放系数参与的情况下未进行测试

在已经有以上知识内容的前提下,修正一个bug。

因为pivot这个变量是用于铆钉控件自身的对齐方式。举例:如果我把坐标填写成0.0.0点。空间将会用pivot的点去对。而pivot是以左下角为0.0,所以如果pivot为0.0的时候,你会看到控件以左下角为对齐点去对0.0.0点坐标。

提这个的原因是因为,获取到A的坐标之后,其实是获取的A的pivot点。需要通过修正来满足自己的需求。本文上面的

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;

这段修正代码,是在计算左下点的坐标,而不是左上。

提到这里我觉得就够了,坐标点算偏移不是难事,各位自己算下自己期望的坐标偏移就好。我也要自己检查下为什么之前用这个算法算出来竟然是期望的左上。

好吧,我查了下我自己的bug,我这个算法,其实是左下角对齐的方式。但是呢,因为我的镶嵌背景和镶嵌物等大,所以镶嵌上去之后,左下对齐和左上对齐就没有任何差别。

如果是不等大的坐标排列,可以自己计算一下高度差,或加或减一下就好了。