
上一篇讲解穿透,开头我甩了几句移动平台挤、传送重生、数值边界就带过了成因,重点放在怎么把角色顶出来。有读者留言问,移动逻辑我写得挺仔细的,sweep 检测也做了,角色到底怎么还会陷进墙里?这问题问得好。因为你要是不理解它怎么陷进去的,解穿透那套兜底逻辑你就只是照抄,遇到具体的穿模 bug 还是两眼一抹黑。这篇就专门把陷进墙这件事扒开,一个个来源讲清楚。搞明白了,你才知道解穿透到底在给谁擦屁股。先立个前提:sweep 只防移动中撞墙要理解角色怎么陷进去,得先明白你的 sweep 检测到底防的是什么。Collide-and-Slide 里的 sweep,检测的是角色从 A 点移动到 B 点,这一路上撞不撞得到东西。它是沿着运动轨迹扫描的。这意味着它有两个隐含前提:一是角色得主动在动。sweep 是由角色的位移触发的,你不动,它就没有轨迹可扫。二是起点 A 得是干净的。sweep 假设你出发时没跟任何东西重叠,它只负责这段路。几乎所有陷进墙的情况,都是因为打破了这两个前提中的某一个。要么角色不是主动撞的墙(墙来撞它),要么起点本来就脏。下面挨个看。来源一:墙主动来撞你(移动平台)这是最经典的一类。玩家站着不动,或者慢慢走,一块移动平台——电梯、升降台、传送带、机关门——朝他挤过来。问题就出在,sweep 是由角色的位移触发的。平台在动,角色没动(或者动得慢),角色这一帧的位移很小甚至为零,它的 sweep 扫出来的轨迹也就一丁点,根本覆盖不到平台冲过来的那段距离。第 N 帧 第 N1 帧 ● 角色站着 ████ 平台挤过来了 ● 角色纹丝没动 ████ 平台在这 但已经被平台包住 → 平台朝角色移动 角色 sweep 位移≈0 压根没检测到平台角色的检测逻辑心想我这帧没怎么动,前方也没主动撞上啥,于是啥也没做。可平台不管这个,它自顾自地移动,把角色整个吞进去了。等下一帧一看,角色中心已经在平台内部——陷进去了。这就是为什么有移动平台的场景,解穿透几乎是刚需。平台的运动和角色的检测是两套独立的东西,角色的 sweep 管不了别人主动往自己身上撞。来源二:位置被啪地设过去(传送、重生、拉拽)第二类是位置瞬移,不走移动逻辑。正常移动是算出速度,sweep 检测,滑动,更新位置,一整套流程,中间有碰撞把关。但游戏里大量的位置变更是绕过这套流程的,直接把坐标赋值过去:玩家死亡后重生,pos spawnPoint,啪,设过去了。被钩爪、被技能拉拽、被爆炸击飞到某个点,pos targetPoint。场景切换、剧情演出把角色摆到指定位置。这些操作里,落点是策划配的或者技能算的,谁也不保证那个点一定在空地上。重生点可能被临时生成的物件占了,拉拽的终点可能正好嵌在墙里,击飞的落点可能穿进了地板。因为是直接赋值,中间没有任何 sweep 拦一下,角色唰地就出现在几何体内部了。这类穿透和运动完全无关,纯粹是落点没校验。解穿透在这里的作用就是:不管你被啪到哪,只要陷进去了,我下一帧立刻把你顶到最近的空地。来源三:多个碰撞体夹击,滑动解不干净第三类是角色被夹住。上一篇讲滑动,提到墙角、V 字夹缝这种地方,滑动会撞满MAX_SLIDE次数上限然后放弃,剩下的位移不走了。这是防死循环的必要妥协,但代价是——放弃的那一刻,角色可能正贴着两面墙,残留一点点重叠没解干净。墙 A ██ ██ ●← 角色被 A、B 两面墙夹在角落 ████████ 滑向 A 就撞 B滑向 B 就撞 A 墙 B 滑动次数用完残留一丝重叠单帧这点重叠很小,不处理也不明显。但如果玩家持续往墙角里顶(比如按着前进键怼墙角),这点重叠会一帧帧累积、被移动逻辑反复放大,慢慢就陷进去了。还有相向挤压的情况:两块移动平台从两边同时夹过来,或者一面移动的墙把角色压向一面固定的墙。角色被两个约束同时拉扯,滑动这种沿单一表面走的逻辑根本处理不了双向挤压,只能靠解穿透去反复顶。来源四:数值边界和 sweep 自身的精度第四类比较底层,是检测本身留下的残渣。sweep 检测算的是角色形状沿轨迹移动,第一次碰到表面是在哪个点。理想情况它让角色停在刚好贴着表面的位置——不重叠,也不留缝。但刚好贴着这件事,在离散的数值世界里很难做到分毫不差。擦着表面走、贴着墙角拐、高速移动一帧跨过很大距离,这些情况下 sweep 算出的停靠点可能带一点点误差,让角色停在了陷进去一丁点的位置。单帧这点误差微不足道,但它是个种子——下一帧移动从这个已经陷进去一点的脏起点出发,又违反了起点得干净的前提,误差被放大,滚雪球。这也是为什么上一篇讲解穿透时要留一层 skin width(皮肤厚度)。让角色始终贴着但不压进表面,留一层薄缓冲,就是为了给 sweep 的精度误差留余地,不让角色真的压到表面上去积累这种残渣。来源五:几何体自己变了最后一类容易被忽略:角色没动、也没被挤,是环境变了。可破坏的墙被炸开又生成、机关门关闭、场景里动态刷出一块几何体、平台旋转扫过角色所在的位置。这些情况下,是碰撞几何在角色脚下(或身上)发生了变化,角色原本站的干净位置,突然就变成了几何体内部。角色压根没机会检测——它上一帧还站在空地上,这一帧空地变成了实体。这跟移动平台是一类问题(环境主动改变角色的处境),但触发方式更隐蔽,因为连有东西朝我移动都没有,是几何体凭空出现在了原地。归纳:两条主线五个来源看着杂,其实归到那两个前提上就清楚了:违反角色主动在动这条的:移动平台、相向挤压、几何体变化——共同点是角色被动挨撞,不是它自己撞上去的,sweep 由角色位移触发,它管不了别人往自己身上撞。违反起点干净这条的:传送重生落点没校验、滑动残留、sweep 精度残渣——共同点是角色某一帧的起始位置就已经脏了,后续移动在脏起点上继续,越陷越深。理解了这两条主线,你就明白解穿透为什么是兜底而不是主逻辑:它不负责预防穿透(那是移动逻辑和落点校验的事),它负责的是预防没做到、或者根本没法预防的那些情况,每帧开头擦一遍,保证不管角色以什么离谱方式陷进去,都能被顶回合法位置。一个实践建议:别指望预防,老实兜底新手常有个误区,想把穿透从源头彻底堵死——把 sweep 做得无比精细、把每个传送落点都校验、把移动平台也做 sweep。这条路走不通,成本极高还堵不干净,因为像几何体凭空变化这种情况你根本没法在移动侧预防。大厂的思路是反过来的:承认穿透必然会发生,与其耗尽力气去预防每一种,不如做一个稳健的解穿透兜底,每帧无脑擦一遍。预防做到八九成够用就行,剩下的一两成交给兜底。这样代码简单、行为可预测,而且因为解穿透是定点的、逐比特一致的,客户端和服务器擦出来的结果还完全一样,不会因为这台机器预防住了那台没预防住而分叉。预防是尽力而为,兜底是万无一失——这个分工想清楚了,穿模这类 bug 才不会没完没了。收尾角色怎么会陷进墙?根子上就两句话:要么它是被动挨撞的(墙、平台、变化的几何体主动往它身上招呼,而 sweep 只由角色自己的位移触发,防不住),要么它的起点本来就脏了(传送落点没校验、滑动残留、检测精度残渣,让后续移动在错误的位置上滚雪球)。sweep 检测防的是我主动移动时撞不撞墙,它有清晰的能力边界。边界之外的所有情况——被挤、被传送、被夹、被环境改变——都会让角色陷进去,而这些恰恰是解穿透存在的理由。搞清楚它在给谁擦屁股,你写解穿透才不是照抄,遇到具体穿模才知道该往哪查。