1. 为什么一个动画库的API值得单独拎出来聊做Unity客户端开发的人几乎都绕不开补间动画这件事。UI弹窗要缩放、按钮要回弹、血条要平滑过渡、道具要飘动、相机要跟随这些需求如果全部手写协程或者Update里插值代码很快就会变成一锅粥。LeanTween就是在这个场景下被大量使用的轻量级补间动画库它的定位很明确用极少的代码量把从A状态平滑过渡到B状态这件事做到极致。我最早接触它是因为一个卡牌类项目UI动效需求密集到离谱用原生协程写缩放加淡入加位移的组合动画一个弹窗就要写几十行改一个缓动曲线要翻半天代码。换成LeanTween之后同样的效果三五行就搞定而且链式调用读起来非常直观。但问题也随之而来——它的API数量不少命名风格又高度相似move、moveLocal、moveSpline、value、alpha、scale、rotate加上各种setEase、setDelay、setLoop、setOnComplete新手很容易在我到底该用哪个这个问题上卡住。这篇内容就是把我这几年在项目里反复用到的LeanTween常用API做一次系统梳理。不是照搬官方文档的罗列而是按实际开发中你会遇到什么需求来组织把每个API的适用场景、参数含义、容易踩的坑讲清楚。适合已经上手Unity、正在做UI动效或者游戏表现层开发的人参考也适合刚接触LeanTween、被它那一长串方法名搞晕的人当作速查手册。需要说明的是LeanTween的版本迭代中部分API有过调整下面讲的内容以当前主流稳定版本的常见用法为准如果你用的是很老的版本个别方法名可能有差异以实际工程里的智能提示为准。2. 位移类APImove、moveLocal与moveSpline的取舍位移是补间动画里用得最多的操作LeanTween围绕位移提供了好几个方法名字看着像实际行为差别不小。选错了轻则动画方向不对重则物体直接飞到屏幕外所以这块必须先讲透。2.1 move与moveLocal世界坐标和本地坐标的分水岭LeanTween.move(gameObject, targetPosition, duration)是最基础的一个它操作的是物体的世界坐标。也就是说不管这个物体挂在哪个父节点下、父节点有没有旋转缩放它最终都会移动到你把targetPosition填进去的那个世界位置。LeanTween.moveLocal(gameObject, targetLocalPosition, duration)操作的是本地坐标也就是相对于父节点的位置。这个区别在UI开发里尤其关键。UGUI的节点都是嵌套的一个按钮可能挂在三层父节点下面如果你用move去设置一个世界坐标父节点一移动按钮的实际表现就会和你预期的不一样而用moveLocal你填的就是它在父节点坐标系里的位置父节点怎么动它跟着动逻辑更符合UI布局的直觉。我踩过的一个典型坑做一个列表项滑入效果用move设置了目标世界坐标结果列表容器本身有个滚动偏移滑入的终点全歪了。改成moveLocal之后直接按本地坐标算问题当场消失。所以我的经验是——UI节点优先用moveLocal3D场景里的独立物体用move除非你明确知道自己要的就是世界坐标。这两个方法都支持传入Vector3作为目标位置也支持传入一个返回Vector3的委托后者适合目标位置在动画开始前才能确定的场景。duration是动画时长单位秒。2.2 moveSpline让物体沿着路径走LeanTween.moveSpline是位移API里比较特别的一个它让物体沿着一条样条曲线移动而不是直线。用法上你需要传入一组路径点Vector3[] path new Vector3[] { p0, p1, p2, p3 }; LeanTween.moveSpline(gameObject, path, 2.0f);它内部会用Catmull-Rom样条把这些点连成平滑曲线。这个API在做巡逻路线、飞行轨迹、抛物线效果时特别好用。但有几个细节要注意第一路径点的数量。至少需要两个点才能构成一条路径但如果只有两个点它退化成直线和move没区别。想要曲线效果至少给三个点。第二路径点的坐标系。moveSpline默认也是基于世界坐标的如果你的物体在移动的父节点下路径点要按世界坐标来算否则轨迹会偏。第三性能。样条计算比直线插值开销大如果同时有几十上百个物体在跑样条移动端要留意帧率。我一般会把同屏样条动画的数量控制在合理范围或者用对象池复用。还有一个容易忽略的点moveSpline的路径点数组在动画开始后如果被修改动画不会实时反映变化因为它在启动时就把路径数据缓存了。要动态改路径得先取消再重新调用。2.3 位移API的参数补充与常见误区位移类方法除了基本的位置和时长还经常配合setEase使用。默认的缓动是LeanTweenType.linear也就是匀速但实际项目里匀速运动看起来最假UI动效一般会用easeOutQuad、easeOutBack这类先快后慢或者带一点回弹的曲线。一个常见误区是认为duration越小动画越流畅。实际上duration决定的是动画总时长流畅度取决于帧率两者不是一回事。duration设得太小比如0.05秒动画会一闪而过用户根本看不清设得太大又显得拖沓。UI动效的经验值是0.15到0.4秒之间具体看元素大小和动效类型。另外move系列方法返回的是一个LTDescr对象你可以链式调用各种配置方法。这个返回值一定要接住后面取消动画、设置回调都靠它。很多人写完LeanTween.move(...)就不管了结果想中途停止的时候找不到句柄只能暴力销毁物体这就很被动。3. 缩放、旋转与透明度表现层三件套位移之外UI和物体表现最常用的就是缩放、旋转和透明度变化。这三个操作在LeanTween里各有对应的API用法上有共性也有各自的坑。3.1 scale与scaleX/Y/Z整体缩放和单轴缩放LeanTween.scale(gameObject, targetScale, duration)做的是整体缩放传入一个Vector3表示目标缩放值。比如从Vector3.one缩到Vector3.zero就是缩小消失反过来就是弹出。如果只想改某一个轴的缩放可以用scaleX、scaleY、scaleZ它们接受一个float值。做血条的时候我经常用scaleX因为血条通常只横向变化纵向保持不变用整体scale会把高度也改了还得额外补偿不如直接单轴操作干净。这里有个UGUI特有的坑RectTransform的scale和localScale。LeanTween的scale操作的是Transform的localScale对于UGUI节点来说就是RectTransform的localScale。如果你在代码里同时用anchoredPosition或者sizeDelta去改布局又用scale做动画两者会叠加视觉效果可能超出预期。我的做法是动画归动画、布局归布局尽量不混着改同一个属性。还有一个细节缩放动画的起点是物体当前的localScale不是固定的1。如果你想要从0弹到1的效果得先把localScale设成0再调用scale或者用setFrom指定起始值。setFrom这个配置方法很实用它能让动画从一个指定值开始而不是从当前值开始省去了手动初始化的步骤。3.2 rotate与rotateLocal旋转的方向问题LeanTween.rotate(gameObject, targetEulerAngles, duration)传入的是欧拉角做的是世界空间的旋转。rotateLocal则是本地空间旋转。和位移一样UI节点优先考虑rotateLocal。旋转最容易出问题的地方是角度插值的路径。比如从350度转到10度直觉上应该转20度但如果不做处理它可能会反向转340度看起来就是物体绕了一大圈。LeanTween内部对欧拉角插值做了一定处理但在角度跨越0/360边界时仍然可能出意外。我的经验是做旋转动画时尽量把角度控制在合理范围内或者用四元数的方式思考避免大角度跨越。另外rotate还有一个重载是绕某个轴旋转指定角度比如LeanTween.rotateAround(gameObject, axis, degrees, duration)这个在做转盘、指针、齿轮效果时很好用它明确指定了旋转轴比直接给欧拉角更可控。3.3 alpha与canvasGroup淡入淡出的两种做法透明度动画有两个层面。如果对象是普通的SpriteRenderer或者有Material的物体用LeanTween.alpha(gameObject, targetAlpha, duration)它改的是渲染器的颜色alpha。如果是UGUI节点更推荐用LeanTween.alphaCanvas(canvasGroup, targetAlpha, duration)操作的是CanvasGroup组件的alpha。为什么UI推荐用CanvasGroup因为直接改Image或者Text的color alpha只影响单个组件一个弹窗里有十几个子元素的话你得逐个改而且半透明叠加时容易出现颜色不均。CanvasGroup的alpha是作用于整个组的一次设置组内所有元素统一变化而且还能配合blocksRaycasts和interactable控制交互做弹窗淡入淡出时非常顺手。用CanvasGroup还有一个好处它不会触发UI的网格重建。改单个Graphic的color会触发重建频繁改的话有性能开销CanvasGroup的alpha变化是渲染层面的开销小得多。做频繁的淡入淡出这一点很关键。alpha和alphaCanvas的targetAlpha都是0到1的float。注意alpha动画结束后如果alpha是0物体虽然看不见但还在场景里射线检测可能还会命中。做弹窗关闭时记得在setOnComplete里把物体SetActive(false)或者把CanvasGroup的blocksRaycasts关掉。4. 通用配置方法让动画真正听话前面讲的都是做什么动画这一节讲怎么控制动画。LeanTween的配置方法都挂在LTDescr上通过链式调用设置它们决定了动画的节奏、循环、回调和生命周期。这部分是区分能用和用好的关键。4.1 setEase缓动曲线决定动画的手感setEase(LeanTweenType easeType)是使用频率最高的配置方法。缓动曲线决定了动画在时间轴上的速度分布同样的位移和时长换一条曲线观感完全不同。LeanTween内置了相当丰富的缓动类型常用的几类线性类linear匀速机械感强一般只用于需要严格匀速的场景。二次缓动easeInQuad、easeOutQuad、easeInOutQuad最常用的通用曲线easeOutQuad适合入场easeInQuad适合退场。三次缓动easeInCubic、easeOutCubic、easeInOutCubic比二次更猛适合需要明显加速感的场景。回弹类easeOutBack、easeInBack带一点超出再回弹的效果做按钮点击、弹窗弹出特别有活力。弹性类easeOutElastic、easeOutBounce弹性十足适合强调性的动效但用多了会显得浮夸。选曲线的经验入场动画用easeOut系列先快后慢符合物体减速停下的直觉退场动画用easeIn系列先慢后快符合加速离开的直觉需要强调的用back或elastic需要平稳的用quad或cubic的inOut。除了预设类型setEase还支持传入AnimationCurve这给了你完全自定义的能力。项目里如果有统一的设计规范可以把曲线做成配置所有动画从配置读取保证风格一致。4.2 setDelay、setLoop与setRepeat时间控制setDelay(float delay)让动画延迟指定秒数后开始。做序列动画时给不同元素设置递增的delay就能做出错落有致的入场效果比手动写协程等待优雅得多。setLoop(int count)设置循环次数setLoopClamp是循环到指定次数后停止setLoopPingPong是来回循环正放一遍反放一遍。做呼吸灯、心跳、待机浮动这类循环动效时setLoopPingPong特别合适因为它自动处理了往返不用你手动写两段动画。setRepeat(int count)是重复播放和loop的区别在于repeat是每次从头开始loop在pingpong模式下会反向。实际用的时候循环动画记得给一个合理的次数或者用setLoopClamp配合条件判断否则无限循环的动画在物体销毁时如果没取消可能引发空引用。4.3 setOnComplete、setOnStart与setOnUpdate回调的时机setOnComplete(System.Action callback)在动画结束时调用这是最常用的回调。做序列动画时前一段的onComplete里启动下一段就能串起复杂的动画流程。setOnStart在动画开始时调用适合做动画前的准备工作比如激活物体、初始化状态。setOnUpdate(System.Actionfloat callback)每帧调用参数是0到1的进度值。这个在需要根据动画进度同步其他属性时很有用比如动画移动的同时更新一个进度条或者根据进度播放音效。回调里最容易踩的坑是闭包捕获变量。在循环里给多个物体设置onComplete如果回调里用了循环变量由于闭包的特性所有回调看到的可能是同一个最终值。解决办法是在循环内用一个局部变量拷贝当前值或者用参数化的方式传递。这个坑和LeanTween本身无关是C#闭包的通用问题但在批量设置动画回调时特别容易中招。4.4 setIgnoreTimeScale暂停时的动画处理setIgnoreTimeScale(bool ignore)决定动画是否受Time.timeScale影响。游戏暂停时timeScale设为0普通动画会停住但如果你希望某些UI动画比如暂停菜单的弹出在暂停状态下仍然播放就要设setIgnoreTimeScale(true)。这个配置在做暂停功能时是刚需。我见过不少项目暂停后菜单弹不出来就是因为菜单动画没忽略时间缩放timeScale为0导致动画永远停在第一帧。反过来游戏内的角色动画一般不应该忽略时间缩放否则暂停时角色还在动就穿帮了。5. 取消、暂停与生命周期管理动画写出来容易管好难。一个场景里同时跑几十个动画切换界面时如果不清理轻则动画错乱重则内存泄漏和空引用。这一节讲LeanTween的生命周期管理。5.1 cancel与cancelAll精准和粗暴的两种清理每个动画方法返回的LTDescr对象上都有cancel()方法调用它就能取消这一个动画。这是精准清理适合你知道要停哪个动画的场景。LeanTween.cancel(gameObject)取消某个物体上的所有动画LeanTween.cancelAll()取消场景里所有LeanTween动画。切换界面时我通常会在界面的OnDestroy或者关闭逻辑里调用LeanTween.cancel(gameObject)把这个界面相关的动画全部清掉避免残留的动画在物体销毁后还在跑导致空引用报错。这里有个非常关键的坑动画的目标物体被销毁后动画不会自动停止。LeanTween内部持有物体的引用如果物体Destroy了但动画还在跑下一帧访问Transform就会抛异常。所以销毁物体前务必先cancel它身上的动画。我一般的做法是封装一个扩展方法在销毁前统一清理养成习惯。5.2 pause与resume临时挂起动画LeanTween.pause(gameObject)暂停物体上的动画LeanTween.resume(gameObject)恢复。和cancel不同pause保留了动画状态恢复后从暂停处继续。这个在做打开背包时暂停场景动画这类需求时有用。但要注意pause和timeScale是两套机制pause是LeanTween层面的timeScale是引擎层面的两者可以叠加。如果timeScale为0即使resume了动画也不会动。5.3 动画ID与批量管理LeanTween支持给动画设置唯一IDLeanTween.move(...).setId(myId)之后可以用LeanTween.cancel(myId)按ID取消。这在需要按逻辑分组管理动画时很有用比如所有属于某个角色的动画共用一个ID前缀角色离场时一次性取消。不过ID是int类型要自己保证唯一性用不好反而容易冲突。我的建议是简单的场景用物体引用取消就够了只有在确实需要跨物体分组管理时才用ID并且做好ID的分配规划。6. 实战中那些文档不会告诉你的细节前面按API类别讲了一遍这一节专门讲实战经验都是踩过坑之后总结出来的文档里基本不会写。6.1 动画叠加与属性冲突同一个物体上同时跑两个改同一属性的动画结果是不确定的。比如一个move动画还没结束你又启动一个move两个动画会互相抢控制权物体可能抖动或者瞬移。解决办法是要么先cancel再启动新的要么用setOnComplete串行执行。改不同属性的动画可以并行比如一边move一边alpha这没问题。但要注意如果两个动画都用了setEase且曲线不同视觉上可能不协调最好统一风格。6.2 对象池与动画的配合用对象池复用物体时从池里取出的物体可能还残留着上次的动画状态。比如上次是缩小到0消失的这次取出来scale还是0直接显示就看不见了。所以从池里取出时要么先cancel所有动画并重置Transform要么在动画开始时用setFrom明确指定起始值。我倾向于在对象池的取出回调里统一重置这样最保险。6.3 移动端的性能考量LeanTween本身很轻量但动画数量多了照样吃性能。移动端上同屏活跃动画建议控制在一定数量内尤其是样条动画和带回调的复杂动画。另外频繁创建和销毁LTDescr对象会产生GC压力能复用动画配置的尽量复用或者用对象池管理动画目标。还有一个细节UI动画尽量用CanvasGroup而不是逐个改Graphic的color前面提过这里再强调一次因为它对性能的影响在移动端很明显。6.4 编辑器里预览动画LeanTween的动画在编辑器非运行状态下不会播放调试时只能运行起来看。如果要做精细调整可以写一个简单的编辑器脚本在OnValidate或者自定义按钮里触发一次动画预览。这个投入在动效密集的项目里很值得能省下大量反复运行的时间。6.5 版本差异与API兼容LeanTween不同版本之间个别API的名字和行为有变化。比如早期版本某些方法的参数顺序和现在不同升级时要注意。我的做法是项目锁定一个稳定版本后不轻易升级升级前先在测试工程里跑一遍所有用到的API确认行为一致再合并。工程里如果用了反射或者字符串调用LeanTween方法升级时更要小心。7. 一套可复用的动画封装思路讲了这么多API最后分享一个我在项目里常用的封装思路把常用动效做成语义化的方法业务代码调用时不用关心底层是move还是scale。核心思路是建一个静态工具类把弹窗弹出弹窗关闭按钮点击反馈列表项入场这些高频动效封装成方法内部用LeanTween实现参数只暴露时长和回调。这样业务层写起来就是一行调用动效风格也统一。比如弹窗弹出内部就是CanvasGroup的alpha从0到1配合scale从0.8到1用easeOutBack时长0.25秒。弹窗关闭反过来用easeInBack时长0.2秒onComplete里SetActive(false)。这些参数集中在一个配置类里改风格只改一处。封装的时候要注意工具类方法要返回LTDescr或者一个取消句柄让调用方能取消。另外所有方法内部都要处理物体为null、CanvasGroup不存在等边界情况避免业务层传错参数导致报错。这套封装用下来团队里新人也能快速做出风格一致的动效不用每个人都去研究缓动曲线。当然封装不是万能的遇到特殊动效还是得直接调LeanTween的API封装只是覆盖高频场景。我个人在实际项目里的体会是LeanTween的价值不在于它有多少API而在于它让你能用最少的代码表达动画意图。把常用API吃透再配合合理的封装和生命周期管理UI动效这块基本就不会成为项目的痛点。真正要花心思的是动效的节奏和曲线选择那是设计层面的功夫API只是工具。
阅读完成 · 觉得有帮助?