做Unity UI的,谁没被NGUI的pivot坑过一两次?我说个真事:有次改一个活动按钮的布局,我只是把pivot从Center换成了BottomRight,结果整个按钮直接从屏幕中间瞬移到了屏幕外,UI组同事当场以为我改错了坐标。后来定位半天,根子就在pivot上。NGUI作为老牌UI插件,pivot(枢轴/锚点)是UI布局里最基础也最容易被忽略的机制——它决定了一个UI元素“自己怎么站”,而它的好兄弟anchor决定这个元素“跟着谁走”。这篇文章我就专门把NGUI Pivot讲透:先分清它和anchor的区别,再看九个取值的几何行为,然后落到血条、聊天气泡、弹窗动画这些真实场景,最后聊动态修改pivot时遇到的坐标系陷阱,以及团队怎么定规范才能不让pivot成为玄学。
1. 先看一个翻车现场:pivot改一下,按钮飞了
1.1 事情是怎么发生的
背景是这样:一个128x128的按钮,pivot=Center,transform.position是(0,0,0),也就是按钮中心正好在原点。我为了让它和图片的右下角对齐,把pivot改成了BottomRight。结果NGUI执行了它最朴素的逻辑:pivot是原点,pivot变了,transform.position不变,于是按钮的右下角跑到了(0,0),整个按钮出现在(-128,-128)到(0,0)的区间里。如果这个按钮的父节点还有2倍缩放,视觉上偏移量直接翻倍,按钮直接出了屏幕。
这个现象放到现实里就很好理解:你一只手原本拎着行李箱正上方的提手,现在突然让你改拎箱子的右下角,箱子肯定朝另一侧甩出去。pivot就是那个“提手的位置”,UIWidget在绘制顶点时永远以pivot为基准,向上下左右按照宽高展开。改pivot而保持position不变,等于保住了“手的位置”,任箱子(widget的几何体)自己乱甩。
1.2 这种错位为什么难排查
大家debug时最容易忽略pivot,是因为它在属性面板里只是一个下拉框,改了不会报错、不会打警告、不增加新的DrawCall。位置没变、尺寸没变、深度没变、脚本没报错,好像是渲染结果自己“叛变”了。于是新手会把问题怀疑到图集、Shader、UIPanel裁剪上,绕一大圈回来才发现只是一个下拉框。
另一个原因是,很多人会把pivot和anchor当成同一个东西。“我锚定的是同一个点,怎么会跳?”这种话我在项目里听过不止一次。但实际上anchor和pivot是两个维度,混在一起想永远想不通。所以下一节专门把这两个概念掰开。
1.3 pivot与anchor:一个管自己,一个管爹妈
简单说:
- anchor(在NGUI 3.x里是UIRect内置的anchor,老版本是UIAnchor组件):决定这个UI元素相对于父容器、屏幕边缘或某个Target的参考位置。anchor负责计算localPosition,它管的是“这个元素待在屏幕/父面板的哪一侧”,比如贴底、贴顶、四边拉伸。它决定“跟谁走”。
- pivot(UIWidget.Pivot枚举):决定这个widget自身的坐标系原点在宽高矩形的哪个位置。它管的是“transform.position这个坐标值,落在widget自身身上的哪个点”。它决定“自己怎么站”。
用贴墙挂钩来类比:anchor是墙上钉的挂钩,pivot是相框背面挂到挂钩上的那个挂绳位置。挂钩决定相框挂在墙上的大区域,挂绳位置决定相框相对挂钩怎么摆。你可以换挂钩的位置来换区域,也可以换挂绳的位置来换姿态,两者可以组合出非常多的效果,但不要指望只调其中一个就能覆盖所有布局需求。
NGUI的布局组件也依赖pivot的语义。比如UIGrid、UITable在自动排列子物体时,会按照每个子物体的pivot去算格子起点。你一个列表项pivot是TopLeft,跟它pivot是Center,排列出来的位置会有半格偏差;如果你在运行期随意切换pivot,已经排好的列表会像多米诺骨牌一样错开。这一点经常被动态生成列表的项目踩到。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 九个pivot取值,本质是三个“把手”
2.1 pivot枚举对照表
NGUI把pivot定义成了UIWidget.Pivot枚举,一共九个点,正好是一个3x3网格。用“中心点为原点”的坐标系来理解最直观:
| NGUI枚举 | 几何位置 | 相对中心偏移权重 | 典型用途 |
|---|---|---|---|
| TopLeft | 左上角 | (-0.5, 0.5) | 左对齐列表项、顶栏图标 |
| Top | 上边中点 | (0, 0.5) | 顶部下滑提示条 |
| TopRight | 右上角 | (0.5, 0.5) | 红点、关闭按钮、右上角徽标 |
| Left | 左边中点 | (-0.5, 0) | 左侧抽屉、左对齐文本 |
| Center | 中心 | (0, 0) | 弹窗、按钮、大多数静态控件 |
| Right | 右边中点 | (0.5, 0) | 右侧抽屉、右对齐文本 |
| BottomLeft | 左下角 | (-0.5, -0.5) | 聊天气泡左下尾部定位 |
| Bottom | 下边中点 | (0, -0.5) | 头顶名字、血条填充、飘字 |
| BottomRight | 右下角 | (0.5, -0.5) | 右下角角标、气泡右下尾部 |
这个“相对中心偏移权重”是我为了方便推算写的:它指的是pivot点相对于widget中心的偏移,再乘上widget的宽高,就能得到位置偏移量。比如一个宽100、高50的widget,pivot从Center改成BottomLeft,几何体就会向左下移动(-50, -25, 0)像素。你在编辑器里手动把pivot从Center改成BottomLeft,看到UI往左下跳,就是这个偏移在起作用。
2.2 缩放和旋转时pivot决定“钉子在哪儿”
pivot不只是影响静态渲染,还决定缩放和旋转时哪个点保持不动。这个特性在做动画时特别关键。
TweenScale从(0,0,0)补间到(1,1,1),视觉上是从pivot点向外“生长”出来的。pivot=Center时,是标准中心开花;pivot=Top时,是整个物体从顶部往下展开,像拉开卷帘;pivot=BottomLeft时,从左下角向右上角长大,像游戏里“卡牌从手牌区翻出”的效果。
旋转也同理。pivot=Center是绕中心旋转,适合Logo、倒计时数字;pivot=Left是绕左边缘旋转,适合转盘表盘;pivot=Bottom是绕底边旋转,适合模拟“立起来的盾牌倒地”这类动画。做这些动画前,先想清楚我需要哪个点钉在原地,然后把pivot设成那个点,动画逻辑会简单很多。
反过来,如果你把pivot设错了,又要做连续动画,就会陷入“一边缩放一边不停改position找补”的泥潭。我在项目里见过有人为了做一个右上角出现的角标,pivot=Center,然后每帧用代码把位置往左上偏,最后缩放结束位置还差几个像素。后来改成pivot=TopRight,去掉所有补偿代码,动画一帧就对了。
2.3 拖拽和自动布局里的“手感差异”
NGUI的UIDragObject在计算拖拽偏移时,本质上会参考你按下时的事件坐标和物体pivot之间的距离。pivot=Center时,物体中心跟着鼠标/手指,经典“拎中心”手感;如果pivot设在某个角落,你按下之后物体会“躲在”手指旁边,拖起来总觉得有半截东西甩在外面,体验比较奇怪。所以可拖拽的物体我基本一律锁死pivot=Center。
在自动布局里,pivot同样会造成肉眼可见的差异。UIGrid横向排列时,将子物体pivot设为Left,它会把每个子物体的左边界当做cell起点,排出来非常紧凑;如果pivot=Center,cell起点按中心算,相邻物体之间会出现半格空隙。UI需要像素级对齐时,这种差异会被QA直接标成bug。
3. 实战里最常用的五个pivot姿势
3.1 血条与进度条:Left/Right的世界
血条最经典的实现是:底框一个、填充条一个,填充条叠加在底框内部。要让血条从右往左减少,正确做法是把填充条pivot设成Left,然后通过修改widget.width(或者localScale.x,但width更直观)来缩短填充条。pivot=Left意味着缩短宽度时,左边界不动,右边界往左收,正好是“从右边扣血”。
反过来,pivot=Right适合充能条从左往右增长,或者敌人血条从左侧扣血。如果你用pivot=Center去改width,宽度会向两端同时收缩,血条会从中间向两边“消失”,看起来像是被擦掉而不是被扣血,实战中没人想要这种效果。
这里有个容易忽略的细节:UISprite如果type选Filled,血条也可以直接用fillAmount,它和pivot无关,是基于sprite自身网格的归一化填充。但很多项目为了配合滑动条、遮罩、高光层,还是习惯用width方式。这时候pivot的选择就决定了你在代码里写的到底是fillBar.width = value还是fillBar.transform.localScale = new Vector3(value, 1, 1)。我建议统一用width,因为localScale会影响子物体文字、高光、边框的缩放,容易出现“血条越短,高光越细”这种奇怪效果。
3.2 聊天气泡和头顶冒字:Bottom系集体上位
聊天气泡通常有个指向说话人的小三角。很多新手会把气泡pivot设成Center,再手动偏移半个气泡宽高,让三角对准说话人。气泡尺寸一旦变化,这些偏移全部要重算,很容易漏。
更好的做法:把气泡根节点pivot设成BottomLeft或BottomRight。定位时只需要把气泡的pivot点对准说话人头顶附近的坐标,气泡会自动朝一个方向展开,不需要手动计算气泡宽高。比如三角在左下角的气泡,pivot=BottomLeft;三角在右下角,pivot=BottomRight。这个设计在动态生成聊天气泡时非常省心。
头顶名字、伤害飘字也类似。名字组件的pivot设成Bottom,把这个点对齐到角色头顶屏幕坐标,名字文本就在头顶上方往上生长。如果pivot=Center,同一Y坐标下名字会上下各占一半,上半部分会遮住角色头顶,视觉上好像名字“插”到了角色脑袋里。伤害数字向上飘的时候,pivot=Bottom会让数字有一种从头顶被“顶”出来的连续感,而pivot=Center会显得数字在原地拉升后瞬移。
3.3 弹窗与Toaster:Center是安全牌
弹窗类UI,尤其是带入场缩放的弹窗,pivot建议选Center。TweenScale从0到1,从中心向四周展开,这是主流游戏弹窗的标准观感。有人为了内容对齐方便,把弹窗根节点pivot设成Left,再做中心缩放,结果缩放时弹窗从左边“长”出来,而不是从中心出现,看起来极其别扭。
如果你既要中心缩放,又要左对齐内容,正确做法是分层:外层窗口挂pivot=Center负责弹窗的缩放和定位;内层放一个pivot=Left的容器负责内容的左对齐排布。不要把两个需求压在一个widget上,否则性能和效果都打折。
Toast提示从顶部滑下来,如果用pivot=Top + TweenPosition,它能贴着屏幕顶部下滑,滑动过程非常自然;如果用pivot=Center,它从屏幕中上方滑入,视觉上是“穿过”顶部边界进来的,总觉得哪里不对。这类小细节就是UI“手感”的来源。
3.4 红点徽标:TopRight的角标逻辑
未读红点、数字角标挂在按钮右上角,这是所有游戏UI都有的需求。正确姿势是:红点pivot=TopRight,anchor设置到父按钮的右上角。这样红点的右上角顶着按钮右上角,红点主体自然出现在外侧,按钮大小变化时红点会自动跟随,不需要额外代码去偏。
如果红点pivot=Center,你要手动偏移红点宽高的一半,按钮尺寸一变,偏移就失效。所以“自带偏移”是pivot的重要价值。数字角标里的UILabel同理:alignment=Right、pivot=TopRight,保证文字右对齐且矩形右上角顶在按钮角上。这样不管数字是“1”还是“99”,红点区域都能稳定扩展。
3.5 底部操作栏与多分辨率适配:anchor+pivot组合
多分辨率适配里,anchor和pivot要成对思考。底部操作栏需要永远贴住屏幕底边,标准组合是anchor=Bottom、pivot=Bottom。操作栏这个widget的底边正好对准屏幕底边,往上排列所有按钮。如果anchor=Bottom但pivot=Top,操作栏的顶部会顶在屏幕底边,整个操作栏跑到屏幕下方看不见。这种低级错误在切分辨率测试时最容易暴露。
顶部标题反过来,anchor=Top、pivot=Top。全屏自适应面板则是anchor四边都吸附到父面板边界,pivot=Center,这样拉伸时所有边缘保持一致,不会出现某一边溢出屏幕。理解这个组合逻辑后,大多数UI适配问题都能在设计阶段规避掉,而不是等测试机型反馈。
4. 动态修改pivot:坐标系陷阱与保位置公式
4.1 修改pivot位置跳变的本质
先说结论:NGUI改pivot时不会帮你保持视觉位置,跳变是机制而不是bug。因为UIWidget绘制顶点时永远以pivot为原点,按宽高向四周展开。pivot从Center改成BottomLeft,原点的语义变了,但transform.position没变,于是整个几何体向左下偏移(宽/2, 高/2)。如果你想在运行时动态改pivot,且保证UI位置不跳,必须自己补偿坐标。
这跟UGUI不一样。UGUI的RectTransform把anchor、pivot、sizeDelta、offsetMin/Max一起维护,你改pivot时,Unity会尽量保持anchoredPosition不变,视觉不容易跳。NGUI比较“原教旨”,pivot就是pivot,改了就动了,补偿得自己来。从NGUI迁到UGUI的团队,反而容易因为两边行为差异踩坑。
4.2 保视觉位置的通用代码逻辑
补偿思路其实很朴素:记录改动前pivot点在世界空间的位置,改动pivot后,把新的pivot点移回原来的位置。
下面是封装好的辅助方法,核心关键在正确使用widget.worldCorners。需要提醒的是,调用前先ForceUpdate,否则worldCorners可能是上一帧的旧数据:
csharp复制public static void SetPivotKeepWorld(UIWidget widget, UIWidget.Pivot newPivot)
{
if (widget == null || widget.pivot == newPivot) return;
widget.ForceUpdate();
Vector3 oldPivotPos = GetPivotWorldPosition(widget, widget.pivot);
widget.pivot = newPivot;
widget.ForceUpdate();
Vector3 newPivotPos = GetPivotWorldPosition(widget, widget.pivot);
Vector3 delta = oldPivotPos - newPivotPos;
widget.transform.position += delta;
widget.ForceUpdate();
}
static Vector3 GetPivotWorldPosition(UIWidget widget, UIWidget.Pivot pivot)
{
Vector3[] corners = widget.worldCorners;
// NGUI 的 worldCorners 顺序为:0=BottomLeft, 1=TopLeft, 2=TopRight, 3=BottomRight
switch (pivot)
{
case UIWidget.Pivot.TopLeft:
return corners[1];
case UIWidget.Pivot.Top:
return (corners[1] + corners[2]) * 0.5f;
case UIWidget.Pivot.TopRight:
return corners[2];
case UIWidget.Pivot.Left:
return (corners[0] + corners[1]) * 0.5f;
case UIWidget.Pivot.Center:
return (corners[0] + corners[2]) * 0.5f;
case UIWidget.Pivot.Right:
return (corners[2] + corners[3]) * 0.5f;
case UIWidget.Pivot.BottomLeft:
return corners[0];
case UIWidget.Pivot.Bottom:
return (corners[0] + corners[3]) * 0.5f;
case UIWidget.Pivot.BottomRight:
return corners[3];
}
return widget.transform.position;
}
这段代码用worldCorners而不是localPosition,好处是自动包含父节点缩放、旋转、anchor偏移的影响。如果你的项目NGUI版本没有ForceUpdate,可以改成widget.UpdateAnchors()之后再访问widget的几何数据,但总的原则一致:先刷新布局,再算偏移,最后改坐标并再次刷新。
4.3 和anchor叠加时,顺序比你想的重要
动态修改pivot还有一个进阶陷阱:如果widget挂了anchor,anchor系统会在刷新时重新计算localPosition,它根本不管你pivot补偿算得对不对。你上一帧刚把位置补回去,下一帧anchor一刷新,又给你按新pivot的几何关系拉走了。
我的建议是,运行期改pivot尽量绕开anchor。要么临时断anchor,改pivot、补偿坐标之后再把anchor加回来;要么把“切换pivot”替换成“切换不同pivot的预制体/子节点”,从根上避免冲突。大部分项目里,血条、列表项这类需要变pivot的东西,我都改成了固定pivot+调整width/height的方案,运行期一个pivot都不改,bug少很多。
顺带一提,如果你在Awake/Start里改pivot,要确保改完立刻ForceUpdate。否则同帧后续代码去读widget.width、worldCorners,拿到的还是旧pivot的数值,这种“看起来没改成功”的假象在动态创建UI的模块里特别常见。
4.4 这些操作千万别在Update里做
修改pivot会触发NGUI几何体重建,包括widget顶点、碰撞体、panel的绘制数据更新。一个两个还好,Update里几十上百个widget一起改,掉帧是必然的。如果只是想要“从某个角展开”的动态效果,优先用TweenScale、TweenWidth,而不是每帧改pivot。
更隐蔽的一个坑是,UIDragObject过程中改pivot,会让拖拽中心突变,被拖物体突然跳到鼠标的另一侧。我踩过一次之后就规定:可拖拽的widget,pivot在初始化后锁死Center,任何人不得在拖拽流程里改。
5. pivot的“连带效应”:文字对齐、碰撞体、Tween
5.1 UILabel的alignment和pivot不匹配的后果
UILabel的alignment控制文本在自身矩形里的排列方向,pivot控制这个矩形的原点在哪里。两者必须配套使用,否则会出现“字不见了”或者“文字偏到一边”的诡异现象。
举个实际例子:一个列表的文字,你希望它左对齐显示,label的alignment=Left,pivot也应该设成Left。如果pivot还是Center,文字会以物体位置为中心向两边排布,左对齐就失效;更糟的是,如果label宽度等于父节点可用宽度,文字会从中心向两边溢出,部分文字被裁掉。右对齐的倒计时文本同理,alignment=Right时pivot设Right,否则数字越少,看起来离右边界越远。
处理Label时还有一个隐藏点:UILabel如果开了自动宽高(比如ShrinkContent),宽高在运行期会变,这时pivot和alignment的关系更要固定好,否则每次文本变化,整个矩形都在动。我一般会在制作UI时就把Label的尺寸显式指定,并关闭不必要的自动宽高,保证pivot语义稳定。
5.2 BoxCollider跟着pivot走,点击区域才会准
NGUI的点击检测依赖BoxCollider,而BoxCollider默认按照widget的尺寸和pivot位置生成。pivot变了,widget边界变了,碰撞体的中心和大小也要跟着变,否则会出现“按钮看着在这里,点旁边才有反应”的错位。
NGUI在编辑器里给widget加UIButton时通常会自动把BoxCollider调整好,但在运行期动态改pivot后,碰撞体不会自动重算。这时候要么手动设置:
csharp复制BoxCollider box = widget.GetComponent<BoxCollider>();
if (box != null)
{
box.size = new Vector3(widget.width, widget.height, 0f);
switch (widget.pivot)
{
case UIWidget.Pivot.Center:
box.center = Vector3.zero;
break;
case UIWidget.Pivot.TopLeft:
box.center = new Vector3(widget.width * 0.5f, -widget.height * 0.5f, 0f);
break;
// 其他枚举按同一规律推导,pivot在哪个角,中心就往反方向偏移半个宽高
}
}
检查点击区域最快的方式是选中widget,展开BoxCollider看center和size,是不是和widget的几何范围对应。如果对应不上,点击区域就是歪的。
5.3 Tween动画靠pivot吃香
这部分前面已经提到,但值得系统整理一下常见的三个Tween和pivot的关系:
- TweenScale:scale从0到1,物体从pivot点向外生长。中心缩放=Center,从顶部展开=Top,从左下角展开=BottomLeft。
- TweenRotation:pivot就是旋转中心。想围绕哪个点转,就把pivot设成哪个点。
- TweenWidth/TweenHeight:专门改widget尺寸的Tween,pivot决定宽度/高度向哪端增长。pivot=Left时,width增大向右扩展,非常适合做“从左边拉出来的进度条”动画。
如果你做弹窗动画发现内部文字有两帧被压扁,通常是因为整层物体在TweenScale,子物体的文字、图标也跟着一起变形。规避做法是只缩放一个空壳层,子物体保持1倍scale;或者给子物体加一个反向scale的Tween,动画结束时的值对齐。这个和pivot没有直接关系,但常常和pivot选择混在一起背锅。
5.4 UIRect.ForceUpdate与pivot修改的先后关系
NGUI的UIRect是一切UIWidget的基类,anchor刷新、几何体更新都在这条链路上。修改pivot之后,我强烈建议显式调用一次widget.ForceUpdate(),让所有坐标、碰撞体、面板裁剪在同一帧内同步。否则同一帧其他脚本读取worldCorners、width、localSize时,拿到的可能是上个pivot状态的残留值。
还有一个容易忽略的点:改pivot之后,transform.hasChanged不一定会被NGUI标记为脏,某些缓存系统可能漏更新。你不能只依赖Unity的Transform系统,要主动调widget的更新接口。这类“布局脏标记”的问题在NGUI 3.x各个小版本里行为不完全一致,所以封装辅助函数时,把ForceUpdate这件事写死进去,是性价比最高的做法。
6. 团队规范与快速排查:别让pivot成为玄学
6.1 我推荐的pivot约定俗成表
项目做大了以后,UI错位问题多数不是技术难度,而是规范不统一。同一个按钮,程序觉得pivot应该Center,美术觉得应该TopLeft,改来改去就是一团乱麻。我在团队里推行过一张pivot约定表,效果很好:
| UI类型 | 推荐pivot | 配合anchor | 选型理由 |
|---|---|---|---|
| 全屏面板 | Center | 四边拉伸 | 自适应面板 |
| 弹窗/Modal | Center | 屏幕中心或四边拉伸 | 中心缩放动画 |
| 底部操作栏 | Bottom | Bottom | 贴底向上 |
| 顶部标题 | Top | Top | 贴顶向下 |
| 血条/进度条填充 | Left/Right | 固定一侧 | 单向增减 |
| 聊天气泡 | BottomLeft/BottomRight | 说话人坐标 | 尾部定位 |
| 头顶名字/伤害数字 | Bottom | 角色头顶坐标 | 向上生长 |
| 角标/红点 | TopRight | 父按钮右上角 | 自带偏移 |
这张表贴在团队wiki里,程序写代码时照着选,美术摆UI时也照着摆,能消灭大量“看着不对但说不清哪不对”的沟通成本。
6.2 遇到UI错位,按这个顺序排查
如果你接手一个乱糟糟的场景,别上来就怀疑图集和Shader,按下面这个顺序来:
- 检查anchor:父节点变了anchor没刷新?切分辨率时anchor是否丢失了Target?
- 检查pivot:Inspector里的Pivot是不是被脚本动态改过?改过就加断点查是谁改的。
- 检查depth:同屏UI的depth乱掉,会出现“看不见”或“被盖住”,表面看也像位置错。
- 检查anchor和pivot的组合:anchor=Right但pivot=Left,物体整体会偏出去一个身位,这是最容易被看走眼的组合。
- 检查Tween/SpringPosition:如果动画没停,任何坐标都是动态的,先停动画再定位。
- 打印widget.worldCorners:最直观,直接看四个角落在了屏幕哪个区间,再反推pivot和anchor。
这个流程我用了很多年,绝大多数UI错位十分钟内能定位。pivot的坑之所以防不胜防,就是因为它在第四步,大家都习惯先怀疑外部因素。
6.3 与UGUI pivot的换算:给迁移者的对照表
很多做NGUI的老项目都在往UGUI迁移。NGUI和UGUI的pivot概念基本一致,只是表现形式不同:NGUI用9个枚举值,UGUI用(0,0)到(1,1)的连续向量。迁移时对照关系如下:
| NGUI UIWidget.Pivot | UGUI RectTransform.pivot |
|---|---|
| BottomLeft | (0, 0) |
| Left | (0, 0.5) |
| TopLeft | (0, 1) |
| Top | (0.5, 1) |
| TopRight | (1, 1) |
| Right | (1, 0.5) |
| BottomRight | (1, 0) |
| Bottom | (0.5, 0) |
| Center | (0.5, 0.5) |
迁到UGUI后,大多数团队习惯把pivot设成0/0.5/1这些档位,逻辑上能和NGUI时代保持一致。UGUI虽然会自动计算很多偏移,但RectTransform的position语义依然基于pivot;不同的是它通过anchorMin/anchorMax、offsetMin/offsetMax把锚定和偏移统一维护了。所以迁移时别把“UI自适应”全部甩给UGUI,pivot选错一样会错位。
6.4 我的个人习惯与最后一个小技巧
我的项目规范总结起来就三条:可拖拽UI一律Center,贴边需求一律交给anchor,动态生成对象的pivot在实例化瞬间定死,之后不再动。这套规则帮我在多个项目里避开了大量“运行期pivot被改坏”的坑。
最后分享一个编辑器小技巧:把第4章的SetPivotKeepWorld封装成MenuItem工具脚本,放在Editor目录下,选中多个widget一键统一pivot并保持世界位置。这样就算UI美术手滑改错了pivot,也能在编辑器里一键恢复,不用重新拖位置。脚本代码就是把上一节的函数放到静态类里,加上[MenuItem("Tools/UI/Set Pivot And Keep World")]就行,实现原理完全一样。
pivot不是越复杂越好。一个UI体系里,90%的控件只需要Center、Left、Bottom、TopRight这四个值就够了。如果有一天你发现自己的代码里到处都在改pivot,那不是pivot功能不够强,而是你的UI结构没拆好。把pivot当成“布局的钉子”,一开始就把钉子位置钉对,比事后用代码找补要轻松得多。
