我先说个这两年做 LLM 应用最明显的感受:大部分人刚上手时,都喜欢把大模型当成“高级文本装饰器”,逻辑写死,分支写死,再让模型往壳子里填几句台词。放到游戏玩法领域,这种思路很快就会撞墙。游戏最值钱的东西是“可玩性”,可玩性来自选择和反馈。如果选择全是脚本写好的,模型再会写,也只是在各种结局里换一层皮。于是我开始尝试另一种做法:把一部分玩法控制权交给 LLM,让它的输出能直接改变游戏状态。这个方向现在有个很形象的说法——从“收权”到“放权”。
所谓收权,是把大模型严格限制在文本生成层,世界规则、数值变化、剧情走向全部由人等设计好;放权,则是让大模型参与游戏运行的实时决策,根据玩家输入生成事件、修改角色关系、触发资源变化,甚至影响整个世界观的演化。它解决的问题很直接:传统分支叙事成本高、内容不可复用、玩家重复体验时的“天花板感”很强;而放权后的 LLM 玩法有机会做到千人千面、可重玩、可涌现。这篇文章的核心不是教你调 prompt,而是一套更接近工程化的思考:LLM 进了玩法之后,哪些权该收、哪些权该放、怎么用工具调用和知识库兜底、怎么控制 token 上下文、怎么避免模型把剧情说崩。
如果你是做剧情驱动游戏、模拟经营、开放世界、AI NPC、或者想研究“LLM 智能体玩法”的开发者,这篇文章可以当一份实操参考。即便你只是对游戏 AI 感兴趣,读完也能理解为什么现在圈子里都在聊“LLM Agent 游戏”而不是“聊天机器人游戏”。
1. 从“收权”到“放权”:LLM 进入玩法的核心逻辑
1.1 两种集成模式:模型当音箱,还是模型当乐手
我习惯把 LLM 接玩法分成两档。第一档叫“收权模式”,模型是音箱,负责把设计好的内容播放出来。典型场景是传统 CRPG 对话:玩家点选项,脚本判断条件,返回固定台词,模型只负责润色文字,最多做一点即时翻译。这种模式的好处是稳定、可控、便宜,坏处是内容生产仍然靠人肉堆量,且玩家只要试过一轮完整流程,就很难再有惊喜。
第二档叫“放权模式”,模型是乐手,可以即兴发挥,甚至改变整首曲子的走向。比如让 NPC 不止是复读台词,而是根据自己的记忆、状态、对玩家的好感度,实时调整说话内容和交易条件;让游戏事件生成器根据玩家的行为惯性,动态设计下一阶段的挑战;让整个城市势力之间的关系,在玩家的推动下重新洗牌。这里模型的输出不再只是“给玩家看的话”,而是可以被游戏系统解析、校验、执行的状态变更指令。
但这里有个很容易走偏的地方:放权不等于撒手不管。最危险的实现方式,是把游戏世界状态全部塞给模型,让模型自由修改,那结果一定是指数级增长的逻辑 bug。真正稳的做法是“放权但有边界”,模型负责提案,游戏引擎负责审批执行。我在 1.2 和第三章会反复强调这个原则。
1.2 放权带来的设计红利与失控风险
先聊聊红利。第一是玩法空间和成本不再线性绑定。传统对话的每一句分支都要设计、配音、写触发条件;而 LLM 可以把一部分内容生成成本转移给模型,让一个人一天生成的变量,等于过去一个编剧小组一个月的工作量。第二是可重玩性明显提升。同样开局,玩家这次说“我不愿意给钱”,下次说“我愿意先支付一半”,模型给出的反馈可能完全不同,场景状态也可能朝向不同方向演化。第三是涌现趣味。玩家会尝试设计者没想到的策略组合,模型理解意图后能给出比固定选项更灵活的回应,这种超出脚本的响应,往往就是玩家眼里的“自由度”。
再谈风险。最明显的是失控,模型经常自己打自己脸。前一轮 NPC 还咬牙切齿要复仇,下一轮就把玩家当恩人。典型原因是模型没有真正读取世界状态,只靠着上下文里的零散对话在编故事。第二个风险是成本不可控。如果每个 NPC 每轮交互都调用大模型,token 会像流水一样烧掉,一局游戏下来甚至可能超过正常付费额度。第三个风险是输出格式不规范。一次工具调用层出的错误,可能让整个游戏状态崩溃或逻辑上产生重复扣血、双倍奖励之类的问题。
所以我的结论是:放权必须建立在收权能力增强的基础上。你越能给模型提供精确的知识检索、严格的结构化输出、完整的状态校验,你就越敢放权给模型。收权和放权不是对立面,它们是一套天平,核心看你能不能把“不确定性”控制在可接受的范围内。
1.3 放权路线图:从填空对话到世界模型
如果你想把 LLM 逐步引入玩法,我建议按这条路线推进,每一步的放权程度递增:
- 第一步:剧本填空。模型只生成当前对话中的一小段,不参与任何状态变更。
- 第二步:对话增强。模型根据玩家自由输入,从多个预设事件中选一个触发,仍然由规则决定结果。
- 第三步:规则协作。模型生成结构化意图和参数,游戏引擎执行数值变化、掉落、好感度变化。
- 第四步:世界演化。模型不仅生成事件结果,还生成后续事件的候选条件、NPC 目标变化、势力关系调整,由策划配置的边界做校验。
目前的成熟案例大多停在第三步到第四步之间。哪怕是开放世界大作,也没有让模型直接无约束改文件,而是把模型包裹在“事件提案层”里。这也给了我一个判断标准:如果某个游戏里 LLM 完全自由生成世界状态,而且没有状态校验,我基本会认为它是个演示 Demo,不是可落地的工业化方案。
2. 技术底座:用什么支撑“有边界的放权”
2.1 token 三元组:key、query、value 在游戏中的具体用法
先把这个概念说透。很多人在玩 LLM 时都会听到一句话:token 的三个关键点是“key 我是谁、query 我在找什么、value 我能提供什么”。不完全是术语表,它其实是一套语义结构,放到游戏里特别清晰。
key 是实体和状态的标记。游戏里的 NPC、物品、任务、大事件,都应该有唯一的 key,比如npc_merchant_01、state_trust_merchant、event_bargain_01。模型在生成时,提到这些 key 就等于引用游戏世界里的真实数据。query 是当前输入的意图,也是模型需要理解的目标。玩家说“你价格太高了吧,能不能便宜点”,query 可能是“压价谈判”,也可能是“威胁 NPC”,需要靠意图识别区分。value 是模型根据 key 和 query 给出的可行方案,比如降价幅度、好感度变化、可能的后续事件。
实操中我最常用的落地方式,是把这三元组做成一张内部表:先让模型从玩家输入中抽取 query,然后用 query 去检索游戏知识库里相关的 key,最后让模型生成基于 value 的行为提案。这一步非常接近 RAG 的做法,但比通用 RAG 多了“状态变更”的含义。它让模型不再是凭空编,而是围绕游戏世界已有的资产做推断。
2.2 不要让模型裸奔:RAG、本体论与 GraphRAG 兜底
游戏里的内容一致性,很多时候不是靠 prompt 那几行字能保住的。角色有几十个,世界观有几百条,事件链条有两千多字,全部塞进上下文既不现实也没必要。这时候就要靠知识库和检索增强生成来兜底。
我建议游戏项目把知识层做成三层。第一层是基础 RAG,把设定文档、角色档案、物品说明切成块,用向量检索召回最相关的部分,保证模型写对话时引用正确。第二层是本体层,也就是 ontology,把实体之间的关系结构化:谁是谁的下属、哪个阵营和哪个阵营敌对、哪些道具受哪个 NPC 信任影响。这层一旦配好,模型在生成复杂剧情时不至于把敌对关系说成联盟关系。第三层是 GraphRAG,适合处理跨实体推理。比如玩家问“如果我把城主的密信交给铁匠,会发生什么?”,普通 RAG 只能检索到密信和铁匠的独立信息,GraphRAG 可以通过关系路径推理出“铁匠实际上是城主失散兄弟”这种隐藏关系,让剧情展开更可信。
现在很多人还在用“llm wiki 知识库”这类资料集来给模型补知识,其实玩法项目也是一样的逻辑。区别在于游戏知识库还要考虑版本化。策划改了一个角色设定,知识库必须同步更新,不能让模型还在引用旧文案。
2.3 工具调用与自主智能体的边界
放权放到最后,模型要有手有脚。也就是说,它不能只说句话,还得能发起查询、修改临时状态、申请执行某个行为。这就涉及工具调用和自主智能体。
我的做法是把工具分成三类。第一类是查询工具,模型可以调用,去查当前 NPC 状态、物品栏、关系网。第二类是事件提案工具,模型生成一个事件请求,其中包括意图、参数、预期影响,然后交给游戏引擎校验执行。第三类是重试工具,模型发现自己说的内容和已知事实冲突时,可以主动重新检索或者要求人类玩家澄清。
需要强调的是“边界”。模型永远不应该直接修改数据库,否则一个 prompt 注入或者一次幻觉就能炸掉整个存档。它应该只能“提交变更”。引擎侧做一个权限控制,把模型提议的数值变化限制在上下限里,比如每次谈判好感度变化不超过 ±15,一次交易金额调整不超过商品均价的 30%。这就是把规则收回来,把表达和推演放出去。
之所以要做这个设计,是因为 LLM 驱动的自主智能体本质上是个概率系统。概率系统在表现好时确实很像人,但在出错时也会出得很离谱。如果你不给边界,它会把一次普通对话玩成世界末日。
3. 实操记录:从最小原型到稳定玩法循环
3.1 搭建一个最简的“谈判事件”原型
我们做一个最简案例:模拟经营游戏里,玩家和商人 NPC 谈判采购价格。传统写法是给商人写三到四句分支对白,玩家选固定选项。现在改成由 LLM 参与,玩家可以自由输入任何话术,模型负责解析意图并生成行为提案。
我用 Python 写的原型可以压缩成几个核心函数。模型侧使用支持工具调用/function calling 的接口,先把事件 schema 发给模型,让它决定该触发什么变更。
def negotiation_event(player_input, npc_state): schema = { "type": "function", "function": { "name": "apply_negotiation_proposal", "parameters": { "type": "object", "properties": { "intent": {"type": "string", "enum": ["bargain", "threat", "flattery", "buyout"]}, "price_modifier": {"type": "number"}, "trust_delta": {"type": "number"}, "diplomacy_delta": {"type": "number"}, "facts_to_check": {"type": "array", "items": {"type": "string"}}, "npc_response": {"type": "string"} }, "required": ["intent", "price_modifier", "trust_delta", "diplomacy_delta", "npc_response"] } } } result = llm.chat_with_tools( system_prompt=build_system_prompt(npc_state), user_message=player_input, tools=[schema] ) proposal = parse_tool_call(result) return validate_and_apply(npc_state, proposal)这个简单的框架已经把“收权”和“放权”分开了:模型负责出方案,代码负责做约束。npc_state是游戏世界里商人当前的状态,包括存货、底价、信誉度、对玩家的印象。facts_to_check是模型自己提出的校验项,引擎侧可以针对这些项去做一致性检查。
3.2 关键参数与上下文预算:从拍脑袋到算得清
原型的第二个关键点,是控制好 temperature、top_p 和上下文预算。很多入门者喜欢把 temperature 开到 0.9,觉得这样“有创意”。但在玩法系统里,创意和稳定性要分场景。我的经验是:
- 生成 NPC 台词、剧情描述、玩家失败后的备选路线:temperature 0.8,希望有意外感。
- 意图解析、工具参数提取、状态判断:temperature 0.1 甚至 0,不能让它自由发挥。
- 两者可以拆成两次请求,而不是用同一个高 temperature 参数完成“理解+生成”两个任务。目标识别错了,台词再有趣都没意义。
上下文预算也要算清楚。以一次谈判事件为例,我按下面这个预算表估算 token 消耗:
| 内容模块 | 预估 token |
|---|---|
| System Prompt 与角色设定 | 800 |
| 当前世界状态摘要 | 1000 |
| 长线记忆(最近 10 轮关键决策) | 800 |
| 短期事件上下文(最近 5 句对白) | 1200 |
| 模型输出(状态提案 + 对白) | 500 |
| 合计 | 4300 |
在这个预算下,一个普通的 8k 上下文模型还能运转;但如果 NPC 很多、世界场景很大,或者玩家连续玩了半小时,堆上下文会快速超限。所以我在原型阶段就给每条上下文加了失效时间:超过 30 分钟未活跃的世界状态自动降级成大摘要,避免把过期记忆当成最新事实。
3.3 把输出约束变成玩法规则
模型输出的不是“一串话”,而是一个行为提案。为了让引擎能执行,我用 JSON schema 把它约束成固定的结构。引擎拿到提案后,会做三件事:第一,校验数值是否在允许范围内;第二,检查引用的事实是否存在;第三,把变更当前状态的操作写入本地事务,保证中途失败时不留下半截状态。
实际运行一次谈判。玩家输入:“老板,这批货有个瑕疵,你看外观都划花了,你按半价给我行不行?”模型可能返回:
{ "intent": "bargain", "price_modifier": -0.2, "trust_delta": 3, "diplomacy_delta": 8, "facts_to_check": ["goods_quality_score"], "npc_response": "划痕确实有,但半价哪做得下去,最多给你让两成,就当交个朋友。" }引擎侧会先检查goods_quality_score当前值。如果质量分很高,说明玩家在说谎,模型提案里的trust_delta就被扣掉,NPC 甚至会改口。这是一个很关键的机制:模型可以基于玩家话术生成一个看似合理的交易条件,但最终能否成立,由游戏世界的客观状态决定。玩家对世界状态的操纵,也被纳入“收权”范围,防止模型被一句话带跑。
3.4 记忆与一致性:从一层 session 记忆到分层记忆
游戏玩法的记忆系统和聊天机器人的记忆完全不同。聊天机器人记住“你上次说你养猫”就够了,游戏里的记忆要影响胜负、资源和剧情走向。我在原型的第四个阶段,把记忆从单层 session 升级成三层。
第一层是短期对话记忆,存最近几轮交互,保证当前事件的连贯性。第二层是长期事实记忆,存玩家和 NPC 之间的关键事件,比如“帮过商人一次”“在城门口被守卫敲诈过”。这层数据会被向量化,按相关性召回,也可以在 GraphRAG 里做关系查询。第三层是状态快照记忆,也就是世界状态在关键节点的完整存档。当模型需要生成剧情时,可以回到某个历史快照上做推理,而不是每次都从头重建。
记忆同步是重点。每次事件结束之后,引擎要把事件结果回写知识库,更新 NPC 对玩家的态度。否则模型下一轮继续读旧档案,又会把玩家当陌生人。我做了一个记忆冲刷任务,每 10 轮交互启动一次,把所有短期对话翻译成摘要,更新长期记忆,并删除不再需要的原始记录。这样上下文不会无限膨胀,模型也能基于一个“浓缩但准确”的世界印象继续发挥。
4. 常见问题排查与避坑实录
4.1 幻觉变成了游戏 BUG:NPC 自己打自己脸
这个坑我踩过很多次。最典型的情况是玩家上一轮在商人那里买过一批低价矿石,下一轮商人主动找玩家交易,却完全不认识玩家,价还是按新客价算。原因很简单:模型没有把“买卖矿石”这件事写进世界状态,它的上下文里只有零散的对话,没有结构化的事件记录。
后来我在引擎里加了两条规则:第一,凡是做过交易、战斗、势力变动等关键操作,必须同步写入状态快照,并给相关实体打上事件 tag。第二,模型在生成内容前必须调用retrieve_relevant_history工具,把玩家和当前 NPC 的历史关系拉出来。模型输出的facts_to_check字段也不是摆样子,引擎会逐项做事实校验,过不了就拒绝执行,并且要求模型重新生成。
排查这类问题,最快的方法是直接把系统提示里的 key 检查一遍。如果 NPC 的状态表示里没有trust_level、last_deal_time这种字段,单纯靠对话记忆去维持一致性,那幻觉早晚会变成主线剧情 bug。
4.2 上下文爆掉与 token 失控
玩法场景里上下文爆掉几乎必然发生,不是技术问题,是成本问题。我曾经在测试时连续跑了一个小时战斗事件,每次事件都带完整的中场总结,结果一次对话上下文的 token 冲到 12000 多,不仅响应变慢,费用也肉眼可见地涨。
解决思路是“能省则省,该透才透”。入口处过滤日志,把玩家吐槽、重复骚话、无意义刷屏简短化,只保留影响状态的信息。记忆加载可以走最近优先加权:距离当前时间越近、与当前场景越相关,召回率越高。还有一个技巧,我在长期记忆里插入“软衰减”,超过三十分钟没被命中的事件,权重降 20%,避免无关的旧剧情持续占据 token 预算。
成本估算公式很简单,我每次都会在项目文档里列出来:单次交互成本等于输入 token 数乘输入单价,加上输出 token 数乘输出单价。如果要跑几百个 NPC,还得加上每个 NPC 每轮调用的次数。提前按这个公式估算,你就知道自己要不要限制每分钟调用次数、限制单局总调用次数。
4.3 接口报错与工具 payload 不匹配
在实际联调时,我最常看到的报错是类似“provider rejected the request schema or tool payload”。出现这种问题,十有八九不是模型的错,而是你传给接口的 schema 和实际返回工具参数对不上。
排查步骤我固定如下:第一步,检查 tools 参数是否符合厂商规定的 JSON Schema 子集,禁止放无限嵌套的对象,不要给字段设置互斥且同值的默认值。第二步,所有函数参数都要写清楚type和required,别用“可选的字符串数组”这种模糊定义。第三步,用本地 mock 数据把所有字段类型跑一遍,确认返回的参数能直接解析进 Pydantic model 或 dataclass。第四步,特别检查枚举类型。我在 schema 里写过intent的合法值为["bargain", "threat", "flattery", "buyout"],结果模型返回了"price",引擎不认识,直接断开。后来我把枚举校验从模型侧往引擎侧移,反正引擎本身就要做白名单校验,模型给不出来就重试,而不是永远相信它。
另外,如果你接了多个模型服务,最好在模型路由层做一次统一格式化。不同厂商对工具调用的数据结构有细微差别,统一路由层只管两件事:身份验证和 payload 标准化。这样后端代码不会因为换模型就大面积改逻辑。
4.4 同一个玩法,什么时候该收权,什么时候该放权
最后补一个很现实的设计问题。不是所有玩法都适合放权,也不是放得越多越好。我的判断标准很简单:看这个玩法在玩家体验里是否依赖“确定性反馈”。如果玩家做一个操作,需要精确知道结果,比如战斗伤害计算、装备强化概率、任务完成条件,那就该收权,让规则引擎说了算。如果玩家希望获得“被理解”和“被意外”的体验,比如 NPC 对话、剧情推演、任务解法,那就该放权,让 LLM 参与推荐。
这也就是为什么我不会把战斗数值直接交给 LLM 改。战斗结果是确定性规则,模型来改只会制造不公平。但在战斗之外,我可以让模型生成战前挑衅、战后嘲讽、负伤台词,以及特殊胜利后的势力关系变化。这部分是情感空间,放权收益远大于收权成本。把这两种空间分明,LLM 在游戏里才不会变成乱改数值的破坏者,而是真正提升体验的协作者。
我在实际项目中最后定下的方案是“双引擎”架构:规则引擎负责所有确定性数值,LLM 引擎负责所有开放表达和事件提案。两者之间用一个事件总线的格式做通信。模型只往事件总线里写“建议事件”,规则引擎判断是否可以采纳并执行。改到这个架构之后,模型的自由度没减少,但出的问题少了一大半。我个人认为,“从收权到放权”真正落地时,并不是单纯把权力转移给模型,而是把权力变成一种有边界的提案能力。模型越来越会建议,引擎越来越会审批,玩家越来越敢尝试,这个三角关系稳了,玩法也就活了。