最近一周我抽空做了一个挺有意思的实战练习:拿开维游戏引擎搭一个 2D 横版小游戏,让 AI 编码助手自动生成代码,目标是把经典玩法“飞翔的小鸟 FlappyBird”从零到一完整跑起来,包括重力、点击跳跃、随机管道、碰撞、计分、重新开始这些全套逻辑。
文章标题里带着“AI 自动生成游戏代码”,但我得先说清楚一个现实:AI 不是闭着眼睛把整个项目丢给你,而是更像一个速度极快、但缺乏常识的结对实习生。它能帮你把 70% 的模式化代码瞬间写出来,剩下 30% 的架构梳理、数值调优、边界修复,仍然需要你亲手把关。这篇博文会把整个流程、提示词写法、踩到的坑一次讲透,适合想尝试“AI+游戏开发”的初学者,也适合正在研究怎么把大模型工具引入日常开发流程的从业者。
1. 为什么拿“飞翔的小鸟”当 AI 生成游戏的第一站
1.1 麻雀虽小,五脏俱全的玩法闭环
很多人觉得 FlappyBird 玩法简单,代码量应该很小,不值得认真对待。这其实是个误区。表面上它只有一个“点击就让小鸟跳一下”的交互,但背后包含了游戏开发中最核心的五块拼图:
- 游戏循环:持续更新、渲染、输入响应;
- 物理模拟:重力加速度、瞬时速度、位置积分;
- 场景生成:管道障碍按节奏随机生成,并持续向左移动;
- 碰撞检测:小鸟与上下管道、地面、天花板的相交判断;
- 状态管理:待开始、游戏中、游戏结束三种状态的切换,以及计分和重置逻辑。
这五块内容几乎覆盖了大多数 2D 休闲游戏的全部骨架。把这一套跑通之后,做跑酷、打砖块、甚至简单的纵版射击,都会非常顺手。所以我拿它当“AI 生成游戏代码”的试金石,是有意的选择。
1.2 判断 AI 适不适合生成某个项目的两个标准
我看过很多失败案例,一上来就让 AI 生成一个“大型开放世界游戏”,结果自然是灾难。AI 编码助手擅长的是把描述清晰的规则转成代码,而不是凭空架构一个复杂系统。用两个标准可以快速判断一个项目适不适合:
- 规则是否可以被书面化:如果玩法能用几条数学规则描述清楚,比如“重力 320 像素每平方秒”“管道间距 1.5 秒”,AI 就很容易理解。反之,那些依赖灵感、审美、手感模糊的功能,AI 只能给个雏形。
- 反馈回路是否短:改完代码能不能立刻看到效果?如果每次调试都要等编译、部署、热更新半天,“人机协作”的迭代速度就会大打折扣。
“飞翔的小鸟”恰好两个标准都满足。它不需要复杂动画资源,不需要服务端联调,改一个数值立刻能玩,非常适合做 AI 生成的试点项目。
1.3 开维引擎给我的初印象
这次用的是开维引擎。它是一个典型的“组件化”轻量级 2D 游戏引擎:场景里有各种实体,实体上挂脚本组件,组件里写生命周期回调,比如start()、update(dt)、on_input()。渲染这块我直接用引擎内置的矩形、图片和文本绘制接口,没有引入额外的渲染框架。
选择这个引擎做 AI 实验还有一个原因:它的 API 命名足够规范,结构简单,AI 编码助手对这类常见模式的生成质量普遍比较高。相反,如果你拿一个非常小众、资料极少的自研引擎去让 AI 生成代码,它没有足够语料可参考,生成出来的代码大概率是自嗨式 API 调用,根本跑不起来。
提示:如果你想复刻这次实验,建议选主流语法风格、资料完整的引擎或框架。AI 生成的可用率会高很多。
2. 工程初始化与场景搭建:先让小鸟“出现”在屏幕上
2.1 项目目录结构
在动手让 AI 写逻辑之前,我先把工程目录搭好。这一步看似没技术含量,却决定了后面 AI 生成代码时的上下文清晰度。我按下述结构组织:
assets/:存放图标、音效、字体等静态资源;scripts/:游戏逻辑脚本;scenes/:场景文件,描述实体和层级关系;data/:数值配置,比如重力、速度、管道间距等。
为什么要把数值单独放一个目录?因为 AI 生成的代码往往喜欢把魔法数字写死在脚本里,后期调手感很不方便。我会在提示词里主动要求 AI 读取一个data/game_config.json文件,把重力、管道速度、生成间隔这些常量集中管理。改一个 JSON 字段就能试一版新手感,这个体验比每次翻代码找数字强太多。
2.2 画布尺寸、相机设置与地平面高度
我设计的竖屏画布是 480×720 像素,这种比例和经典玩法接近,也方便在手机上预览。正交相机放在原点,世界坐标直接对应像素坐标,省去做坐标转换的麻烦。
地平面高度设在地图 y=600 的位置。也就是说,小鸟在竖轴上从 0 开始往下掉,掉到 y=600 就会撞地。天空区域留白很大,让管道的随机位置有充足的上下空间。
这些看似不起眼的坐标约定,在 AI 生成代码时必须写清楚。否则 AI 很可能会默认“地面就是屏幕底部”,然后用screen_height动态计算,结果画布尺寸一改,碰撞判定就乱套。固定坐标虽然不够灵活,但在原型阶段能大幅减少变量。
2.3 第一次和 AI 协作:生成初步场景
接着我给出了第一份提示词,让它生成一个带背景和地面层级的场景:
请用开维引擎生成一个 FlappyBird 风格场景: 1. 创建主场景,画布 480x720,正交相机; 2. 背景色为浅蓝色渐变,用 2-3 个层叠矩形从下往上渐变; 3. 在 y=600 创建地面实体,渲染为绿色横条,宽 480,高 60; 4. 创建一个空实体 “BirdAnchor”,作为小鸟的挂载点。AI 很快生成了场景结构,但它的背景渐变是用三个固定色块硬拼的,拼接处能看到明显色差。这个我不会去手工调整颜色——对于原型阶段来说,有背景颜色分层就够了,等手感调好再换美术资源也不迟。
生成场景骨架之后,我检查了一下层级关系。BirdAnchor被正确创建,地面实体没有透视关系,相机参数也对。这个阶段的重点是确保实体树清楚,AI 写逻辑时知道该往哪个节点上挂组件,而不是把代码写错对象。
3. AI 生成核心玩法:把“游戏规则”翻译成人话再翻译成代码
3.1 像写验收标准一样写提示词
在真正写代码之前,有一个步骤决定成败:把玩法规则整理成一段可以被验收的“需求说明”。AI 不懂“手感好”这种主观描述,它需要数字、边界条件和行为顺序。
我给 AI 的核心提示词是这样写的:
请生成 scripts/bird.py: - 组件挂在 BirdAnchor 实体上; - 包含重力加速度 320px/s²、初始速度为 0; - 每次点击/按空格,立刻把垂直速度设为 -100px/s; - 只在 GameController.state == PLAYING 时更新位置; - 旋转角度限制在 -25 到 80 度之间; - 所有数值从 data 配置读取,不要写死。这比直接说“实现一只鸟的飞行动作”要可靠得多。原因很简单:AI 生成代码本质上是概率预测,描述越不精确,它越可能用“常见飞行动作”的思路去套,而不是你的思路。把数字写清楚,AI 的搜索空间就被锁定了。
3.2 小鸟的运动组件:重力与跳动
AI 生成的鸟组件核心逻辑如下,我稍微整理过:
# scripts/bird.py class Bird(Component): def start(self): self.velocity = 0.0 self.gravity = cfg.gravity self.flap_speed = cfg.flap_speed self.entity.y = cfg.bird_start_y def update(self, dt): if Game.state != GameState.PLAYING: return # 速度随时间增加,模拟重力 self.velocity += self.gravity * dt self.entity.y += self.velocity * dt # 角度反馈,向上时抬头,下落时低头 angle = clamp(self.velocity * 0.2, -25, 80) self.entity.rotation = angle def flap(self): self.velocity = self.flap_speed这段代码有几个关键点我需要仔细核对。首先是self.velocity += self.gravity * dt,这用的是“速度积分”而不是直接改坐标。如果 AI 直接写entity.y += gravity * dt * dt,那会造成二次方位移,整体下落会慢得离谱。
其次是dt的使用。所有跟时间相关的更新都必须乘dt,否则游戏在不同帧率下速度不一样。后面排错篇章我会专门讲这个问题。
3.3 管道生成器:随机性与节奏控制
管道生成器的逻辑比小鸟运动复杂一些。它需要在固定间隔生成一对上下管道,管道之间留一个可以让鸟穿过的洞,同时整个管道组以恒定速度向左移动。
我让 AI 生成的逻辑是这样的:
# scripts/pipe_spawner.py class PipeSpawner(Component): def start(self): self.timer = 0.0 self.spawn_interval = 1.5 self.pipe_speed = 120.0 self.hole_height = 150.0 self.min_hole_center_y = 180.0 self.max_hole_center_y = 420.0 def update(self, dt): self.timer -= dt if self.timer <= 0: self.timer = self.spawn_interval center_y = random.uniform(self.min_hole_center_y, self.max_hole_center_y) self.spawn_pipe_pair(center_y) def spawn_pipe_pair(self, center_y): top_pipe = self.create_pipe( y=center_y - self.hole_height / 2, anchor="bottom") bottom_pipe = self.create_pipe( y=center_y + self.hole_height / 2, anchor="top") group = self.entity.scene.create_entity("PipeGroup") group.add(top_pipe) group.add(bottom_pipe)这里最值得留意的是锚点处理。上管道应该从洞口顶部向上延伸,下管道从洞口底部向下延伸。如果用中心点坐标去放管道,边缘很难对齐。我给提示词里特意加了“锚点”两个字,AI 才给出正确的实现。
下面这张表列出了我在配置文件中使用的核心数值,也是后续调手感的起点:
| 配置项 | 数值 | 含义 | 调整方向 |
|---|---|---|---|
| gravity | 320 px/s² | 下落加速度 | 数值越大,下落越快 |
| flap_speed | -100 px/s | 跳跃瞬间速度 | 绝对值越大,跳得越高 |
| pipe_speed | 120 px/s | 管道左移速度 | 越大,难度越高 |
| spawn_interval | 1.5 s | 管道生成间隔 | 越小,节奏越紧 |
| hole_height | 150 px | 管道洞口高度 | 越小,难度越高 |
这套数值并不是凭空定的。我心里有个基准:整个游戏过程中,鸟的正常下落速度大约 200~400 px/s,管道左移速度决定反应时间,1.5 秒一根管的节奏对新手来说比较友好。
4. 碰撞检测、计分与状态管理:AI 最容易写崩的三块硬骨头
4.1 碰撞检测:视觉矩形不等同于碰撞矩形
AI 生成的碰撞检测通常是标准的 AABB(轴对齐包围盒)相交判断,这个几乎不会错。但它最容易错的地方在于:什么时候判断碰撞,碰撞范围到底取多大。
我让 AI 生成了如下检测逻辑:
def is_colliding(rect_a, rect_b): return not ( rect_a.right <= rect_b.left or rect_b.right <= rect_a.left or rect_a.bottom <= rect_b.top or rect_b.bottom <= rect_a.top )这个函数本身完全正确。但真正的问题出在碰撞矩形上。如果直接用小鸟图片的整个矩形去做碰撞,玩家会明显感觉“明明没碰着管道却死了”,因为图片透明部分也被算了进去。正确的做法是给小鸟的碰撞盒做内缩,通常缩到图片尺寸的 70% 左右,给玩家留一点容错空间。
我给 AI 的后续提示是:
- 小鸟的碰撞盒尺寸为 40x32,而不是图片尺寸; - 管道碰撞盒宽度为 70,高度用实际管道高度; - 地面碰撞盒从 y=600 开始向上; - 碰撞到地面或管道时,切换到 OVER 状态。有了这些边界设定,碰撞的“手感”才开始靠谱。
4.2 状态机:避免多个布尔变量互相打架
AI 很常见的一个问题是,用is_ready、is_playing、is_over三个布尔变量分别表达状态,结果逻辑一旦复杂,就会出现“又 ready 又 over”的荒谬情况。我在提示词里直接要求它用一个枚举:
class GameState: READY = 0 PLAYING = 1 OVER = 2然后所有的状态判断都围绕着Game.state。这一招立竿见影。AI 生成的代码里,所有跳跃、重力、管道生成、计数的逻辑都先检查Game.state,逻辑链路清晰很多。
下面是我实际使用的状态转换规则:
- 初始进入 READY:鸟悬浮在空中,不落体不动;
- 第一次点击:进入 PLAYING,鸟开始受重力影响;
- 碰撞或落地:进入 OVER,鸟停止操作,播放撞击效果;
- 点击重开按钮:清空管道,分数归零,回到 READY。
这套状态机在群里被大家称为“最不 AI 的 AI 代码”,因为每个转换都显式、可追踪,没有任何模糊的布尔组合。
4.3 得分逻辑:什么时候算“通过了一个管道”
这是整段代码里 AI 第一次给我挖坑的地方。它第一次生成的计分逻辑是:当鸟的碰撞盒和管道碰撞盒重叠的那一帧,分数加一。这显然不对,因为鸟碰到管道就直接死了,它永远不可能靠“碰撞”来得分。
正确的得分时机,应该是鸟的 x 坐标已经越过了某个管道的右边缘,并且这个管道还没有被计过份。我给出的实现是:
# scripts/pipe.py class Pipe(Component): def start(self): self.scored = False def update(self, dt): if self.scored: return bird_x = Game.bird.entity.x if bird_x > self.entity.x + self.width: self.scored = True Game.score += 1 Sound.play("score")这里关键点在于给每个管道挂了一个scored标签。这个标签的作用是防止重复计分——鸟在两根管道之间停留时,如果每帧都判断为“已经越过”,分数就会不停往上跳。AI 第一次没考虑到这一点,我后来在排错时补上了。
5. 资源、音效与手感打磨:AI 生成的玩法,还得靠人补细节
5.1 用程序化图形代替美术资源
做原型的时候,我没有去找任何外部美术素材,直接用引擎的绘图接口生成了所有图形。背景是三层渐变的矩形,地面是纹理平铺的绿色长条,小鸟就是一个白色矩形加一个橙色三角形嘴巴,管道的绿色圆柱体也是用细长矩形拼出来的。
用程序化图形的最大好处是零版权风险、零加载耗时,饿死哪里都能跑。但缺点也很明显:画面平淡、层次感弱。我给自己定的标准是“先能玩,再能看”,等玩法验证完成了,再让 AI 生成资源命名规范和加载流程,方便后续替换成正式美术。
5.2 手感的数值调优
手感是最难量化、也最需要人力介入的部分。我前后调整了三轮数值:
- 第一轮:重力 320,跳跃速度 -100,管道间隔 1.5 秒,玩起来鸟有点“飘”;
- 第二轮:重力提高到 380,跳跃速度 -120,落点更干脆,但跳跃高度变化不够明显;
- 第三轮:把跳跃加速度改为“脉冲式”——每次点击先给一个小幅向上的速度,再叠加轻微向上加速度,视觉上鸟会有一个微微抬头再下落的过程。
其实整个调参过程完全可以交给 AI 去做,比如让它生成十几个参数组合,但前提是你得明确“手感”的数学模型。现阶段我还没有那么精细的量化指标,所以这块仍然是人肉测试。
5.3 音频反馈:短促而有辨识度
音效我用了两个办法生成:一个是用引擎内置的合成器接口,播放短促的方波音调;另一个是直接导入几个公开版权的短音频文件。在 AI 提示词里,我要求它把音效调用封装成独立接口,例如Sound.play("wing")、Sound.play("score")、Sound.play("hit"),然后由音效管理脚本统一加载。这样做的好处是后续替换音频文件不需要改动逻辑代码。
我实际用的三个音效都很短:跳跃是一声清脆的“咻”,得分是一声上扬的“叮”,撞击是一声低频的“砰”。这三个反馈清晰地告知玩家当前事件,而且不干扰操作注意力。
6. 排错实录:AI 生成的代码里我实际踩过的五个坑
6.1 管道自己会动,但还是跟场景坐标打架
第一版管道的生成逻辑是每根管道自己在 update 里修改世界坐标。看起来没问题,因为管道确实在向左移动。但问题出现在相机有轻微位移模拟的时候——当场景里同时存在相机滚动和管道自移动,管道组与背景的移动速度对不上,产生严重的“漂移感”。
修复方法是把管道移动统一放到一个“管道组”的父实体上,让父实体统一左移,子管道只维护相对坐标。这样视角移动和管道移动互相独立,逻辑也很清晰。
6.2 重新开局后,上一局的管道残留导致“突然暴毙”
AI 在生成 restart 逻辑时,只重置了鸟的坐标和分数,却忘了清理场景中已存在的管道实体。结果玩家第二次开局时,上一局的管道还留在屏幕上,直接撞死。
我让 AI 在生成所有管道时,把管道实体加入一个pipe_collection列表,在restart()里遍历并移除。这一步很关键,必须在提示词里显式要求:
- 生成的管道必须加入场景中的 PipeCollection 列表; - restart 时先清除 PipeCollection 中所有实体,再重置分数和鸟坐标。6.3 帧率不稳导致难度漂移
AI 第一次生成的移动逻辑里,管道每帧固定移动 7 像素。这在一台稳定 60 帧的机器上没问题,但一旦掉到 30 帧,游戏速度立刻下降一半,玩家会明显感觉“节奏变慢了”。
我把它改为每帧移动120 * dt像素,即先乘以每帧间隔时间,再累加到位移量。这一点在多个游戏框架里都是老生常谈,但 AI 生成代码时经常会“忘记”乘dt,生成完必须逐行检查。
6.4 高速下落时“穿过”管道
把帧率调到 30 帧测试时,鸟下落速度最快能到 500 px/s,每帧移动约 17 像素。而管道的碰撞盒厚度只有 70 像素,理论上不会穿模。但 AI 生成的小鸟碰撞盒比较小,管道和鸟的厚度加总后,在极端情况下仍可能出现检测不到碰撞的一帧。
这里我没有走连续碰撞检测的复杂路线,而是采用了一个更务实的方案:把碰撞检测从“每帧检测一次”改成“按固定时间步长做两次子级检测”,同时适当扩大碰撞盒宽度到和管道一致。对原型来说,这个方案已经完全够用。
6.5 AI 把“像素”和“逻辑单位”混为一谈
我在 480×720 画布上开发,AI 生成的某些随机范围用的是“0~1000”这种和画布无关的值,导致管道经常生成在画面外,或者洞口偏离可玩区域。反复修正太耗费时间后,我改变了策略:在提示词里把所有数字单位统一写成“画布像素”,并给出画布尺寸作为前提。这个提示词上的小细节,让后续生成的代码正确率肉眼可见地上升。
7. 和 AI 协作生成游戏代码的一点心得
这次项目做下来,我对“ AI 自动生成游戏代码”这件事有了更具体的认知。
AI 很适合承担那些“模式化表达”的工作:把核心算法转成代码、生成重复的碰撞组件、把 JSON 配置读取逻辑补全。但它的输出质量高度依赖“需求输入的清晰度”。你给它的提示词越接近一份验收标准,它给你的代码就越接近可运行的产品。我通常的做法是,先写出一段 30 行左右的需求说明,再让它生成代码,跑通之后再让 AI 自己补充注释和重构。
人工审查环节不能省。我见过很多人把 AI 生成的代码直接丢进项目,结果逻辑混乱、资源泄漏、甚至安全漏洞。正确对待 AI 代码的方式,是把它当成刚入职的初级开发者写的代码来 review。每一行你都要理解它的意图,对不上的地方改掉,而不是因为“这代码是 AI 写的”就默认它是对的。
数值配置独立出来是这次最值的一步。重力、管道速度、洞口高度全部放进配置文件之后,调手感只需要改 JSON,再配合 AI 生成的“参数读取组件”,整个项目变成了一个可以被快速试错的原型工具。后续我可以在这个基础上扩展更多玩法,比如加不同颜色的管道皮肤、全球排行榜、甚至把这一套流程复用到其他小游戏上。这些扩展现在不是没头绪的事,而是有了一条清晰的、经过验证的路径。