1. 为什么需求文档是AI协作的"紧箍咒"
上周在咖啡厅见到老同事王工,他正对着屏幕上一堆杂乱无章的代码抓耳挠腮。"让Claude帮我写个回合制游戏,结果生成的都是些什么啊!"他指着屏幕上四不像的代码抱怨道。这让我想起去年用AI开发Tiny Civ时踩过的坑——直到我学会用需求文档约束AI,开发效率才真正起飞。
游戏开发中最贵的成本不是写代码,而是返工。当我在Claude中输入"帮我写个文明类游戏"时,它给出了一个能运行的Python版本——但单位移动逻辑放在客户端、战斗计算却在服务端,存档系统用JSON直接存储整个地图状态。这些架构问题在原型阶段并不明显,但当游戏规模扩大后就会变成灾难。
经验之谈:AI生成的代码就像乐高积木,单个模块可能很精美,但缺乏整体蓝图就会变成一堆积木块。而需求文档就是这个蓝图。
1.1 AI编码的三大幻觉症状
在与Claude合作开发Tiny Civ的过程中,我总结出AI辅助开发时最常见的跑偏模式:
- 过度联想症:当提示词说"实现科技树"时,Claude可能会加入魔法系统——因为它从其他游戏代码中学习到"科技"常与"魔法"并列出现
- 细节膨胀症:要求"战斗系统"可能得到包含20种状态效果的复杂实现,而实际上我们只需要基础攻击力对比
- 架构失忆症:不同会话中生成的模块会使用不一致的数据结构,比如单位坐标有时用(x,y)元组,有时用{"x":,"y":}字典
这些问题的根源在于,AI没有项目全局视角。就像让十个程序员各自开发不同模块却不给设计文档,最终整合时必然灾难。
1.2 航空级需求文档的特征
NASA在开发航天软件时,需求文档要满足"航空级"标准——即任何工程师拿到文档都能开发出符合预期的组件。在与Claude协作时,我发现这类文档需要三个关键要素:
| 要素 | 普通需求文档 | 航空级需求文档 | 案例(Tiny Civ移动系统) |
|---|---|---|---|
| 输入边界 | "单位可以移动" | "单位每回合移动力=3,移动消耗:平原=1,山地=2,河流=3" | move_cost = {'plain':1, 'mountain':2, 'river':3} |
| 状态约束 | "不能穿过敌人" | "移动路径计算时过滤掉occupation=True的格子" | if not grid[x][y]['occupation'] |
| 异常处理 | "处理异常情况" | "移动力不足时返回'INSUFFICIENT_MOVEMENT'" | raise GameError('INSUFFICIENT_MOVEMENT') |
这种级别的约束才能让Claude生成的代码真正可用。上周我让实习生测试这个方式:一组用自然语言描述需求,另一组用结构化文档。结果后者生成的代码首次运行通过率提高了67%。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 如何编写AI能理解的需求文档
去年在开发Tiny Civ的资源系统时,我经历了三次重构才明白:给AI的需求文档和人看的完全不同。传统PRD中的用户故事和流程图对Claude几乎无用,它需要的是可执行的数学表达。
2.1 从用户故事到状态方程
普通需求会这样描述资源系统:
"玩家可以采集资源,资源用于建造建筑和训练单位"
这对AI来说太模糊了。最终有效的写法是:
python复制class ResourceSystem:
# 资源类型及初始存量
RESOURCE_TYPES = {
'gold': {'storage': 1000, 'yield': 10},
'wood': {'storage': 500, 'yield': 5}
}
# 消耗规则
BUILDING_COST = {
'farm': {'wood': 50},
'barracks': {'wood': 100, 'gold': 200}
}
# 更新规则:每回合自动增加yield值
def update_turn():
for r in RESOURCE_TYPES.values():
r['storage'] += r['yield']
这种类Python的伪代码格式,Claude理解起来准确率最高。实测表明,用这种写法生成的资源系统代码,与预期设计的吻合度达到92%,而自然语言描述只有35%。
2.2 接口设计的黄金法则
在定义建筑系统接口时,我总结出三条AI友好规范:
- 方法签名包含完整类型提示:
typescript复制interface IBuilding { getProductionBonus(resourceType: 'gold'|'wood'): number; canBuild(location: {x:number, y:number}): boolean; } - 用枚举替代自由字符串:
python复制class UnitType(Enum): WORKER = auto() SOLDIER = auto() - 预定义所有错误码:
json复制{ "ERR_INSUFFICIENT_RESOURCES": 1001, "ERR_INVALID_LOCATION": 1002 }
这样约束后,Claude生成的建筑类会自动实现这些接口,且不同会话间保持一致性。有个反例:之前没定义TerrainType枚举时,有的模块用"mountain",有的用"MT",导致地图渲染崩溃。
2.3 测试用例即需求
最有效的需求文档形式其实是测试用例。在开发战斗系统时,我直接给Claude看这个:
python复制def test_battle():
# 测试用例1:攻击力高的单位应该获胜
attacker = Unit(strength=5)
defender = Unit(strength=3)
result = Battle.resolve(attacker, defender)
assert result.winner == attacker
# 测试用例2:地形加成影响结果
attacker = Unit(strength=5)
defender = Unit(strength=5, terrain_bonus=0.2)
result = Battle.resolve(attacker, defender)
assert result.winner == defender
Claude根据这些测试反推出的战斗系统,第一次就通过了所有边界条件检查。这比用文字描述"战斗要考虑攻击力和地形加成"有效得多。
3. Tiny Civ开发中的需求文档实践
实际开发中,我发现不同类型的功能需要不同形式的需求文档。以下是Tiny Civ中三个典型系统的文档范例。
3.1 回合系统:状态机表示法
回合制游戏的核心是状态流转。用有限状态机描述比文字更清晰:
code复制[玩家回合开始]
-> 资源产出
-> 等待指令
-> [移动单位]: 消耗行动点
-> [建造建筑]: 检查资源并消耗
-> 结束回合按钮点击
[AI回合开始]
-> 执行AI策略树
-> 自动结束
配合状态转移条件表:
| 当前状态 | 触发条件 | 下一状态 | 执行动作 |
|---|---|---|---|
| 等待指令 | 单位移动指令 | 等待指令 | 更新单位坐标,扣除移动力 |
| 等待指令 | 剩余移动力=0 | 回合结束待确认 | 显示结束回合按钮 |
| AI回合开始 | 策略树执行完毕 | 玩家回合开始 | 重置所有单位行动点 |
用这种文档生成的回合控制系统,首次运行就正确处理了连续点击结束回合的边界情况。
3.2 科技树:邻接矩阵定义
科技树最容易出现AI过度设计的问题。我的解决方案是用矩阵明确定义前置关系:
python复制tech_tree = {
# 科技ID: [所需前置科技ID...]
'wheel': [],
'writing': [],
'philosophy': ['writing'],
'mathematics': ['writing'],
'engineering': ['wheel', 'mathematics']
}
再加上每个科技的效果明确定义:
python复制tech_effects = {
'wheel': {'unit_movement_bonus': 1},
'engineering': {'allow_buildings': ['aqueduct']}
}
这样生成的科技系统代码既不会少功能,也不会多出奇怪的效果。有个对比:之前用"研发轮子提升移动力"这样的描述时,Claude给骑兵也加了移动加成——因为它从历史数据中学习到轮子与骑兵的关联。
3.3 存档系统:数据schema规范
最灾难的一次是存档系统重构。起初只说了"要能保存游戏状态",结果生成的存档包含整个UI状态——包括某个弹窗的临时滚动条位置。现在我会给出完整的JSON Schema:
json复制{
"version": "1.0",
"required": ["players", "map", "turn"],
"properties": {
"players": {
"type": "array",
"items": {
"resources": {"type": "object"},
"techs": {"type": "array"}
}
},
"map": {
"tiles": {"type": "array"},
"units": {"type": "array"}
}
}
}
配合示例数据:
json复制{
"version": "1.0",
"players": [{
"resources": {"gold": 100},
"techs": ["writing"]
}],
"map": {
"tiles": [{"type": "plain", "coord": [0,0]}],
"units": [{"type": "worker", "coord": [0,0]}]
}
}
这种文档下生成的存档代码,序列化/反序列化一次通过,且存档大小从原来的2MB降到50KB。
4. 需求驱动的AI协作工作流
经过六个版本的迭代,我总结出与Claude协作开发游戏的高效流程。关键在于把需求文档作为唯一真相源。
4.1 文档版本控制策略
在Git仓库中这样组织:
code复制docs/
├── requirements/
│ ├── v1.0/
│ │ ├── economy.md
│ │ └── combat.md
│ └── v2.0/
│ ├── economy.md
│ └── diplomacy.md
src/
└── generated/
├── v1.0/
└── v2.0/
每次大版本更新时:
- 复制旧需求文档到新版本目录
- 用
diff工具标出修改处 - 让Claude根据差异生成迁移代码
这比直接说"我们要改经济系统"可靠得多。有一次从v1到v2的资源系统改造,手动改要3天,而用差异文档指导Claude只用了4小时。
4.2 会话管理技巧
每个功能模块使用独立的Chat会话,并在首条消息嵌入需求文档:
markdown复制# Tiny Civ 战斗系统规范 v1.2
## 输入
- 攻击方: {type: Unit, strength: int}
- 防御方: {type: Unit, strength: int, terrain: enum}
## 输出
- {winner: Unit, damage: int}
## 规则
1. 基础伤害 = max(1, 攻击方.strength - 防御方.strength)
2. 地形加成: 山地 +2防御
然后在该会话中只讨论战斗系统问题。这能保持AI的"上下文专注度"。测试显示,专用会话的代码一致性问题减少78%。
4.3 验证闭环构建
最关键的环节是建立验证闭环:
code复制生成代码 -> 自动化测试 -> 差异报告 -> 修正需求
我的做法是在CI流水线中加入Claude验证环节:
- 用pytest运行生成的代码
- 将失败用例格式化后发给Claude
- Claude分析失败原因并给出修正建议
例如当测试报错"骑兵在山地不应有加成"时,Claude会自动定位到:
diff复制- if unit.type == 'cavalry': bonus += 1
+ if unit.type == 'cavalry' and terrain != 'mountain': bonus += 1
这种闭环让Tiny Civ的AI生成代码缺陷率从32%降到5%以下。
