2024年之后,做游戏的门槛真的被拉低了一大截。以前从0开发一个带AI玩法的游戏,要懂程序、懂美术、懂算法,现在只要你会用对AI编程工具,再挑一个对新手友好的游戏引擎,完全能自己一个人把Demo做出来。这篇文章就记录一下我最近从零开始做一款AI猜词小游戏的全过程:怎么选引擎、怎么搭环境、怎么写核心逻辑、怎么把大模型接进游戏里,以及踩过的各种坑。不管你是刚接触编程的小白,还是已经有点基础想尝试AI玩法尝鲜的人,这篇教程应该都能让你少走几天弯路。
先说清楚这篇文章能给你带来什么。它不是一个泛泛的“AI游戏开发概论”,而是一条可以照着做的完整路径:先讲怎么把模糊的想法变成一个具体可落地的游戏原型,再讲工具链怎么搭配,然后以Godot引擎为例,手把手把“AI猜词游戏”的核心功能敲出来,最后把我在接入AI接口时遇到的经典问题连排查过程一起摊开讲。整篇下来,你会拿到一份能跑的代码工程结构、几条写AI提示词的经验,以及一套处理游戏与AI交互异常的套路。这些话我尽量不说教,全部是我自己踩出来的实操心法。
1. 起步前的准备:需求定位与工具矩阵
1.1 先想清楚你要做一款什么样的AI游戏
我见过太多人一上来就问“我想做AI游戏,该学Unreal还是Unity”,这其实把因果搞反了。AI游戏的核心不在于用了多炫的引擎,而在于“AI到底在游戏里承担什么角色”。所以在打开编辑器之前,先花半小时把下面几个问题写下来:
- 游戏的核心玩法是什么?是玩家和AI对抗,还是AI做裁判、做队友,抑或是AI动态生成关卡和剧情?
- AI的“智能”来自哪儿?是传统游戏AI(状态机、寻路、行为树),还是接入大模型接口做对话和语义判断?
- 玩家在游戏里最频繁交互的对象是谁?如果是AI,那AI的响应速度、容错能力和文本输出格式,会直接影响游戏体验。
我当时选的是“玩家通过提问,让AI裁判悄悄想一个词并判断是否猜中”的玩法。为什么选这个?因为它把AI的能力放在一个非常聚焦的位置:只负责理解玩家输入的语义,并给出“接近了/还差得远/猜中了”的反馈。这个功能用规则脚本也能做,但规则脚本根本接不住千奇百怪的中文表达,而大模型天然擅长干这个。换句话说,这个玩法能最大化体现AI的价值,又不会让开发量失控。
当然,你完全可以选择别的方向。比如让AI扮演NPC跟你对话、用AI生成随机迷宫、或者让AI根据玩家表现动态调整难度。但不管选哪条路,都要遵循同一个原则:先明确AI能否稳定产生“对游戏进程有实际影响”的输出,再动手写代码。如果AI的输出只是可有可无的装饰,那这个玩法就不值得做。
1.2 工具选型:Godot、Cocos还是Unity
引擎选择是很多人卡住的第一关。我的建议很直接:如果是个人开发或小团队,尤其你还要同时兼顾AI接入,首选Godot;如果你目标明确就是要做微信小程序游戏,Cocos值得认真考虑;如果之前已经学过Unity,继续用Unity也没问题,没必要为了跟风而换引擎。
这里说说我为什么在个人项目里越来越倾向Godot。第一,Windows版编辑器只有百来MB,打开速度快,内存占用比Unity轻太多。我机器只有16GB内存,同时开编辑器、VS Code和一个本地大模型服务,Godot依然很稳,Unity开个3D示例项目就能把我卡得怀疑人生。第二,Godot自带的GDScript语法和Python很像,对写过一点脚本的人非常友好,不需要像C#那样关心一堆类型声明。第三,GDScript信号系统天生适合做事件驱动,尤其适合处理“玩家输入→AI响应→UI更新”这种异步链条。第四,Godot导出的Windows、Linux、Android包都挺省事,对于做小体量游戏来说完全够用。
Cocos的优势在微信小程序生态,如果你最终要发到微信上,Cocos Creator的构建工具链是打磨得最顺的。但我个人不太建议一个纯新手在第一个AI游戏项目里选Cocos,因为它官方文档里关于原生开发的内容更多,如果你不是主攻小游戏,很多配置会显得冗余。
Unity的优势是资料多、插件多、岗位需求量大,但劣势也明显:编辑器越来越重,版本碎片化严重,新项目默认管线都要折腾半天。如果你以后想进游戏公司做商业化项目,Unity依然是国内的主流,这个考量没问题。但就“从0快速验证一个AI玩法”这件事来说,Godot是我目前实测下来最稳妥的选择。
1.3 环境搭建:那些必装的开发工具
确定引擎后,下一步就是把开发环境一次配好,免得半路被工具链折磨。我整理一套适合AI游戏开发的基础工具清单:
- Godot引擎:到官网下载4.x稳定版。注意一定要选Standard版本,不要选Mono版,Mono版要额外配.NET环境,GDScript对新手更合适。
- VS Code:写GDScript、JSON脚本、Python辅助脚本都用得上。安装后屏蔽掉几个用不上的插件,避免后台占内存。
- Python环境:优先用Miniconda管理,别直接往系统里装原版Python。创建独立虚拟环境来跑AI模型的调用脚本,比全局安装干净得多。
- Git:无论多小的项目我都建议用Git管理,这是给自己留后悔药。安装时一路默认即可,但安装完务必先把
user.name和user.email设置好,否则第一次提交就会报错。 - Docker Desktop:如果后面要部署AI推理服务,Docker能帮你把环境封装成镜像,省去很多依赖地狱。不熟悉的可以先跳过,不影响前期开发。
装完这些,我给自己的要求是:能在10分钟内从一个干净系统把Godot项目跑起来。如果你装完还要折腾半天才能新建一个场景,说明哪里配置出了问题,先解决掉再继续,千万不要带着bug上路。
2. 核心设计:游戏循环、数据与AI能力接入
2.1 设计一个能让AI自然融入的游戏循环
很多AI游戏做出来像“网页聊天窗口套了个游戏皮”,问题就出在游戏循环没有真正和AI交互挂钩。我设计AI猜词游戏的思路可以拆成四步循环,每一步AI都有明确的职责:
- 游戏开始,AI从词库里随机选一个目标词,并对玩家隐藏。
- 玩家在输入框敲一段自然语言描述,比如“是一种不会飞的大型鸟类吗?”,点击提交。
- AI接收这段描述,结合目标词判断“接近程度”,返回三选一的反馈:完全命中、接近、不接近。
- 如果不是完全命中,游戏根据反馈给玩家提示次数和得分,然后回到第2步。
这四步循环里,AI不是被“调用一次就没事了”,而是每一次反馈都在影响玩家下一步策略。玩家会不断调整提问角度,AI也一直在做语义判断,这就形成了一个有策略深度的游戏闭环。我建议你在做自己的AI游戏时,把所有玩法环节都摊在桌面上,标出哪些环节需要AI、AI在环节里输入什么、输出什么、输出如何影响下一个环节。只要有一条线是通的,玩法就立住了。
2.2 核心选型:规则脚本、本地大模型还是云API
AI能力怎么接,直接决定开发难度和成本。这里我分三个层次讲,方便你对号入座:
- 纯规则脚本:适合极其确定的逻辑,比如“如果玩家输入包含‘鸟’并且目标词是‘鸵鸟’,则返回接近”。这个方案零成本、响应快,但写起来死板,玩家换个说法就露馅。
- 本地大模型:比如通过Ollama或llama.cpp跑一个量化后的中小模型。好处是免费、离线、数据不出本机,隐私安全可控;坏处是模型小的话语义理解会粗糙,而且首次启动加载模型要占不少内存。
- 云API调用:理解能力最强,也是我个人首推的方案,尤其适合快速验证玩法。你不需要管理任何模型环境,只要处理好网络请求和返回解析就行。缺点是要花钱、要考虑并发和限流,而且必须做超时处理和错误重试。
我这篇教程里的案例用的是本地大模型方案,这样读者不需要申请任何API Key就能复现。不过开发过程中我依然建议先把AI调用逻辑封装成独立函数,这样后面想从本地模型切到云API,只需要改一处请求实现,其余代码不受影响。封装得好不好,直接决定你后面迭代的速度。
2.3 数据流设计:玩家输入、AI输出、游戏反馈
AI游戏里最容易翻车的地方,不是AI不会回答,而是AI的返回格式“不够程序化”。你让AI返回一大段自然语言,然后想在游戏里判断“猜没猜中”,光靠文本匹配根本不可靠。我踩了坑之后总结出一个标准套路:让AI输出结构化JSON,游戏只解析JSON字段。
具体来说,我自己定义的提示词里会要求模型输出这样的JSON格式:
{ "is_win": false, "closeness": 0.7, "hint": "接近了!再想想会不会飞?" }is_win:布尔值,表示是否完全猜中目标词。closeness:0到1之间的数值,表示玩家描述与目标词的接近程度,游戏可以据此计算得分。hint:字符串,给玩家方向性提示,用于提升体验。
在GDScript里,拿到AI返回的字符串后,直接解析这个JSON字段,再根据字段值驱动游戏UI变化。这样数据和表现就彻底分离了:AI只负责输出决策,游戏负责把决策变成视觉和音效。如果你能保证AI输出永远是合法JSON,后续所有逻辑都会变得异常清爽。
3. 实操:用Godot从零搭一个AI猜词小游戏
3.1 创建项目与场景结构
打开Godot后,新建一个“空项目”,项目名我建议就叫guess_word,渲染器保持默认。项目创建好后,先别急着写代码,我们把场景结构规划清楚。一个最小的可玩版本,至少要包含三个场景:
Main.tscn:主场景,承载UI布局和整个游戏流程控制。GameManager.gd:挂在Main上的根脚本,负责初始化游戏、连接各个节点。AIInference.gd:独立负责AI请求的封装,把HTTP请求、超时、JSON解析全部隔离在这里。
节点层级我建议这样组织:
Main (Node2D) ├── UI (CanvasLayer) │ ├── TitleLabel (Label) │ ├── InputBox (LineEdit) │ ├── SubmitButton (Button) │ ├── FeedbackLabel (Label) │ └── ScoreLabel (Label) └── AI (Node)实操中我犯过一个低级错误:把UI节点直接堆到Node2D下面,导致CanvasLayer混在普通节点里,渲染层级一团乱。后来把所有UI清理进CanvasLayer,输入框和按钮的坐标才变得可控。记住:UI一律放CanvasLayer,这是Godot项目里最基础也最容易忽略的规范。
3.2 编写核心游戏脚本
先写GameManager.gd,它就是游戏流程的指挥中心。核心逻辑非常直白:存储当前目标词,绑定按钮点击事件,调用AIInference的异步请求,然后根据JSON结果更新界面。
extends Node var target_word : String @onready var feedback_label : Label = $UI/FeedbackLabel @onready var score_label : Label = $UI/ScoreLabel @onready var input_box : LineEdit = $UI/InputBox @onready var submit_button : Button = $UI/SubmitButton @onready var ai_node : Node = $AI var score := 0 func _ready(): submit_button.pressed.connect(_on_submit_pressed) _start_round() func _start_round(): target_word = "鸵鸟" feedback_label.text = "我心里想了一个词,试试描述它来猜出是什么!" score = 0 score_label.text = "得分:0" func _on_submit_pressed(): var player_input := input_box.text.strip_edges() if player_input.is_empty(): return input_box.text = "" # 利用协程等AI结果返回,后续函数体需要拆分为async var ai_result := await ai_node.request_judgement(target_word, player_input) _apply_ai_result(ai_result)注意,GDScript里await和协程的写法是4.x版本重点,不要用yield这种老语法了。写的时候如果编辑器报“cannot wait without async”,就去检查是否把函数声明成协程形式。这个坑我在切换Godot 4后踩了不下三次。
然后写_apply_ai_result,把AI的JSON结果翻译成界面更新:
func _apply_ai_result(result: Dictionary): var is_win: bool = result.get("is_win", false) var closeness: float = result.get("closeness", 0.0) var hint: String = result.get("hint", "") score += int(closeness * 100) score_label.text = "得分:%d" % score if is_win: feedback_label.text = "恭喜你猜中了!答案就是:“%s”。" % target_word _start_round() else: feedback_label.text = hint这一步很容易跑通,但你要注意一个体验细节:玩家猜中后我直接调用_start_round()开启新词,这个逻辑会让玩家来不及看清胜利说明。我后来又加了1.5秒的延迟再开局,否则玩家会以为“我刚猜中怎么界面就重置了”。情绪节奏也是游戏逻辑的一部分,不能省。
3.3 封装AI请求:HTTP调用与超时处理
现在到了重头戏:AIInference.gd。这个脚本必须把“AI服务地址”“生成提示词”“发送HTTP请求”“解析JSON”四件事全部管理起来。在这个案例里,我通过本地Ollama服务的HTTP接口来调用模型,URL通常是http://127.0.0.1:11434/api/generate。如果你用的是其他AI服务,只需要替换请求端点、请求头和认证参数即可。
extends Node const API_URL := "http://127.0.0.1:11434/api/generate" const HTTP_TIMEOUT := 15.0 func request_judgement(target_word: String, player_input: String) -> Dictionary: var prompt := "你是猜词游戏的裁判。目标词是“%s”。玩家描述是:“%s”。请根据两者语义相似程度,严格返回JSON,格式为:{\"is_win\": false, \"closeness\": 0.0~1.0, \"hint\": \"中文提示\"}。不要输出任何额外文本。" % [target_word, player_input] var payload := { "model": "qwen2.5:7b", "prompt": prompt, "stream": false, "format": "json" } var http_request := HTTPRequest.new() add_child(http_request) var headers := ["Content-Type: application/json"] var body := JSON.stringify(payload) var foreground := "request_completed" http_request.request_completed.connect(foreground) var err := http_request.request(API_URL, headers, HTTPClient.METHOD_POST, body) if err != OK: return {"is_win": false, "closeness": 0.0, "hint": "请求失败"} var result: Array = await _request_finished http_request.queue_free() if result.is_empty(): return {"is_win": false, "closeness": 0.0, "hint": "服务无响应"} var response_code: int = result[0] var response_body: PackedByteArray = result[1] if response_code != 200: return {"is_win": false, "closeness": 0.0, "hint": "AI服务返回错误"} var json := JSON.parse_string(response_body.get_string_from_utf8()) if json is Dictionary and json.has("response"): var ai_text: String = json["response"] var parsed := JSON.parse_string(ai_text) if parsed is Dictionary: return parsed return {"is_win": false, "closeness": 0.0, "hint": "AI输出格式异常"}这段脚本里有几个细节特别重要。第一,我加了HTTP_TIMEOUT常量,但实际上HTTPRequest本身的超时行为并不稳定,我还需要额外写一个计时器来强制终止请求。第二,request()返回的err只代表请求是否成功发出,不代表服务是否正常返回。第三,Ollama的format参数让模型直接输出JSON,这比让AI“自由发挥”再尝试解析要可靠得多。
这里特别提醒:本地模型首次调用时要冷启动,可能好几秒没反应,这时候游戏UI如果没有任何提示,玩家会以为游戏卡死了。我建议在提交按钮点击后先把按钮禁用、按钮文字改成“AI思考中...”,等返回结果后再恢复。这个交互细节能极大提升体验的“顺滑感”。
3.4 关卡内容扩展:让游戏不只是一个词
单轮的猜词demo只能证明技术跑通了,真正让游戏有耐玩性的,是内容。我建议你至少准备一组目标词,脚本从词库里随机抽取。词库可以是内置的数组,也可以读外部JSON文件,这样以后更新词库不用重新打包程序。
GDScript里读取外部JSON很简单:
func load_word_bank(path: String) -> Array: if not FileAccess.file_exists(path): return [] var file := FileAccess.open(path, FileAccess.READ) var data := JSON.parse_string(file.get_as_text()) file.close() if data is Dictionary and data.has("words"): return data["words"] return []词库JSON大概长这样:
{ "words": [ "鸵鸟", "冰箱", "行星", "恐高症", "充电宝" ] }内容设计上有个技巧:尽量不要选太具象的东西,比如“手机”“苹果”这种,因为玩家两三句描述就能猜到,游戏结束得太快。多选一些组合概念,比如“会飞的非鸟类动物”“怕水的狗”,这样AI判断的乐趣就出来了,玩家也会觉得“这个AI真的有理解能力”。如果你做过几轮测试,应该会和我一样发现,AI猜词游戏的乐趣不在“猜对”,而在“AI怎么理解那些词不达意的描述”。
我还会额外加一个功能:给每个词配一条“冷知识提示”,当玩家连续三次都不够接近时,游戏自动显示这条提示作为兜底。这个办法很好地缓解了AI反馈不够精准导致的挫败感。
4. 常见问题与排查实录
4.1 AI响应慢、超时或直接无响应
这是所有接入外部AI的游戏最先遇到的问题。我的排查顺序是:
- 先看AI服务日志。比如Ollama在控制台跑起来之后,请求进来会打印模型名和耗时,如果日志里没有任何请求,那问题出在游戏端的请求路径上。
- 再检查URL和端口。本地服务最容易把
127.0.0.1写成localhost,有些环境两者没问题,有些会有奇奇怪怪的IPv6解析延迟。 - 然后确认
model名称是否正确。本地模型版本用ollama list查看,名字写错会直接报404或500。 - 最后看返回内容。如果内容能返回但JSON解析失败,多半是模型没完全遵循
format约束,这时候可以考虑把提示词里“严格返回JSON”改成“只返回一个JSON对象,不要任何解释文字”,或者在后端再包一层清洗逻辑。
我做需求时还发现,如果词库里有生僻词,AI的closeness值波动会很大。比如目标词是“充电宝”,玩家说“能装电的小盒子”,模型可能给你一个0.95,但目标词换成“恐高症”,同一句话直接变成0.2。这种不稳定性是模型特性,单靠改提示词很难根治。我最后做了一层“接近度平滑”:只把上一轮和当前轮的closeness取平均值再映射到分数上。这样分数变化更平滑,玩家也不会因为偶尔一次误判而彻底愤怒。
4.2 编辑器报错、版本冲突与依赖地狱
用Godot 4开发时,最容易遇到三类报错。第一类:Invalid call. Nonexistent function,通常是你在别的脚本里把函数写错了,或者信号连接时节点路径没写对。第二类:Parse Error,多半是GDScript语法问题,比如中文字符串打成了中文引号,排查时先检查引号和括号。第三类:Can't open file,这类纠结在资源路径上,比如你从外部导入的资源忘了放到res://目录下。
如果你同时装了Cocos、Unity和Godot,互相之间不会冲突,但要注意环境变量和默认文件关联。比如系统里装了Cocos Creator和Godot,.tscn文件默认打开方式可能会被抢。我建议给Godot的可执行文件做桌面快捷方式,别再靠双击工程文件来打开。
依赖管理方面,最头疼的是Python虚拟环境。我以前直接在全局环境里装了一堆依赖,后来做AI请求测试时发现某个包版本被另一个项目搞坏了。现在我的做法是:凡是要跑Python脚本的AI工具,一律用Miniconda新建独立环境,比如conda create -n ai_game python=3.11,然后用conda activate ai_game每次进入。这个习惯帮我省掉了无数次“昨天还能跑今天突然报ImportError”的修复时间。
4.3 调试技巧:日志、假数据与无人值守测试
AI游戏最坑爹的是“偶发无法复现”。比如AI返回有时快、有时慢,有时合法JSON、有时打印一段废话。这种问题靠肉眼看屏幕是看不出来的,你必须让日志说话。我的做法是给AIInference.gd每个关键分支都加一行print("[AI][req] 开始请求")、print("[AI][resp] 收到JSON:")这样的输出,然后在开发模式下把日志完整保留。
还有一个非常推荐的做法:做一个“假AI”模式。在游戏设置里放一个开关,按F2键可以切换成规则脚本模拟AI返回,比如直接返回一个预先写好的JSON片段。这样你在调UI、调动画、测分数计算的时候完全不需要依赖AI服务,开发速度会翻倍。等UI稳定了再切回真实AI,遇到问题也能快速分清是AI的问题还是游戏逻辑的问题。
如果你性格像我一样懒,还可以写一个简单的Shell脚本,在每次启动Godot项目前自动拉起Ollama服务,并检查模型是否存在:
#!/bin/bash if ! command -v ollama &> /dev/null; then echo "Ollama 未安装" exit 1 fi if ! ollama list | grep -q "qwen2.5:7b"; then echo "模型不存在,开始拉取..." ollama pull qwen2.5:7b fi这个脚本看起来很简陋,但能避免我经常遇到的“项目都打开了才发现没启动模型”的尴尬。把这类重复操作脚本化,是独立开发提效的关键。
5. 从Demo到完整作品的进阶路线
5.1 扩展玩法:AI不只是裁判
当你把AI猜词这个Demo做稳定后,完全可以顺着同一条数据管线去做更多玩法。一个方向是“AI出题人”:由AI根据玩家选择的主题现场生成目标词,不局限于固定词库。实现方式是把目标词的选择也变成一个AI调用,返回一个JSON,包含词和对应的提示。这样每局游戏都是不一样的,重玩性立刻拉满。
另一个方向是“多轮AI对话解谜”:给AI设一个角色卡,让玩家通过对话去探索一个虚构世界的谜题。这个本质上就是从“猜词裁判”升级到“AI NPC”,数据结构上只需要把单轮请求改成多轮对话历史。Godot里可以用一个数组保存历史消息,每次请求时把它拼进prompt字段,模拟对话上下文。要注意的是,历史消息超过一定长度后,模型输出质量会下降,所以最好做一个滑动窗口,只保留最近10轮左右的对话。
5.2 美术素材与UI提升
纯逻辑Demo可以跑,但要做成能发出去给人玩的作品,UI和美术也得跟上。我的经验是,AI图片生成工具现在是新手最该依赖的“美术外挂”。我给自己做这套猜词游戏的背景图时,用的一个通用提示词模板是:“极简扁平化卡通场景,大面积留白,暖色调,不包含任何文字,适合作为游戏背景”。生成之后我再用Godot自带的TextureRect导入,调整裁切模式。这样半小时就能搞定一套不丢人的界面。
Blender我也会用一些,主要是做简单的3D图标,但你完全没必要一开始就学,用AI生图加Godot自带的Polygon2D画几个几何图形就够Demo用了。等游戏玩法证明好玩了,再回头补美术也不迟。
5.3 发布与后续优化
Godot导出游戏包可以说是我用过最省心的流程之一。项目菜单里选“导出”,选择Windows/Linux/macOS平台,点导出就会生成一个可执行文件。如果你有微信小游戏的需求,Godot也有转Web的导出选项,不过对交互频率这么高的AI游戏来说,Web端受浏览器网络策略影响比较大,个人建议先导出桌面版验证核心体验。
发布之后,数据埋点一定要做。我在游戏里记录每个词的平均猜中轮数、AI返回超时的次数、玩家输入的平均字数。这些数据用来决定要不要换AI模型、要不要改提示词、要不要调整计分规则。有时候你觉得“AI好像变蠢了”,结果看日志才发现是玩家输入太短导致模型缺乏上下文。数据不会说谎。
6. 写在最后的一些实践心得
整个项目从零到能跑,我大约花了两个晚上,加起来不到十小时。但这十小时里最花时间的不是写游戏逻辑,而是调试AI返回格式和试出合适的closeness阈值。我强烈建议你在做自己的AI游戏时,把前两小时全花在“写一个最小可用的AI返回解析器”上,而不是先沉迷搭界面或调动画。AI输出稳定了,其他一切都好说。
关于引擎,我不止一次被人问“Godot能不能做商业游戏”。我的回答是:你能把Demo做完再说。引擎永远不会是限制你做出好玩游戏的因素,真正限制你的是玩法、内容和完成度。把一个简单的AI玩法做完整,比在三个引擎之间反复横跳有价值得多。
最后再分享一个小技巧:开发AI游戏时,记得把AI服务端和游戏端分开跑,别在一个终端窗口里干两件事。Ollama加载模型后很占CPU,如果你还让它在后台占着终端输出日志,一阵子下来会明显卡顿。把服务放一个窗口,让日志自然流动,游戏在另一个窗口开发调试,两者互不干扰。这套“服务与客户端分离”的习惯,放到生产环境接云API时也是同样的道理。
这只是一个起点。当你把AI裁判做通,再尝试AI队友、AI生成关卡,你会发现所有AI游戏的底层逻辑高度一致:模型输出可靠的结构化数据,游戏循环消费这些数据,并用反馈闭环塑造玩家体验。抓住这一点,下次你面对新玩法时就不会发怵了。