我喜欢飞行模拟游戏很多年了。但我一直想要的那个东西,好像一直没人做出来:正经的模拟太重,光是学会起飞就得啃半年手册;手机上的空战游戏太假,飞机像贴纸一样在天上滑;想和朋友随便开一局,结果不是要买同一个平台,就是根本没有联机。
I've loved flight sims for years. But the thing I actually wanted never seemed to exist. Real sims are too heavy — half a year of manuals before you can take off. Mobile dogfight games are too fake — planes slide across the sky like stickers. And playing a quick round with friends means either buying into the same platform, or there being no multiplayer at all.
2026 年 7 月,我决定自己做一个。整个过程我都和 AI 一起——具体说,是 Claude Code。两周之后,它有了四关战役、能从航母上弹射起飞和挂索着舰、能绕着一颗 1:100 的真实地球飞、还能在手机和电脑之间开黑。
In July 2026 I decided to build it myself, working the whole way with an AI — Claude Code, specifically. Two weeks later it had a four-mission campaign, catapult launches and arrested landings on a carrier, a 1:100 Earth you could fly all the way around, and multiplayer between phones and desktops.
这篇是记录它怎么一节一节长出来的。几乎每一节都不在原计划里——都是飞着飞着突然想要的。
This is a record of how it grew, one piece at a time. Almost none of it was planned. Each piece came from flying the thing and suddenly wanting more.
01 / 07.16一架飞机,一片海One aircraft, one sea
起点小得不能再小:一架低多边形的战斗机,一片海,几座岛。能飞、能开炮、能锁定、能被打下来。
The starting point was as small as it gets: one low-poly fighter, one sea, a few islands. It could fly, fire, lock on, and get shot down.
选低多边形不是为了风格,是为了能做完。一个人做不出写实资产,但低多边形的山、海、飞机可以用代码生成——地形是程序化侵蚀出来的,海面是着色器算出来的。这个决定后来救了我很多次:想加一张新地图,写一个高程函数就行,不用等美术。
Low-poly wasn't a style choice, it was a shipping choice. One person can't produce realistic assets, but low-poly mountains, seas and aircraft can be generated in code — terrain from simulated erosion, water from a shader. That decision saved me over and over: adding a new map meant writing one height function, not waiting on an artist.
02 / 07.17有了敌人,就想要理由Once there were enemies, I wanted a reason
能打之后,打靶很快就腻了。我想要的不是刷分,是为什么要打这一仗。
Once you can fight, target practice gets old fast. I didn't want a score counter — I wanted a reason to fly the sortie.
于是有了第一章四关:训练、海峡拦截、静默走廊、北境护航。有幕间剧场、有开场运镜、有电台里预警机给你报点、有僚机跟着你飞。最后一关是护送三架运输机穿过山脊——如果它们全被打下来,就算你还活着,任务也是失败。
So came chapter one: training, a strait intercept, a silent corridor, a northern escort. Cutscenes between missions, a cinematic opener, an AWACS calling contacts over the radio, wingmen holding formation on you. The last mission escorts three transports through a mountain ridge — if they all go down, the mission fails even if you're still flying.
当时踩的坑:「打完山脊突袭,怎么突然就结束了」What went wrong: "the mission just ends after the ridge ambush"
我实机飞完报了这个问题,查出来是两个 bug 叠在一起。
I hit this while actually flying it. It turned out to be two bugs stacked.
一是运输机 AI 的油门反解写错了:它用 throttle = v / maxSpeed 反推油门,正确的是 (v - min) / (max - min)——结果运输机一路超速。二是"航线走完了"的判定:飞过倒数第二个航点的那一帧,航点索引已经推进了,却还在用旧的距离判断,于是提前认定"已送达"。
First, the transport AI inverted throttle wrong — it used throttle = v / maxSpeed where the correct form is (v - min) / (max - min), so the transports flew permanently overspeed. Second, the "route complete" check: on the frame that passed the second-to-last waypoint, the index had already advanced but the old distance was still being tested — so it declared "delivered" early.
第二个才是"突然结束"的真凶。修完之后我又加了个结尾飞越镜头:送达时镜头压到低空,看着编队掠过信标飞走。
The second one was the real culprit. After fixing it I added a closing flyby: on delivery the camera drops low and watches the formation sweep over the beacon and away.
03 / 07.17打完之后,我想看看刚才到底发生了什么After the fight, I wanted to see what had actually happened
空战最让人难受的一点是:你被打下来了,但你不知道自己是怎么死的。那发导弹是从哪来的?我是什么时候被锁上的?刚才那个转弯到底该不该拉?
The most frustrating thing about air combat is getting shot down without knowing how. Where did that missile come from? When did they lock me up? Should I have pulled that turn at all?
所以有了回放。每一局都会被记下来,打完可以在一张等高线沙盘上重看:谁在哪、谁打了谁、导弹从哪飞到哪、什么时候被锁定。时间轴可以来回拖,可以挑任意一架飞机跟着看,击落和发射的时刻在轴上都有打点,一眼就能跳过去。
So: replays. Every sortie gets recorded, and afterwards you can replay it on a contour sandbox — who was where, who shot whom, where each missile flew, when you got locked. You can scrub the timeline, follow any aircraft you like, and jump straight to the marked moments where kills and launches happened.
我最喜欢的是它能导出 .acmi——Tacview 的格式。那是真正的飞行模拟玩家在用的复盘工具,现在它能直接打开我自己游戏里的一场战斗。这件事让我觉得这个游戏"成立"了。
My favourite part is that it exports .acmi — Tacview's format. That's the debrief tool real flight sim players use, and it will open a fight from my own game. That's the moment this project started feeling legitimate to me.
当时踩的坑:回放要记什么,和手机上的两个洞What went wrong: what to record, and two holes on mobile
回放最核心的取舍是记什么。位置、姿态、速度、血量这些是连续量,按固定频率采样就行;但击落、发射、锁定这些是瞬时事件,采样频率再高也可能正好卡在两帧之间漏掉。所以两种要分开记:连续量采样,事件单独打点。只做前者,时间轴拖得动但关键瞬间会莫名其妙消失。
The core trade-off in a replay is what to record. Position, attitude, speed and health are continuous — sampling at a fixed rate is fine. But kills, launches and locks are instantaneous events, and no sampling rate saves you from landing between two frames. So they need separate treatment: sample the continuous stuff, log the events explicitly. Do only the first and the timeline scrubs beautifully while key moments quietly vanish.
手机上还有两个洞是我实机撞出来的:触屏的按钮会盖在沙盘上面挡住视线;以及从暂停状态点进回放,退出之后整个界面就锁死了——因为进回放前游戏已经是暂停的,退出时的恢复逻辑没考虑这条路径。
Two more holes I hit on an actual phone: the touch buttons sat on top of the sandbox and blocked the view, and opening a replay from the pause menu left the whole UI locked on exit — the resume path never accounted for the game already being paused when you went in.
04 / 07.18–19海得活过来The sea had to come alive
这一节完全是被我自己逼出来的。飞在海上,我总觉得下面那片蓝是死的。我跟 AI 说"看不出浮动",它把浪加大;我说"还是像玻璃",它加高光;后来我说了一句"像西瓜条"——大尺度的光带在海面上滚,整个海像切开的西瓜皮。
This one I forced on myself. Flying over water, that blue felt dead. I told the AI "I can't see it move"; it raised the waves. "Still looks like glass"; it added specular. Then I said "it looks like watermelon rind" — big smooth light bands rolling across the surface, the whole sea striped like a cut melon.
那句抱怨最后变成了一条铁律:长波涌浪只进几何,不进颜色;颜色只跟碎浪走。
That complaint became a rule: long swells move geometry only, never color. Color follows the small chop.
定下这条之后,海才终于对了:贴岸的浅水最活跃,深海安静;风大的时候浪会碎得更细;日落时海面跟着天色变暖——这最后一条是意外惊喜,在那之前一直是橙色的天配冷蓝的海,怎么看怎么假。
After that the water finally worked: shallows near the shore are the liveliest, deep water is calm, high wind breaks the surface into finer chop, and at sunset the sea warms with the sky — that last one was an accident, and a good one. Before it, an orange sky sat over a cold blue sea and always looked wrong.
当时踩的坑:低掠角下海面裂开一条缝What went wrong: the sea split open at grazing angles
为了让"面在翻动",最直觉的做法是按三角面整体位移。看着挺好——直到我把机头压低贴着海飞,海面上出现一道道浅色划线:相邻面各自整体上下,缝隙就露出了下面的海床。
To make the facets "tilt", the obvious approach is to displace each triangle as a whole. Looks fine — until you drop the nose and skim the surface, and pale streaks appear: neighbouring facets moving independently open gaps, and the seabed shows through.
正解是按顶点位置做哈希:共位的重复顶点算出同一个相位,所以永远水密;而同一个面的三个角相位不同,面还是会真实地倾斜翻动。按面只能用来着色,不能用来位移。
The fix is to hash by vertex position: co-located duplicate vertices resolve to the same phase, so the mesh stays watertight, while the three corners of one facet get different phases, so it still tilts convincingly. Per-face values are for shading only, never for displacement.
05 / 07.20飞了几天,我想落地After a few days of flying, I wanted to land
这是整个项目最让我上头的一段。飞了几天之后,我突然想:能不能落下来?
This is the part that hooked me hardest. After a few days of flying I thought: what if I could land?
先是机场。放起落架、加速、抬轮、离地;回来的时候对准跑道、控制下沉率、接地、刹停。然后我就不满足了——我要航母。
An airfield first. Gear down, accelerate, rotate, lift off; then line up on the runway, manage your sink rate, touch down, brake to a stop. And then that wasn't enough. I wanted a carrier.
航母是另一回事。起飞要仪式:踩住刹车,油门推满,蓄力,甲板前的挡焰板立起来,松刹车的瞬间被弹射器扔出去。降落更难,要在几秒里同时管住速度、下沉率和对正。
A carrier is a different animal. Launching is a ritual: hold the brakes, full throttle, spool up, the jet blast deflector rises behind you, and the instant you release you're thrown off the deck. Recovery is harder — speed, sink rate and lineup, all inside a few seconds.
那天深夜,我第一次成功停在了尼米兹号上。然后我做的第一件事是提了个需求:回放里看不到船,我想欣赏一下自己的着舰英姿。
Late that night I put it down on the Nimitz for the first time. The first thing I did afterwards was file a feature request: the replay didn't render the ship, and I wanted to admire my own landing.
当时踩的坑:三件事都跟"以为对称"有关What went wrong: three bugs, all from assuming symmetry
飞机在甲板上朝后。跑道出生的朝向公式是对的,同一套用到甲板上却反了 180°。推了两次符号都推错,最后老老实实以实测为准。
The aircraft spawned facing backwards on deck. The heading formula was right for runways and 180° wrong on the carrier. I derived the sign twice and got it wrong twice, and finally just took the measured value.
幽灵甲板。重新出击之后飞机沉到甲板底下去了。原因是撤场时航母把自己注销漏了,旧的甲板"跑道条带"留在全局表里没人更新高度,飞机被钉在一个冻结的高度上,而船已经浮走了。教训:动态实体注册进全局表的东西,撤场必须成对注销。
Ghost decks. Launch a second sortie and the aircraft sank below the deck. The carrier wasn't unregistering its landing strip on teardown, so a stale strip stayed in the global table with a frozen height — the aircraft got pinned to it while the ship floated away. Lesson: anything that registers into a global table on spawn must unregister on despawn.
弹射轨本来就是斜的。我报"一上来就是歪的",AI 一开始去查航向锁定的代码。真相是:这艘尼米兹模型的艏部弹射轨本身就不平行于船轴,左右舷各内收 3° 和 5°。这种事代码里查不出来,最后是我在 Blender 里给四个弹射位打了标记点,游戏直接读模型里的标记。
The catapult tracks aren't parallel to the ship. I reported "it's crooked from the start" and the AI went looking at heading-lock code. The truth: on this Nimitz model the bow catapults genuinely aren't parallel to the hull axis — they toe in by 3° and 5°. No amount of code reading finds that. In the end I marked all four catapults in Blender and the game reads the markers straight out of the model.
06 / 07.21手机上也得能玩It had to work on a phone
我一开始就想好了:这游戏得能在手机上玩。不是"移植",是从一开始就有触屏那套——虚拟摇杆、油门滑条、机炮和导弹按钮。
I wanted this on phones from day one — not as a port, but with touch controls built in from the start: a virtual stick, a throttle slider, gun and missile buttons.
着舰之后我发现触屏少了一样东西:方向舵。没有它没法做横向对正,五边进场只能靠压坡度硬掰。于是加了两个方形按钮。同时把机炮键改成"空中开炮、地面刹车"一钮两用——手机屏幕就那么大,能合的都得合。
Carrier work exposed a gap in the touch layout: no rudder. Without it you can't correct lineup, and you end up banking your way down the groove. So I added two square buttons — and merged the gun key into "fire in the air, brake on the ground", because a phone screen only has so much room.
手机上最坑的两件事The two nastiest things about phones
手机横屏不是"窄",是"矮"。我们一开始的响应式断点全是按宽度写的,结果手机横屏宽度常常有 900 多,断点根本不触发,但高度只有 400 出头,界面直接溢出屏幕。真正的信号是 max-height,不是 max-width。
A phone in landscape isn't narrow, it's short. All the responsive breakpoints were written against width — but landscape phones are often 900+ px wide, so nothing triggered, while the height was barely 400 and the UI ran off the screen. The real signal is max-height, not max-width.
整块面板被断点藏起来,把唯一的入口一起带走。这个病我们一天之内犯了两次,两次都是我在手机上实机发现的:多人对战的侧栏在窄屏被隐藏,而"创建房间"和房间码输入框都在里面——手机上根本建不了房;战役界面的侧栏被隐藏,而"中队档案"的唯一入口在里面——飞行员档案彻底进不去。两处最后都改成了可以滑出来的抽屉。
Hiding a whole panel at a breakpoint takes its only entry point with it. We shipped this bug twice in one day, and I caught both on a real phone. The multiplayer side panel was hidden on narrow screens — and it held both "create room" and the room-code field, so you couldn't host a game at all on mobile. The campaign side panel was hidden too — and it held the only link to the squadron dossier, so that screen was simply unreachable. Both became slide-out drawers.
07 / 07.27–28那就飞整个地球Then: fly the whole planet
这个想法最疯,做出来最爽。既然地形是程序生成的,那为什么不把整颗地球放进去?
The wildest idea, and the most fun to land. If terrain is generated anyway — why not put the entire planet in?
于是有了 1:100 的地球:真实的高程数据,四叉树 LOD 按你飞到哪里加载。飞机其实一直在原点,是地球在你脚下转——这样浮点精度不会炸。你可以贴着地中海飞,掠过你认得出来的地方。
So: a 1:100 Earth from real elevation data, with quadtree LOD streaming to wherever you are. The aircraft actually stays at the origin and the planet rotates underneath — that's what keeps floating-point precision from falling apart. You can skim the Mediterranean and buzz places you recognise.
「跑步机」的两个后遗症Two side effects of the treadmill
让世界动、飞机不动,这招很好用,但它悄悄破坏了两处依赖"飞机在动"的东西。
Moving the world instead of the aircraft works beautifully, but it quietly broke two things that assumed the aircraft moves.
干扰弹和机头的凝结雾不往后飘了。它们是按飞机速度往后甩的,可现在飞机速度是零。
Flares and the nose vapour stopped trailing backwards — they were thrown back along the aircraft's velocity, and now that velocity was zero.
回放里的航迹塌成了一个点。回放记录的是飞机的世界坐标,而它一直在原点没动过。最后给回放器加了一个专门的"星球模式"。
The replay track collapsed to a single dot — the recorder logs world coordinates, and those never changed. The viewer ended up needing a dedicated planet mode.
08 / 07.28–29一个人飞,总是少点什么Flying alone was missing something
这是我最想要、也最不敢想的一块:和朋友开黑。
The piece I wanted most and expected least: playing with friends.
做出来是 2v2 / 4v4 的队伍对战,人不够 AI 补位。关键的设计是不做服务器权威:其中一个人的机器就是主机,中转服务器只负责转发消息,什么逻辑都不跑。这意味着桌面版可以把中转直接打包进去——你和朋友开一局,不需要我运营任何服务器。
What came out is 2v2 / 4v4 team combat with AI backfill. The key call was not to make the server authoritative: one player's machine is the host, and the relay only forwards messages — it runs no game logic at all. Which means the desktop build can bundle the relay outright: you and your friends can play without me running a single server.
还有一个改动我很喜欢:阵亡之后不倒计时。你被打下来,残骸会继续往下掉,留在世界里;你什么时候想回来,按一下开火键就回来。像 FPS 里尸体不会消失那样。
One change I'm fond of: no respawn timer. When you're shot down the wreck keeps falling and stays in the world; you come back whenever you press fire. Like bodies that don't despawn in an FPS.
联机是最难验的一段Multiplayer is the hardest thing to verify
联机的 bug 有一多半是时序问题,开两个浏览器标签页根本测不出来——因为后台标签页的渲染循环会被浏览器停掉,时序全是假的。真正管用的办法是写脚本,在命令行里跑两个客户端,把消息按帧喂进去。
Most multiplayer bugs are timing bugs, and two browser tabs won't find them — a background tab has its render loop throttled to a stop, so every timing you observe is fiction. What actually worked was scripting two headless clients and feeding messages frame by frame.
那两天我逐条实机报了二十多个 bug:手柄扳机的开火不上行、房客中弹之后卡死、导弹发射被 20Hz 的采样率漏掉、远端飞机一旦掉出敌机名单就再也回不来、一次击落掉出两具残骸……这些没有一个是读代码能读出来的,全是飞出来的。
Over those two days I filed more than twenty bugs from actual play: gamepad trigger fire not reaching the host, clients freezing when hit, missile launches falling through the 20 Hz sampling window, remote aircraft never recovering once they dropped off the contact list, one kill spawning two wrecks. Not one of these was findable by reading code. Every one came from flying.
09 / 方法METHOD和 AI 一起干活,我学到的What I learned working with an AI
两周做完这些,AI 的产出速度当然是主因。但如果只是"让它写",我大概第三天就会卡死在一堆看起来能跑、实际全是错的代码里。真正让它跑通的,是几条我自己摸出来的规矩。
Two weeks for all of this is obviously down to how fast an AI writes code. But if the process had been "just let it write", I'd have been stuck by day three under a pile of code that looked fine and was quietly wrong. What actually made it work were a few rules I arrived at the hard way.
「看起来对」和「真的对」是两回事。这是我付出代价最多的一条。有一次视觉出问题,AI 连着猜了几种可能的原因,每次都改一点、每次都不对。我批评了它,之后立了规矩:新的数据源先裁一张样图肉眼看;两个坐标系交汇的地方先对齐验证;视觉 bug 要硬取证——截图、读真实数值,不许连环猜。
"Looks right" and "is right" are different things. This one cost me the most. On one visual bug the AI guessed at several possible causes in a row, changing a little each time, wrong each time. I called it out, and we set rules: crop a sample image from any new data source and look at it with your own eyes; verify alignment wherever two coordinate systems meet; and for visual bugs, get hard evidence — screenshots, actual measured values — no chains of guesses.
我能出的最大的力,是去飞。回头看,项目里几乎每一个真问题都是我实机飞出来报的,不是读代码读出来的。AI 可以在几分钟内实现一整套航母弹射,但它不会知道"飞机在甲板上是朝后的"、不会知道"这条弹射轨本来就是歪的"、更不会知道"这个海面看着像西瓜皮"。
The most valuable thing I contributed was flying it. Looking back, nearly every real bug in this project came from me playing, not from anyone reading code. An AI can implement an entire carrier catapult system in minutes — but it won't know the aircraft is facing backwards on the deck, won't know that catapult track is genuinely crooked, and certainly won't know the sea looks like watermelon rind.
手感和取舍必须我拍板。镜头跟车该跟油门还是跟速度、海浪多大算"活"、阵亡要不要倒计时、机队留几架飞机——这些没有正确答案,只有我想要什么。AI 提过好几个我否掉的方案(航母进近的 HUD 信标太花、重力感应转向别扭),也做过我后来叫停的东西。它负责让每条路都走得通,我负责决定走哪条。
Feel and trade-offs have to be my call. Should the chase camera track throttle or speed? How big is "alive" for a wave? Should death have a respawn timer? How many aircraft stay in the hangar? These have no correct answer — only what I want. The AI proposed things I rejected (a HUD beacon for carrier approach was too busy; tilt-to-steer felt wrong) and built things I later cut. It made every road passable. I decided which road.
两周之后我的结论是:AI 没有替我做这个游戏,它拿掉了那道让我一直不敢开始的墙。以前"想做一个飞行游戏"和"真的做出来"之间隔着的是几年的技术积累,现在隔着的是——你愿不愿意每天晚上飞两个小时,然后老老实实把问题一条条写下来。
My conclusion after two weeks: the AI didn't make this game for me — it removed the wall that kept me from starting. What used to sit between "I want to make a flight game" and actually having one was years of accumulated skill. What sits there now is whether you're willing to fly it for a couple of hours every night and write the problems down honestly, one by one.
游戏还没做完。手机版和 PC 版还在打包,Steam 还没上。但它已经是我想要的那个东西了——能在手机上打开、能和朋友开一局、能从航母上飞出去、能一路飞回来。
The game isn't finished. Mobile and desktop builds are still being packaged, Steam hasn't happened yet. But it's already the thing I wanted: open it on a phone, play a round with friends, launch off a carrier, and fly all the way home.
ACEBAT
看看这个游戏现在长什么样
See what the game looks like now