AI游戏开发这个方向,我是从一个很偶然的念头开始的。去年我翻到一条玩家评论,说现在的游戏NPC就像提线木偶,对话永远只有三句。当时我就想:如果把大模型接进游戏里,让NPC真的会说话、会推理、会怀疑,会是什么效果?于是我利用业余时间从零做了一个《AI侦探所》——一款文字推理游戏,玩家和AI侦探一起查案。整个开发过程踩了不少坑,也攒了一堆经验,这篇就以我的实操记录为基础,把从引擎选型到对话系统、记忆系统、工具调用、发布上线会遇到的问题完整过一遍。
这篇教程更偏向“思路+能跑的代码”,而不是泛泛讲概念。你不需要很深的编程底子,我会尽量把每一步的前因后果讲清楚,遇到问题怎么排查也会写明白。如果你正好想做一个带AI角色的游戏,不管用Godot、Unity还是Cocos,这篇的思路都能直接搬过去用。
1. 先把思路理清:AI游戏和普通游戏到底差在哪
1.1 我的选题路径:为什么做一个“AI侦探所”
市面上的AI游戏,绝大多数最后都变成了“套着游戏外壳的聊天框”。玩家点进去,和AI角色聊两句,剧情推不下去,美术资源还贵得离谱。我一开始就在想,什么样的玩法最适合发挥大模型的长处。
答案落在“文字推理”上。文字推理游戏的核心是信息搜集、逻辑推导、谎言识别,这些恰恰是大模型最擅长的事。玩家向AI侦探提问,AI侦探根据卷宗、证物、嫌疑人证词来推断案情,整局游戏的自由度比传统“预设对话树”高得多。而且文字类游戏对美术要求极低,我一个人也能搞定,所以这个方向最适合从零起步。
确定方向之后再做减法:第一版只需要一个聊天窗口、一个证物面板、一个案件背景说明,再加上一个AI侦探角色。不搞地图、不搞战斗、不搞移动。任何复杂度,只要在当前阶段不解决核心问题,就统统砍掉。顺带说一句,哪怕你后续想做3D动作游戏,起步阶段最好也用一个极小的文字原型把AI链路跑通,真的,这能省掉你后面百分之七十的返工时间。
1.2 引擎选型:Godot、Unity、Cocos怎么选
在我那个时代,做独立游戏首选肯定是Unity。但AI游戏有个特殊需求:要频繁发HTTP请求、解析JSON、管理异步回调。这个需求在哪个引擎里都能做,可体验差别很大。
我当时专门列了个对比表:
| 对比项 | Godot 4 | Unity | Cocos Creator |
|---|---|---|---|
| 是否免费商用 | 是(MIT协议) | 个人版免费,收入超阈值要付费 | 免费开源,商用免费 |
| 引擎体积 | 轻量,几十MB | 偏大,启动慢 | 中等偏小 |
| 内置HTTP能力 | HTTPRequest节点,很好用 | 用UnityWebRequest,略繁琐 | 用XMLHttpRequest,通用 |
| 脚本语言 | GDScript,简单易学 | C#,功能强但上手稍慢 | TypeScript |
| 2D文字游戏适配度 | 很高 | 一般 | 高 |
| 微信小游戏发布 | 需要插件配置 | 官方支持,流程成熟 | 官方主推,最顺畅 |
如果你唯一的上线目标是微信小游戏,那Cocos的官方链路确实是最顺的;如果你想做Steam或者多平台,同时希望开发体验轻快一点,我建议优先看Godot。我自己选Godot 4,理由是免费商用、启动快、UI系统对文字推理这种界面友好,而且GDScript对我来说像Python一样好写。Unity我也用了好几年,但杀鸡不必用牛刀,一个文字游戏实在没必要带那么重的引擎。
1.3 整体架构:客户端、服务器与AI服务的三角关系
AI游戏不是把大模型装进包里就能跑的,实际上你的游戏包体里跑的只是一个“前端”和一个“游戏逻辑层”,真正有智力的部分在云端。打个比方,你的客户端是“大脑”,负责游戏流程;大模型API是分布在城市另一端的“外脑”,你每次提问都要用电话线(HTTP请求)去问它,再把回答拿回来用。
这一点很重要,因为很多新人一开始就陷入“怎么把模型塞进游戏里”的焦虑,实际上你根本不需要本地部署模型。以《AI侦探所》的架构为例,跑起来是这样一条链路:
玩家输入 -> Godot客户端(对话界面) -> HTTP POST请求 -> 云端API网关 -> 大模型 AI回复 <- Godot解析JSON <- 大模型流式结果/完整结果 <- 云端返回客户端最关心的只有两件事:把用户说的话发给谁,拿回来的文本怎么显示。至于模型推理过程,完全不用管。我用的调用模型是国内可直接访问的大模型API,比如DeepSeek、通义、豆包都是可行的选择。这里有个关键建议:把API密钥放到单独配置文件里,用.gitignore忽略掉,不要提交到公网仓库,否则第二天醒来你的额度可能就被别人刷光了。
还有一个容易忽视的架构点:把“游戏逻辑token”和“AI对话token”做区分。比如你看证物、切换场景这类操作,完全不应该走AI,只有玩家真正对AI侦探说话时才调模型。这样既省成本,响应也快。
2. 核心细节解析:让AI扮演一个“活人”侦探
2.1 提示词设计:AI侦探的人设是怎么“捏”出来的
做AI游戏,最核心的技能不是写代码,而是写提示词。一个角色像不像真人,90%由你给模型的“系统提示词”决定。这里是我的“人设模板”:
你是一位资深侦探,名叫方睿,冷静、敏锐、带着一点冷幽默。 你正在调查一起庄园谋杀案,玩家是你的助手。 案件背景: [这里放案件描述] 你的行为守则: 1. 会基于已有卷宗和证物回答问题,绝不凭空编造线索。 2. 当玩家问你“现在怎么办”时,给出你的推理过程和下一步建议。 3. 你会记住和玩家聊过的内容,并在后续对话中自然引用。 4. 语气简短有力,每次回复不超过200字,类侦探小说风格。这个提示词里有几个要点值得展开说。第一,我明确告诉它“基于已有卷宗回答,不凭空编造”,这能大幅减少胡说八道。第二,我限制了回复长度,避免它每次写小作文,游戏节奏会被拖垮。第三,我要求它“记住聊过的内容”,这需要配合下文说的消息列表机制,否则光靠提示词是记不住的。
这里有个常见误区:以为系统提示词写一页纸,AI会更聪明。实际上不是,冗余信息会让模型抓不住重点,还增加token消耗。我的建议是控制在一屏以内,把角色性格、行为边界、任务目标说清楚就够。如果性格还是不稳定,就在对话示例里补充“few-shot例子”,比如给一个正确的问答范例:
玩家:我觉得管家在撒谎。 方睿:说说你的依据,助手。你看,他说案发时在后花园修剪花枝,但管家皮鞋上没有泥土。这类示例模型模仿起来非常快,比在系统提示词里强调一百遍“你要简洁”都有用。
2.2 对话记忆:让AI记住3轮之前说过的话
大模型本身没有记忆。它的每一次回复,都是根据你这次请求携带的文本生成的。如果你只把玩家当前这句“我觉得管家在撒谎”丢给它,它完全不知道之前你们聊过什么。所以记忆的本质是:把历史对话一起塞进请求里。
在代码层,我维护了一个名为messages的列表。每个元素是一个结构体,包含role和content两个字段,role有三种取值:
| role取值 | 含义 | 示例 |
|---|---|---|
| system | 系统提示词,设置AI人设 | 你是一位资深侦探方睿 |
| user | 玩家输入 | 管家不在场证明有问题吗 |
| assistant | AI的回复 | 是的,他的鞋底没有泥土 |
每次玩家说话,我就把用户输入追加进列表,再把整个列表发给模型API。模型按顺序看完所有消息之后,就能生成符合上下文的回复。回复拿到后,再把AI回复也追加进列表,一个循环就成立了。
# 这是后端演示代码,实际项目中数据由客户端结构化发送 messages = [ {"role": "system", "content": system_prompt}, {"role": "assistant", "content": "欢迎回来,助手。今天的案子有点意思。"}, ] # 玩家新提问 user_input = "你觉得死者的贴身女仆知道些什么?" messages.append({"role": "user", "content": user_input}) # 调用模型API resp = chat_completion(model="deepseek-chat", messages=messages) reply = resp["choices"][0]["message"]["content"] messages.append({"role": "assistant", "content": reply})不过这里有个现实问题,对话轮数越多,发给模型的token就越多。当对话超过十几轮之后,请求体可能膨胀到几千token,响应慢而且费用高。我的处理策略是做“滑动窗口”:只保留最近的十二轮对话和系统提示词,更早的内容压缩成一句“前情提要”,放在系统提示词里。
举个例子,如果玩家在第20轮再次问起某个线索,我会在发送前把第1到18轮的内容总结为“助手前18轮和你一起排查了管家、女仆、花匠三个嫌疑人,你漏过一条关键信息:死者桌上的茶杯里有苦杏仁味。”这样模型既能理解背景,又不会因为对话太长而发昏。
2.3 工具调用:让AI不只是聊天,还能查证物、翻卷宗
如果AI侦探只会聊天,那它和普通聊天机器人没区别。我给它加了“工具调用”能力,让它能按需查询证物信息。这里用的机制叫Function Calling,目前主流大模型API基本都支持。
我的思路是:给模型声明一个工具函数,描述如下:
{ "type": "function", "function": { "name": "query_evidence", "description": "查询某件证物的背景信息与检验结果", "parameters": { "type": "object", "properties": { "evidence_id": { "type": "string", "description": "证物编号,例如 E001" } }, "required": ["evidence_id"] } } }当模型判断需要查询证物时,它不会直接给出文字回复,而是返回一个“函数调用请求”,包含了工具名和参数。我拿到这个请求之后,在自己游戏逻辑里查到对应的证物信息,再把查询结果以tool角色消息返回给模型,模型继续生成最终回复。整个流程用代码理解是这样的:
1. 玩家问:钥匙上的指纹是谁的? 2. 模型返回:我想查一下E003号证物 3. Godot调用query_evidence("E003") 4. 游戏逻辑返回:指纹属于管家周平 5. 模型生成最终回答:钥匙指纹是管家的,这条线索也许能解释他为什么出现在卧室用这种机制,模型生成的回复就从“编造推理”变成了“基于游戏数据推理”。玩家在游戏里查证物,AI也能同步“看到”证物信息,这在传统对话树里是不可能实现的。我强烈推荐所有做AI游戏的朋友掌握这个功能,它才是让游戏机制和AI深度结合的关键。
如果你是后期才想接这个能力,别慌。底层逻辑都是上面这条路,不同引擎只是网络请求代码不同而已。Godot里用HTTPRequest做POST请求,Unity里用UnityWebRequest,Cocos里用fetch,整体设计完全一致。
3. 实操过程:用Godot搭建一个可以玩的文字对话Demo
3.1 环境准备:安装Godot与创建第一个项目
我用的版本是Godot 4.2稳定版。你直接去官网下载标准版就好,不用选.NET版,因为GDScript已经足够。安装步骤没什么好说的,解压即用。
新建项目时会有个项目模板选择,我选的是“Empty”。用空白项目最干净。然后我会在项目根目录建几个文件夹,养成好习惯:
res://scenes/ 存放场景文件 res://scripts/ 存放GDScript脚本 res://data/ 存放案件数据、提示词配置 res://assets/ 存放美术素材和字体创建完成后我还会把project.godot里的config/features保持默认,不需要额外插件。这里说一下:Godot的HTTP功能不需要额外插件,自带的HTTPRequest节点就好,生命周期短的项目千万别为了省事去搞插件,版本兼容问题能折腾你一整天。
3.2 搭界面:对话窗口、输入框、证物面板
打开Godot后,我会先新建一个主场景,根节点选Control。Control是Godot的UI基类,所有界面元素都挂它下面。具体节点结构如下:
Main (Control) ├── Background (TextureRect) // 游戏背景 ├── DialoguePanel (PanelContainer) // 对话区域 │ └── RichTextLabel // 对话纪录展示,支持富文本 ├── InputArea (HBoxContainer) │ ├── LineEdit // 玩家输入框 │ └── Button // 发送按钮 ├── EvidencePanel (VBoxContainer) // 证物列表证物面板我用了一个简单的ItemList控件,在里面添加三个证物条目:沾有泥土的皮鞋、苦杏仁味茶杯、门把手上的钥匙。每个条目对应一个ID,点击时后台把ID发给AI侦探,就触发了上一节说的工具调用。这一步实操的关键是:RichTextLabel的bbcode_enabled要打开,这样AI回复里的换行和重点信息才能排版好看。
3.3 接API:在GDScript里怎么发HTTP请求
这一节是整个教程最需要“照着抄”的地方。我先说清楚三个前置条件:API密钥、模型服务地址、请求体格式。我以兼容OpenAI格式的大模型API为例,常见的开放平台基本都支持这种格式,你找到你们平台的chat/completions接口就行。
Godot里发HTTP请求很简单。场景里放一个HTTPRequest节点,然后在脚本里这样写:
extends Control @onready var http_request: HTTPRequest = $HTTPRequest @onready var chat_history: Array = [] # 对话记录列表 var api_key: String = "你的API密钥" func _ready(): http_request.request_completed.connect(_on_response) func send_message(user_text: String): chat_history.append({"role": "user", "content": user_text}) var headers = [ "Content-Type: application/json", "Authorization: Bearer " + api_key ] var body = JSON.stringify({ "model": "deepseek-chat", "messages": chat_history, "temperature": 0.7, "max_tokens": 500 }) var url = "https://api.deepseek.com/chat/completions" http_request.request(url, headers, HTTPClient.METHOD_POST, body)request方法的四个参数分别是地址、请求头、请求方法、请求体。关键点是请求头里必须有Content-Type: application/json和Authorization。然后异步回调在_on_response里:
func _on_response(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray): if response_code != 200: print("HTTP错误:", response_code) return var json = JSON.parse_string(body.get_string_from_utf8()) if json == null: return var reply = json["choices"][0]["message"]["content"] chat_history.append({"role": "assistant", "content": reply}) display_message("方睿:", reply)这里有几个新手必踩的坑,我提前帮你标记出来:
第一个坑是请求体格式必须和平台文档完全一致,多数平台是{"model": "xxx", "messages": [...]},但有些平台要求把prompt放在单独字段,报错时第一时间回去核对文档。第二个坑是response_code不一定返回200,尤其是模型服务限流时可能返回429,不要把非200统一当网络错误处理,先打印出来再说。第三个坑是编码问题,中文回复从PackedByteArray读取时一定用get_string_from_utf8(),用默认的get_string()会乱码。
3.4 跑通流程:从玩家的输入到AI回复显示在屏幕上
把上面的代码组装起来,就是一个最简单的循环。我在输入框的text_submitted信号里调用send_message,并把输入框清空:
func _on_line_edit_text_submitted(new_text: String): if new_text.strip_edges().is_empty(): return display_message("你:", new_text) send_message(new_text) $InputArea/LineEdit.clear()网络请求是需要时间的,玩家点发送之后界面会卡顿一下,模型推理通常一到三秒。为了提升体验,我加了一个“正在思考”的状态提示。具体做法是:发送请求前把按钮禁用,在对话区显示“方睿拧着眉头没有立刻说话……”,收到回调后再把按钮恢复。
整套流程描述起来就是:
玩家输入 -> 显示玩家消息 -> 发函数请求给AI -> 等待HTTP回调 -> 如果模型要查证物,先查本地数据再返回 -> 显示AI消息 -> 更新对话历史我第一次跑通这个流程,前后大概花了四十分钟。当时看到方睿真的在根据上下文推理,连我自己都愣了一下,和那种预设对话树的体验完全不一样。这种反馈感,就是坚持做下去的最大动力。
4. 进阶玩法:用AI Agent让侦探“自主行动”
4.1 从“一问一答”到“自动破案”
基础版做好之后,我又做了一件能让游戏“活”起来的事:把AI侦探从被动回答问题,变成主动推理。这里就要引入AI Agent的概念。
最简单的Agent可以理解成一个循环:给AI一个目标,让它观察当前状态,决定下一步行动,执行行动,然后根据结果继续思考。放在游戏里,就是AI侦探自己会“跑”推理。
举个具体例子。我在案件数据里定义了几个关键状态:管家的不在场证明已证伪、女仆的慌张程度上升、花园脚印与花匠鞋子匹配。每次玩家问完一个关键问题,游戏逻辑就更新这些状态,然后让AI侦探基于最新状态开展“自我总结式对话”。
这个机制的实现方式其实很简单,我给它发一条系统级消息:
请基于下面的调查进度,给出你当前的推理结论和下一个调查问题: 1. 管家不在场证明已证伪 2. 女仆的慌张程度上升 3. 花园脚印与花匠鞋子匹配AI回复后,我再把它接回对话历史。这样一来,AI侦探就变成了一个有“内心独白”的角色,玩家即使不问“现在怎么办”,也会看到侦探自己整理思路。这种“AI自己会推进剧情”的感觉,比单纯问答要高级得多,也是当前AI游戏比较流行的设计方向。
4.2 多AI协作:让嫌疑人也“活”起来
做完一个AI角色之后,我又想加一个角色参与互动。玩家可以在游戏中切换询问对象——AI侦探方睿,或者被传唤的嫌疑人。后者也是AI驱动的,用不同的提示词来模拟不同性格。
当玩家当面询问嫌疑人时,我让模型扮演一位紧张的女仆,她的提示词里写着“你在隐瞒一件事:案发当晚你曾在书房门口听到争吵,但不敢说出来”。
关键来了:嫌疑人AI的反应,要和侦探AI的推测形成联动。我做了个简单联动逻辑:如果玩家之前从侦探那里获得了“女仆听到争吵”这一线索,再询问女仆时,她的紧张程度参数会提高,她的回答会变得更闪烁其词。
技术上实现起来就是把“线索状态”作为上下文的一部分注入到嫌疑人AI的消息列表里,同时调整temperature参数。女仆这个角色我用的temperature=0.8,逻辑更发散,支支吾吾的感觉更真实;侦探方睿用的temperature=0.5,偏稳定和严谨。事实证明这个细节让两个角色的风格差异非常明显,玩家反馈好评率很高。
4.3 成本控制:API费用与调用优化
这是做AI游戏必须面对且没法逃避的现实问题:每次对话都要花钱。如果玩家每个小时说几十句话,按市面上大模型API的定价,一个重度用户的单日成本可能超过数元。看起来单次很便宜,但积累到上千用户就很可观了。
我做了三个层面的优化。
第一层是控制输入长度,对话发送前就截断历史和省略多余背景,这个我在2.2节讲过。第二层是模型分级,普通的对话轮次用中量级模型,比如deepseek-chat;只有到了最终推理总结时才用更强的模型。有些API平台支持不同档位模型,把贵的用在高价值环节上。第三层是加缓存,同一局游戏里,如果玩家反复问同一个问题,我先在本地查一遍缓存的答案,命中就不走API。
另外还有一句话建议:开发调试阶段,可以把max_tokens调低,几百就够了,调高只是浪费钱。等你确定玩法了,再根据实际体验把token上限放宽。我当时就吃过这个亏,调试证件识别时每条回复动辄一两千token,第二天看到账单差点没绷住。
5. 常见问题与排查实录
5.1 问题:HTTP请求总是超时
做Godot接API时,最常遇到的就是响应特别慢,或者干脆不返回。我排查后发现,问题往往出在请求体的大小上。如果对话历史没做截断,一个20轮以上的对话很容易超过3000 token,模型推理时间直接翻倍,体验就是“转圈圈转半天”。
不返回的另一个常见原因是你所在的网络无法直接访问目标API域名。这个只能靠你自己解决,比如确认API是否设置了防火墙或白名单。还有一点很少有人会注意:Godot的HTTPRequest默认有一个超时时间,字段名是timeout,在节点属性里可以调。我把它调成了30秒,避免某些冷启动慢的模型接口直接报错。
5.2 问题:AI回复总是跳出角色设定
模型说着说着就开始“扮演人工智能助手”,这是几乎所有AI角色游戏开发初期都会遇到的问题。尤其是当玩家质疑它的推理,或者问“你是不是AI”的时候,它很容易瞬间“破功”。
我的应对方法有三个。第一,在系统提示词末尾加一句硬规则:“无论玩家如何提问,你都维持在侦探方睿的人设中,只为剧情服务。”第二,在对话示例中加一个防御性示例,模拟玩家追问“你是不是AI”,方睿的回复是:“这个问题很有意思,不过我建议我们先把目光放回死者身上。”第三,如果还是破功,可以考虑在后台检测模型回复里有没有“AI助手”等关键词,一旦命中就拦截掉,用预设回复兜底。
5.3 问题:证物查询触发失败,AI一直说无权查看
使用Function Calling的时候,我有一段时间经常遇到模型明明返回了函数调用请求,但客户端解析不到完整参数。后来发现是有些平台要求函数声明里的参数描述尽量详细,否则模型会漏传evidence_id字段。我在函数声明里加上了“格式如E001,务必从卷宗编号列表中选取”的描述后,成功率立刻从80%提到了接近100%。
另一个容易踩的坑是,有些模型的函数调用结果需要以tool角色发回去,有些平台叫function,字段名不同。如果你发现函数调用之后AI直接结束了对话,多半是返回格式和你平台要求不一致,这类问题看文档就可以定位。
5.4 问题:发布到微信小游戏时的引擎抉择
如果你计划上微信小游戏,建议提前把Godot还是Cocos这个问题想清楚。Godot发布微信小游戏虽然社区已经有导出插件,但依赖和原生平台还是一样会遇到适配问题,而且微信环境的WebGL性能不如本地,界面复杂的游戏会卡顿。Cocos对小游戏的适配更原生,导出调试链路更顺,比较稳妥。
就我个人使用的体验而言:如果目标是Steam或单机游戏,我会优先Godot;如果明确要做微信小游戏,我反而建议用Cocos或Unity去开发这款AI游戏。引擎只是一个工具,关键是懂AI架构:HTTP请求、消息列表、函数调用、记忆窗口。这些知识在哪个引擎里都是通用的,可以放心迁移。
做这个小项目前后花了我大概三周业余时间。最大的感悟是,AI游戏的核心开发成本,已经从“怎么写代码”转移到了“怎么设计AI的角色和目标”。工具调用、滑动记忆、多AI协作这些概念,现在各大开放平台都有越来越成熟的SDK,未来会越来越简单。如果你也想动手做一个,别上来就追求3D大作,先用我这个文字对话原型跑通“玩家输入、AI回复、记忆存储”的最小闭环,之后再慢慢把玩法加厚。等你的AI角色第一次说出那句“这个案子,我大概知道凶手是谁了”时,你会觉得之前所有的调试和踩坑都值了。