乐于分享
好东西不私藏

地图编辑器卡到怀疑人生,Codex揪出的真凶竟是“看不见的节点”

地图编辑器卡到怀疑人生,Codex揪出的真凶竟是“看不见的节点”

       

☕ 用一杯咖啡的时间,带你穿透信息的迷雾。

—— AI方略

上篇聊的是真实战场地图的性能优化,结尾预告了下一期要做编辑器的优化。

今天我就通过Codex,全程不写一行代码,让它帮我完成所有需求,看下它的真实的编程场景下表现如何?


第一坑:不能用列表思维做斜向地图

这次优化的起因,其实很朴素。

策划反馈:编辑器里地块是规规整整的矩形表格,但真实战场是斜向六边形布局。

配一块地图,等于要在脑子里先把坐标翻译一遍。不只是不好看,而且很容易配错

于是给我提了优化的需求:编辑器直接还原真实战场布局,真实地图怎么摆,编辑器就怎么摆。

原来我是用了一个虚拟列表 gridList 按行列线性排布的,天然适配横平竖直的表格。斜向六边形坐标完全不在它的能力范围内。

所以第一步:去掉 gridList,引入 mapScroller + gLayer,所有地块挂到 gLayer 上,坐标直接使用真实战场的斜向公式,缩放到 0.5,默认 100×100,默认显示坐标。

改完后如下图看起来顺利,是按真实的缩放一半显示了。


第二坑:地图能显示,但拖不动、显示不全

地图渲染出来后,发现滚动不了。

gLayer 它不会像滚动容器一样移动。真实战场的滚动能力,来自外层的可滚动视图,滚动事件、边界限制、视口位置都在那一层。编辑器只复制了 gLayer,没有复制滚动容器的上下文。

加了 mapScroller 解决了拖动问题。

但紧接着又发现:最上面一行显示不全,往下拖能看到,松手就回弹。

典型的滚动区域边界没算好。六边形布局天然有偏移,第一行、第一列会被裁掉。最后在顶部和左边加了 padding,把整个地图内容区撑开,这才正常。

还有一个问题:编辑器打开时,用户看到一片空白。地图不是没显示,是初始化位置默认停在左上角的空白角落,需要自己拖几下才能找到。

后来加了初始化滚动逻辑:创建完 gLayer 尺寸后,自动跳到地图的可见中心区域。


第三坑:点一个格子,像刷新了整张地图

地图能正常显示、正常拖动之后,又发现了一个更隐蔽的问题。

点选一个地块、只想换一张图片,屏幕却闪了一下。

点一个格子,为什么像刷了整张地图?

排查后发现,修改局部数据之后,触发了过大的渲染范围重建。代码没有区分”改一格”和”改全图”。

修复逻辑很清晰——点一个地块,只做三件事:

1. 改这一格的数据

2. 如果这格当前可见,就更新这格的图片

3. 保存当前状态

不重建、不刷新、不触发全图渲染。

_refreshGridItem 加了缓存比较:icon 没变不重新 setIcon,title 没变不重复 setTitle,类型没变不刷颜色。

闪屏解决了。但点击后图片切换还是有明显延迟——这说明虽然不闪了,但节点数量本身带来的渲染压力还在。

根本问题还没解决。


破局点:数据全量,节点只创建可见的

到这里,问题已经很清楚了。

100×100 的地图,数据必须有 10000 份。但 UI 节点不需要有 10000 个。

这是这次优化最核心的一个点,之前采用分帧创建节点,首屏创建300个,剩下的9700还在后面默默创建,这也是为什么点一下图标会感觉卡了一下的根本原因,其实根本没必须创建那么多不可见的节点。

用户当前视口能看到的,可能只有几十个、最多几百个地块。那就只创建这些。其他的,存在数据层就够了。

改造方案参考虚拟列表的思路:

gridData:保存完整的 10000 格地图数据,不动

visibleGridMap:只记录当前视口附近的活跃节点

gridPool:滚出屏幕后,节点不销毁,回收进池子

滚动时,离屏节点归还到池子;新进入视口的地块,从池子里拿旧节点,重新绑定坐标、图片、标题、点击数据,直接复用。

地图可以无限大,但 UI 节点的数量永远和视口大小相关,而不是和地图面积相关。

这一步改完,编辑器渲染速度明显提升,点选地块响应也流畅了。这才从”能用”开始变得”像个工具”。


第四坑:内部工具的”协作边界”问题

性能问题解决后,又暴露出一个协作层面的坑。

原来 GVG 地图编辑数据存在 UserDefault.xml。这个文件里不只有地图数据,还有很多模块的本地缓存。

策划想把当前地图配置发给开发看,只能发整个 UserDefault.xml——开发一覆盖,本地所有其他模块缓存也跟着没了。

解决方案不复杂:把 GVG 地图数据拆到单独的 GVGMapEditData.xml,读取逻辑兼容旧文件,新文件存在优先读新的。

这个改动不炫技,但非常实用。配置文件的边界,本质上就是协作边界。一个工具越靠近策划的生产流程,就越不能把无关状态混在一起。


最难的一步:把业务状态从 UI 节点上剥离

在场景编辑器里面用上节点池复用之后,我把这套逻辑复用到了真实的游戏场景,又冒出了一个隐藏问题。

以前很多逻辑默认 data.obj 永远存在。但节点复用后,离屏地块的 data.obj 可能已经被回收,是 nil

这时候如果业务逻辑还写”通过 data.obj 去找 cityStatus“,就会报错或行为异常。

修复路径很清晰:

• 城市状态挂到 data.cityStatus,不再依赖节点对象

• 行军头像优先读 mapPos.cityStatus

• 按钮点击时优先读 clickData.cityStatus

• 可达地块高亮不再依赖 data.obj

UI 节点只是显示对象。数据才是状态来源。

这条边界一旦搞清楚,后续很多隐性 bug 都会提前消失。


这次优化真正改掉的,不只是卡顿

从表面看,这是一次地图编辑器的性能优化。

但往深里看,它暴露了之前写这个模块的同学几个更底层的坏习惯:

第一,不能因为数据有 10000 条,就创建 10000 个 UI 节点。 数据全量,渲染虚拟化,这是大地图、大列表、复杂编辑器都应该遵守的基本原则。

第二,业务状态不能寄生在 UI 对象是否存在这件事上。 一旦做节点复用,data.obj 就不再可靠,状态必须回到数据层。

屏幕就这么大,用户当前只能看到这一小块。那就只创建这一小块。其他的,存在数据里就够了。

性能优化,很多时候不是找什么神奇 API——而是先问一句:

这个东西,现在真的有必要被创建出来吗?

最后说一句:这次用 Codex 改代码,稳得出乎意料

节点池改造、状态剥离、文件拆分——这几处代码改动都不小,逻辑耦合也比较重,我是有点担心改出奇怪副作用的,但 Codex 几乎一遍过。

更让我意外的是:它改得很”克制”。 只动该动的地方,不会顺手把周边无关代码一起”优化”掉。这对存量代码改造来说很关键——你不希望一个工具帮你改 A,顺便悄悄动了 B 和 C。

不过 Codex 有一个明显的短板:对网络的要求比较高。

同样的网络、同样的机器,Antigravity 跑得顺,Codex 有时候莫名触发重连。最夸张的一次,一个问题挂了 30 分钟,界面一直显示”正在思考中”——到底在想什么,没有任何反馈。对比下来,Antigravity 在网络稳定性上的容错表现明显更好。

所以目前我的用法是:网络好的时候首选 Codex,稳定性优先的场景用 Antigravity 兜底。

另外一个就是我发现Codex在app里面使用时上下文只有258K,在这个普遍都百万上下文的时代,感觉有点不足了,虽然超了它会自动压缩,但是我感觉还是有损的,有些细节的内容压缩后就没了。对话个七八轮就触发了上限自动压缩了,希望后续能跟上大部队。

还有一件事不得不提——

Codex 今天悄悄把我的额度重置了。没有公告,没有提示,打开一看突然就100%了。

本来我昨天就使用到一周额度只剩下89%左右,然后我今天跑了几个小时之后想看下我的额度消耗了多少,当时显示我的周额度直接变成100%了(当时忘记截图了,这是我下班时候截的图),而且恢复时间也往后延了,本来应该是5月3日重置的。

就是这么任性,我喜欢。

赛博菩萨,实锤了。

感谢你的阅读 💡

如果这篇文章对你有所启发,
欢迎点击右下角的「在看」或分享给需要的朋友。
星标 ⭐ AI方略,我们一起进化。

——The End——

📚 往期回顾

游戏地图性能优化踩了6个坑,每修一个又出一个新的——这才是真实工程现场的样子

从Antigravity迁到Codex:图片生成、工作流兼容、GPT-5.5体验,一次说清楚

2026 编程真相:别再只用 AI 聊天了,这才是 Agentic Coding 的终极形态!