ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

游戏引擎架构深度解析:物理与动画系统的核心设计

游戏引擎架构深度解析:物理与动画系统的核心设计 游戏引擎架构深度解析三物理与动画系统本系列前两篇聊了引擎的整体框架和资源管理今天落到最贴近游戏手感的两大模块物理系统和动画系统。这两个系统在引擎架构里的位置很特殊——物理负责让物体合理地动动画负责让角色像样地动它们既是计算密集型的底层模块又要对上层玩法逻辑暴露友好的接口。很多项目做到一半发现手感不对、角色穿模、动作僵硬根源往往不是美术资源的问题而是物理与动画的架构设计出了问题。这篇文章把我这些年在这两个系统上的设计经验、踩坑记录和调优思路完整写出来希望能给正在做引擎底层或者准备深度定制引擎的开发者一些参考。1. 物理系统架构核心拆解1.1 物理引擎的整体模块划分与数据流物理引擎不是一个黑盒它内部有清晰的分工。以常见的物理引擎架构来看核心模块一般分为四块碰撞检测、刚体动力学、约束求解器、以及可选的软体/流体扩展模块。四者之间的数据流向大致是这样的游戏逻辑层设置物体的初始状态位置、速度、质量、碰撞形状物理引擎接管后先做碰撞检测找出所有可能接触的物体对再把这些接触点交给约束求解器计算冲量和摩擦力最后积分更新所有刚体的位置和速度把结果回调给游戏逻辑。这套流程看起来不复杂但架构设计上的关键决策藏在细节里。比如碰撞检测和约束求解之间是同步执行还是异步执行物理模拟使用固定时间步长还是可变时间步长这些选择直接决定了物理系统的稳定性、性能和可预测性。我见过不少团队上来就急着堆功能结果物理模块里逻辑混乱碰撞回调里改刚体速度、约束求解过程中直接改动位置、物理更新里执行游戏逻辑的AI计算。这样做短期内跑得通但一旦场景复杂起来Bug会非常难查。正确的做法是把物理引擎当作一个服务方游戏逻辑在固定步长里向物理引擎提交请求物理引擎计算完毕后回调通知而不是让游戏逻辑在物理更新中途强行介入。服务方模式下物理模块可以被单独测试、单独重构、甚至单独替换成不同的后端引擎上层完全感知不到。1.2 碰撞检测的Broad Phase与Narrow Phase碰撞检测是物理引擎里最耗时的部分之一所以架构上都采用先粗筛、后精算的两阶段策略。Broad Phase粗检测阶段的目标是快速排除掉那些肯定不相交的物体对不需要精度只需要速度。常用的算法包括Sweep and PruneSAP、动态AABB树Bounding Volume Hierarchy、以及网格哈希法Uniform Grid。SAP算法的思路很直接把物体的AABB包围盒投影到坐标轴上按坐标排序然后只检查坐标轴上有重叠的物体对。这个算法在物体分布相对均匀的场景下效率很高而且实现简单是中小型引擎的常用选择。动态AABB树则更适合大量物体的动态增删场景它不需要全局排序插入和删除都是对数复杂度在开放世界和池化物体管理场景下表现更稳定。Narrow Phase精检测阶段则真正处理几何相交计算。场景里的物体形状各不相同碰撞形状通常分为凸包Convex Hull、球体、胶囊体、以及静态的三角形网格Triangle Mesh。凸体之间最常用的算法是GJKGilbert–Johnson–Keerthi它通过不断地构造支撑点来逼近两个凸体之间的最近距离效率极高。SAT分离轴定理则适合检测分离轴的快速判定尤其是在2D物理引擎里应用广泛。三角形网格对网格的碰撞检测会更复杂通常采用空间加速结构如BVH来快速定位可能相交的三角形子集再做逐三角形的相交测试。这里有一个架构级别的建议物理引擎的碰撞形状尽量用简单的几何体组合而不是直接用美术导出的高精度模型。因为高精度网格不仅让Narrow Phase的计算量爆炸更麻烦的是会引入大量微小的穿透点和接触法线抖动。行业里通用做法是给角色使用胶囊体、给地形使用多个凸包的组合、给静态物体使用简化后的碰撞盒。这么做的好处是物理表现可预测性强调试也容易得多。我见过有人为了逼真的碰撞直接上高精度网格结果角色站在微小的地面凸起上抖动不止最后不得不花大量人力去修接触点过滤。Narrow Phase阶段的输出是一个接触点集合包含位置、法线、穿透深度等数据。这些数据会被交给约束求解器来计算最终的速度修正所以接触点信息的质量直接决定了物理模拟的稳定性。1.3 刚体动力学与约束求解器的工作机制刚体动力学部分的核心是牛顿运动定律的数值积分。物理引擎模拟的不是真实的连续物理世界而是把时间离散化成一个个固定步长常见的是1/60秒约16.67毫秒在每一步里用数值积分方法来近似求解运动方程。常用的积分方法有半隐式欧拉Semi-Implicit Euler和Verlet积分。半隐式欧拉的做法是先更新速度再用新速度更新位置。它简单、稳定且够用是目前绝大多数物理引擎包括Box2D、Bullet、PhysX默认的积分方案。Verlet积分则适合粒子系统和小型网络游戏中的同步场景它不直接存储速度而是通过前后两个位置差来推算出速度内存占用更少但在约束求解时需要额外处理速度变量。架构选择上半隐式欧拉通用性最强除非你的引擎有特殊需求比如大量软体模拟或对确定性要求极高的网络同步否则不需要另辟蹊径。约束求解器是物理引擎里真正的物理拔河比赛的仲裁者。当一个物体有多个接触点时比如箱子放在地面上每个接触点都会产生支持力、摩擦力和可能存在的旋转修正这些约束力之间会互相竞争。约束求解器需要找到一个同时满足所有约束条件的力分配方案。比较常见的求解方式是基于LCP线性互补问题的求解工业界的做法是采用PGS投影高斯赛德尔迭代法这类迭代求解器。它的思路是逐个处理每个约束在保证每个约束满足条件的同时减少对其他约束的破坏经过多个迭代轮次后收敛到一个近似解。迭代次数通常在4到10次之间次数越多越精确但性能开销也越大。实际调优中有一个技巧迭代次数不是越高越好超过某个阈值后果肉眼几乎感知不到白白浪费CPU。我的经验是桌面平台用8次移动平台用4到5次具体效果配合调试可视化和物理调整就能找到平衡点。另外刚体动力学里有一个不能忽视的模块——休眠机制Sleeping。当一个物体长时间处于低速或静止状态时引擎会把它标记为休眠跳过它所有的物理计算。休眠机制是性能的关键来源之一尤其在有大量静态物体的场景里能省下大半物理开销。但这个机制的阈值设置是个细节活速度阈值设高了会发生物体还没完全站稳就睡死的诡异现象设低了又起不到休眠效果。标准做法是分两个阈值判断一个是线性速度阈值一个是角速度阈值两者都低于阈值且持续一段时间后才允许休眠任何一次碰撞或外力打断休眠立即唤醒。2. 动画系统架构核心拆解2.1 骨骼动画的数据管线从绑定姿势到最终渲染动画系统的架构基础和物理系统完全不同。一个完整的骨骼动画数据管线大致是美术在DCC工具如Maya、Blender、3ds Max里绑好骨骼、做好动画导出格式通常包含骨骼层次结构、每个骨骼相对父骨骼的绑定姿势Bind Pose、以及动画剪辑Animation Clip里每个关键帧的骨骼局部变换。运行时引擎需要做以下几个步骤。首先把动画剪辑的关键帧采样成每个骨骼在其局部坐标系下的变换平移、旋转、缩放这里面旋转通常用四元数表示以避免欧拉角的万向锁问题。然后从骨骼根节点开始把局部变换逐层乘到世界空间这个过程叫全局姿态更新Pose Update或骨骼前向递推Forward Kinematics。最后把全局变换乘以绑定姿势的逆矩阵得到蒙皮矩阵交给渲染器去驱动网格顶点。这个管线的架构关键在于分层解耦。动画采样、全局姿态计算、蒙皮矩阵计算这三个阶段应该完全分离允许各自使用不同的数据格式和更新频率。比如动画采样可以只更新局部变换全局姿态计算可以延迟到渲染帧里做蒙皮矩阵甚至可以放到GPU上算GPU Skinning。如果把这个管线揉成一个函数想在后期塞进程序化动画布料模拟或者物理引擎驱动的姿势修正就非常痛苦了。具体到性能预算骨骼动画的开销主要集中在全局姿态更新的矩阵乘法上。假设一个角色有80根骨骼每帧更新一次全局姿态需要做79次骨骼矩阵乘法每个矩阵乘法是4x4矩阵无论怎么优化这都是一笔不小的CPU开销。所以行业里普遍采用局部姿态缓存策略对于静止或长时间保持同一姿势的角色可以跳过采样阶段直接复用上一次计算出来的全局姿态。对于动画状态机中频繁切换的角色则需要把采样和更新拆到多线程里并行处理。2.2 动画状态机、混合树与动画层动画状态机Animator State Machine是游戏动画系统最核心的逻辑控制单元。它在架构上其实是把角色的当前动作状态建模成一个图表每个节点代表一个动画状态如Idle、Walk、Run、Jump每条边代表一个状态切换条件如移动速度大于某个值、在一个触发帧上切换到下一个动作。状态机的架构设计里最容易被低估的部分是过渡Transition管理。两个动画之间有过渡时长和过渡曲线过渡曲线决定了两个动画的权重如何在一段时间内此消彼长。纯新手实现过渡时往往是简单地线性插值结果就是动作切换看起来飘、没有力度感。实际项目里过渡曲线应该支持可编辑的曲线形状关键帧密集的打击动作在起手阶段需要快速切入、在收招阶段需要更细的过渡控制这些都需要动画师在编辑器中亲手调整。混合树Blend Tree解决的是同一个动作类型下不同参数取值之间的平滑变化问题。最典型的例子是Locomotion移动根据当前移动速度把走路动画2m/s和跑步动画5m/s按权重混合在一起中间任意速度都能有对应的姿势。混合树有1D、2D甚至更高维的类型2D混合树在处理速度方向大小这类双参数场景时尤其自然。架构上需要为混合树设计好采样语义每个子节点的权重是实时计算的但权重计算结果会直接驱动关键帧插值所以这个计算必须高效且无副作用。动画层Animation Layers则允许一个角色同时播放多个动画并叠加。最典型的应用是上半身层播放射击或挥剑动作下半身层播放走路或站立动作两者在中间骨骼通常是骨盆或脊柱处进行分层掩码Mask后叠加。层次化动画架构在FPS和动作游戏里是标配。但设计时要注意层之间的同步问题两个层各自播放各自的动画如果它们的目标骨骼之间存在层级交互关系需要指定一个同步层作为基准用它的时间轴驱动其他层否则会出现上半身和下半身动作节奏对不上的别扭感。2.3 动画压缩与资产加载策略动画资产的存储开销非常大。一个具有骨骼细节模型的高质量动画如果每个骨骼每帧都存储满精度浮点数的关键帧一个10秒钟的动画能轻松占掉几兆字节。所以动画压缩在游戏引擎架构里不是可选优化而是标配需求。常见的压缩策略有三个方面。第一是降采样每隔几个关键帧采样一次中间用贝塞尔曲线或样条曲线来插值还原适合大位移、大旋转的运动。第二是量化四元数从float32量化到float16甚至量化到8bit再配合残差预测平移数据用定点数存储并限制误差范围。第三是轨迹裁剪Track Trimming对于数值变化很小的骨骼比如一根不动的手指直接删除它的全部关键帧用绑定姿势替代。实际项目中压缩率通常能做到原始数据的20%到30%视觉损耗基本不可感知。加载策略上动画资产不应该一次性全部载入内存。角色可用的动画可能成百上千个每个状态机的动画剪辑加起来可能有几十兆甚至更大。架构上推荐做成动画资产流式加载配合按需加载缓存淘汰的机制。具体做法是动画状态机加载时只加载元数据和关键帧的轻量化索引真正进入某个动画状态的过渡初期才流式加载这个剪辑。同时在内存紧张时LRU淘汰掉最近最少使用的动画剪辑。这个策略在切场景和开放世界的场景里效果很明显能把内存峰值砍掉三分之一以上。3. 物理与动画的耦合设计3.1 动画驱动物理角色控制器与场景交互的正确姿势物理引擎和动画系统不是两个孤岛游戏里最难处理的往往就是它们之间的耦合。最典型的第一类耦合是动画驱动物理——角色的运动来自动画但物理引擎需要感知并响应角色与场景的碰撞。行业里最成熟的做法是使用角色控制器Character Controller这个组件作为中间层。角色控制器既不是一个普通的动态刚体也不是完全不懂碰撞的动画播放器它是一个特殊的运动学体动画系统负责设置目标速度角色控制器负责把目标速度转化为实际位移并在这个过程中与场景中的静态碰撞体和其他物体发生碰撞时自动修正位移。这样设计有几个好处第一角色的运动轨迹由动画控制保证动作的精准表现第二碰撞响应完全由角色控制器的碰撞检测逻辑处理不会出现角色被物理引擎推得晃来晃去的情况第三上层逻辑只需要设置向哪个方向走、走多快不用关心角色和墙面的接触到底怎么算因为在大多数游戏里玩家并不希望角色撞墙后反弹回来。但这里有一个必须注意的坑角色控制器输出给物理引擎的类型是运动学体而不是动态刚体。如果误把角色设成动态刚体并且用SetVelocity设置速度那么在角色走到斜坡上时重力、摩擦力、碰撞法线会让角色姿态不稳定动画系统设置的移动方向和实际物理速度可能出现偏差最终角色漂移、抖动、甚至被卡进模型里。正确姿势是角色由动画驱动物理引擎中的角色体作为运动学体位置由动画控制物理引擎仅仅是发现碰撞并修正到合法位置。实际项目中角色控制器的碰撞形状通常是胶囊体因为胶囊体在狭小地形和斜坡上的行为比盒子稳定得多。与地面接触的判定也有一个常见陷阱把胶囊体底部直接贴近地板会导致在凹凸地面上角色不断处于接触与脱离的抖动状态。一般需要在角色底部留一定落差碰撞检测用胶囊体的几何体做合理的下探落地时只处理真正的脚底接触。这些细节看起来不起眼但它们直接决定了角色的贴地感好不好。3.2 物理驱动动画布娃娃与死亡表现第二类耦合是物理驱动动画。最常见的是布娃娃Ragdoll系统典型场景是角色死亡后身体受重力影响自然倒下或者被爆炸冲击力抛飞。跟动画驱动物理正好相反此时角色的骨骼不再由动画状态机控制而是由物理引擎来控制——物理引擎为角色的每个主要骨骼骨盆、脊柱、四肢等分别创建一个动态刚体刚体之间用约束Constraint连接起来形成一个物理骨骼链。布娃娃系统的架构设计里最关键的是转换时机。不能简单粗暴地把整条骨骼从动画状态瞬间切换成物理驱动这会给物理引擎制造一个巨大的初始条件跳变轻则让身体瞬间扭曲重则被明显弹飞。成熟的引擎做法是根据死亡触发的冲击点确定一个初始死亡姿势然后让动画骨骼在极短时间内大约100到200毫秒向这个物理姿势做权重插值过渡一旦权重完全切到物理侧动画系统就不再干预交给物理引擎接管。这样玩家视觉上的感受是角色被打飞出后落到地上弹了一下而不是角色先保持姿势半秒然后咔嚓一下变成软瘫状态。布娃娃调试是个大坑。物理约束的属性参数硬度、阻尼、角度限制晦涩难调而布娃娃的表现又直接影响打击感和死亡反馈。我建议在引擎里专门搭建一套调试工具能够用可视化方式查看每个约束的旋转轴和角度限制同时能实时调整约束参数并立刻观察效果。另外所有与布娃娃相关的物理碰撞形状都要设置为只参与物理模拟但不触发游戏逻辑比如布娃娃的碰撞不应触发机关也不要被玩家用碰撞盒推开。除了死亡布娃娃物理驱动动画还常用于另一类场景衣物的动态模拟更进阶的布娃娃/布料混合和运动过程中的肢体修正。在一些表现力要求高的游戏里角色在上坡、下坡、转身时动画状态机的规则不一定能产生正确的落脚点这时候可以引入IK逆向运动学来解算脚部的落地位置。IK本质上是在骨骼链末端加位置约束让物理引擎或专门的IK求解器算出关节旋转角。这一块同样属于物理和动画的交叉地带架构设计上要把IK求解和动画采样分开IK结果作为后处理修正叠加在动画输出之上而不是直接修改动画剪辑本身。3.3 时间步进与同步机制固定步长和插值策略物理和动画在节奏上是天然冲突的。物理引擎要求固定时间步长Fixed Timestep因为变长时间步长会让数值积分不稳定物体在不同帧率下的运动表现会出现差异。而动画系统更多依赖可变帧率因为它要跟随渲染帧率和真实时钟来保证手感一致。解决冲突的经典架构是固定物理步长渲染插值。做法是物理引擎以固定的频率比如每秒60次或每秒120次推进模拟渲染线程则把当前渲染时刻与最近的物理状态做插值。物理向渲染端输出的是两个相邻物理状态过去状态和当前状态渲染端按照当前渲染帧对应的插值系数在这两个状态之间插值出中间状态这个中间状态就是角色实际渲染出来的位置。动画系统也有类似的插值策略。动画时间轴需要跟随真实物理节奏尤其在慢动作特效、击中停顿、关键帧反馈等场景里动画系统要能够暂停慢放或跳帧而不是严格逐帧播放。实现上动画系统在时间轴上使用独立的本地时间变量通过时间尺度Time Scale字段来控制速度同时保留上一帧采样结果和当前帧采样结果在渲染时进行二次插值。这一套让动画和物理在表现上看起来同步了即使内部是不同步的。这里有一个性能相关的架构决策物理和动画的更新频率应该分离。物理可以用120Hz甚至更高来保证基站稳定但动画采样通常只需30Hz到60Hz的频率就够了因为关键帧之间的插值本身就有平滑效果。高频物理低频动画的组合能省下可观的CPU开销同时让碰撞表现更细腻。如果团队在性能上吃紧我建议优先保物理频率动画频率可以适当下调因为物理频率下降带来的穿模和抖动问题比动画插值质量下降显眼得多。4. 架构决策中的常见问题与调优经验4.1 物理系统常见问题与排查思路物理系统的坑大多跟不稳定性有关。最常见的三个现象是穿模、抖动和弹飞。穿模往往是在快速移动的小物体碰撞到薄壁场景造成的解决方案有两类一是开启CCD连续碰撞检测对运动快的物体做扫掠检测而不是简单的离散碰撞二是加大碰撞厚度Collision Margin给薄壁模型加一层小的膨胀体积。前者精度高但开销大后者基本零开销但精度有限实际项目里常常两者结合慢速物体不加CCD只有速度超过阈值的高速物体比如子弹、投掷物才启用。抖动通常来源于概率性的接触点跳变。物体在斜面上滑动或者刚好处于两个碰撞体之间的缝隙中时Narrow Phase算法不同帧推进得到的接触点位置可能在小范围内随机跳变反映到画面上就是物体呼吸一样抖动。解决办法是引入接触点持久化Contact Persistence机制保存上一帧的接触点信息如果新的接触点与上一帧的接触点距离很小就直接复用旧数据这能大幅降低抖动。另一个常见原因是约束迭代次数不足导致接触约束在迭代时没有收敛表现为物体在接触法线方向上来回微颤把迭代次数稍微往上调就能缓解。物理系统还有一个架构层面的性能陷阱多线程调度里物理引擎的窄相位碰撞和约束求解涉及大量的数据竞争如果物理引擎同时被多个线程修改非常容易触发不可复现的随机Bug。我在架构设计时会明确把物理世界Physics World划成单一写者模式任何线程都只能通过物理API提交指令物理引擎在内部统一处理保证没有两个线程同时修改同一个刚体的状态。物理子系统的调试也需要专门的可视化工具包括碰撞形状线框、碰撞接触点标注、速度矢量显示等。物理引擎内部状态难以用普通调试器实时查看没有可视化调试工具排查物理问题等于瞎猫抓老鼠。4.2 动画系统常见问题与调优经验动画系统最常见的性能瓶颈在蒙皮计算。CPU蒙皮在面对大量骨骼角色时会让单帧CPU预算爆炸所以现代引擎几乎都做GPU蒙皮把蒙皮矩阵上传到GPU由顶点着色器完成顶点转换。但要注意GPU蒙皮并不适合所有平台和所有角色批量合批的UI控件或者需要CPU读取骨骼位置做物理交互的角色比如头发、衣摆跟随骨骼摆动GPU蒙皮会增加回读成本这时候需要针对这些特殊角色保留CPU蒙皮路径。架构上建议同时支持两种蒙皮路径通过渲染状态位选择而不是等上线了再打补丁。动画状态机的另一个常见坑是过渡闪烁。当两个动画的骨骼命名不完全一致或者骨骼层级不一样时状态机的过渡插值会先插出无效姿势画面表现为角色瞬间闪一下再进入目标动画。解决这个问题的核心在于过渡运行时统一坐标系所有动画进过渡之前都要经过一个重定位Retarget步骤把源动画和目标动画对齐到同一个参考骨骼通常是根骨骼或骨盆上。重定位在换皮项目和角色皮肤系统里尤其重要没做这层装备了不同骨骼模型的外观就会在动画过渡期间穿模。另一个高频出现的细节是动画数据的后坐力问题对于受击、出招这类需要高帧率判定的动作动画采样一旦卡顿玩家就会觉得我明明按了角色却没反应。这里建议在架构上给关键动作的动画状态设定更高优先级和低延迟路径。具体做法是把普通移动动作和受击动作分到两个动画状态机层受击层优先级更高并且受击层的采样频率可以单独提升到120Hz保证它能在最短的响应窗口内输出关键帧。4.3 性能调优的实测方法论物理和动画的性能调优不能只靠一看很卡就降画质。我的建议是建立一套系统的性能分析流程。第一步是分级预算拆分。在项目启动前就定好物理和动画的CPU/GPU预算。物理系统中碰撞检测和约束求解的消耗占比大约是百毫秒级的项目里碰撞检测占40%、求解占30%、刚体状态更新和回调占30%。动画系统里骨骼采样大约占20%、全局姿态更新占30%、蒙皮计算占35%、状态机逻辑占15%。有了预算表每次性能优化都必须先确认瓶颈在哪个子模块而不是盲目把整体质量往下调。第二步是使用专用的性能剖析工具。物理引擎的自带Profiler如PhysX的Pvd、Bullet的Profiler可以详细到每个算法耗时动画系统的Profiler则要关注每帧采样了哪些动画剪辑、每次混合的权重计算耗时、蒙皮命令的提交耗时。配合真机采集把数据用图表拉出来立刻能看出哪个系统超出了预算。第三步是数据驱动的调优实录。我曾经在一个开放世界项目里发现角色在复杂地形上会周期性卡顿查了大半天都找不到原因后来通过物理Profiler发现是玩家附近有大量相互连接的静态碰撞体它们形成一个巨大的碰撞图导致Narrow Phase阶段要对成千上万个三角形做相交测试。最后的解决办法是把大户型静态场景网格切成多个互相独立的小碰撞体同时把角色周围的大地形网格重合区域做过一次剔除性能立刻回到预算内。这个经历让我养成了一个习惯任何物理性能问题都先从碰撞形状的数量和复杂度开始排查不要一上来就怀疑求解器参数。5. 个人体会和实操建议最后说点更偏经验层面的东西。在引擎架构这个领域摸爬滚打这么多年我最大的体会是物理和动画这两个系统绝对不能等分头开发完了再对接——它们必须在架构设计阶段就当成一对耦合系统来考虑。动画驱动的角色需要知道物理能提供什么样的碰撞反馈物理场景需要知道动画角色会给世界带来什么样的外力输入。等到两边都写完了、各自跑得很欢快再去做整合你会发现接口设计处处别扭改哪里都会动到另一边的核心逻辑。对于一些起步项目的开发者我的建议是先从Unity或Unreal成熟的物理动画体系中学习它们的接缝设计再考虑自制引擎。PhysX和Havok的架构设计虽然繁复但它们在物理与动画之间的桥接方案比如动画射线、持续接触点复用、动力学的速度驱动模式是经过大量项目验证的。做自制引擎时完全可以参考这些思路而不是闭门造车。另外一个很实际的经验是可视化调试工具一定要提前搭。物理和动画系统内部全是高维数据没有线框显示、没有时间轴可视化、没有实时参数调整面板所有优化都只能靠猜。我在早期项目里有过连续三周在调一个布娃娃关节抖动问题最后发现是丈量约束极限的角度范围反了。如果当时有可视化约束编辑器这个折磨人的过程应该一两天就能收工。物理与动画的架构设计本质上是在稳定、性能、表现力三方之间找平衡。没有一套万能的方案只有基于项目特性做出的取舍。希望这篇文章能把一些常见的取舍逻辑和底层原理讲清楚帮助大家在接下来的项目里少踩几个我已经踩过的坑。
返回列表