Godot 中 AI 驱动的动态 NPC 对话
I’m happy to translate the article for you, but I’ll need the full text of the post (excluding the source link you’ve already provided). Could you please paste the article’s content here? Once I have it, I’ll translate it into Simplified Chinese while preserving the original formatting, markdown, and any code blocks or URLs.
概览
这是一个示例,展示如何根据玩家的任意输入为你的 Godot 游戏添加动态 AI 生成的回复,从而让 NPC 在与你对话时能够 保持角色设定。
你需要一个可以被游戏查询的 LLM 服务器。本教程使用运行在与游戏同一台机器上的本地 Ollama 服务器。工作流程如下:
- 获取玩家的文本输入。
- 向 LLM 发送特制的提示(prompt)。
- 在屏幕上显示 LLM 的回复。
- 记录对话内容,以便每次请求都包含足够的上下文。
你需要的东西
- Godot 4.x(示例使用 4.5.0)
- Ollama(任意模型均可)
本地 LLM 与 Ollama 设置
安装 Ollama
从官方站点下载 Ollama:
对于 Linux 可以运行:
curl -fsSL https://ollama.com/install.sh | sh
验证安装:
ollama
安装模型
在此浏览模型库:
本例使用小型 gemma3:1b 模型,您也可以选择其他模型。更大的模型在质量上更好,但会以速度和资源为代价。
ollama run gemma3:1b
命令完成后,您可以在终端与模型聊天。按 CTRL + D 退出。
启动 Ollama 服务器
ollama serve
服务器现在监听 http://localhost:11434。
API 请求格式
要生成响应,向 /api/generate POST 一个 JSON 请求体:
{
"model": "gemma3:1b",
"prompt": "",
"stream": false
}
Source: …
使用 Ollama 响应于 Godot 项目
场景设置
- 创建一个名为
Npc的 Node2D 节点。 - 为其添加一个 HTTPRequest 子节点。
- 在
Npc节点上启用 “Access as Unique Name”。
新建一个脚本 npc.gd 并将其挂载到 Npc 节点上。
导出的 NPC 属性
@export var age: int = 67
@export var gender: String = "male"
@export var profession: String = "farmer"
@export var name_: String = "Henry"
这些属性可以在检查器中编辑。
对话上下文
var context: String = ""
查询 Ollama 的函数
func respondTo(dialogText: String) -> void:
# 将玩家的台词追加到上下文中
context += "Player said: " + dialogText + "\n"
var prompt := """
You are an NPC in a medieval fantasy world named {name}, age {age},
gender {gender}, profession {profession}. The player, a seasoned mercenary,
says to you: {dialogText}. Respond in character, output only dialogue
(no action lines or descriptions), and keep the reply to one or two paragraphs.
Conversation context:
{context}
""".format({
"name": name_,
"age": age,
"gender": gender,
"profession": profession,
"dialogText": dialogText,
"context": context
})
var body := {
"model": "gemma3:1b",
"prompt": prompt,
"stream": false
}
var json_body := JSON.stringify(body)
$HTTPRequest.request(
"http://localhost:11434/api/generate",
["Content-Type: application/json"],
HTTPClient.METHOD_POST,
json_body
)
处理响应
func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
var parsed := JSON.parse_string(body.get_string_from_utf8())
if parsed.error != OK:
push_error("Failed to parse Ollama response")
return
var response_text := parsed.result.response
%TextDisplay.text = response_text
context += "You said: " + response_text + "\n"
连接信号
func _ready() -> void:
$HTTPRequest.request_completed.connect(_on_request_completed)
显示 NPC 回复的 UI
- 向场景中添加一个 Control 节点。
- 在其内部添加一个名为
TextDisplay的 RichTextLabel 节点。 - 在
TextDisplay上启用 “Access as Unique Name”。 - 根据需要调整标签的位置和大小。
现在,你可以在游戏的任何位置获取 Npc 节点的引用并调用:
$Npc.respondTo("Hello, traveler! What brings you to these woods?")
NPC 将使用大语言模型作答,且对话内容会保存在 context 中,后续提示能够记住之前的交流。
Source: …
从玩家获取文本输入
我们现在需要一种方式,让用户能够输入一条消息,以便我们的 NPC 作出回应。
- 在 Control 节点内部创建一个 LineEdit 节点。
- 调整它的大小并将其放置在屏幕上你想要的位置。
- 为该 LineEdit 附加一个新脚本,命名为
input.gd,并添加以下内容:
func _process(delta):
if Input.is_action_just_pressed("ui_submit"):
%Npc.respondTo(text)
text = ""
- 在 项目 → 项目设置 → 输入映射 中定义 ui_submit,并将其映射到实体 Enter 键。
节点层级
下面是最终节点层级的结构:
Node2D
├── Npc
│ └── HTTPRequest
└── Control
├── LineEdit
└── TextDisplay
试一试
运行你的场景,在输入框中键入任意内容,按 Enter,观察 NPC 的回应。
只要你愿意,聊天可以持续下去(受限于你的上下文窗口大小)。
由于传递的上下文,对话应保持一定的一致性,尽管不会 100 % 完美。
Example Dialogue
Player: 嘿,介绍一下你自己吧。
NPC: 亨利。亨利·布莱克伍德。我已经在这片土地上耕耘将近六十年了,你看。我的双手已被风霜侵蚀,但我的心……嗯,我的心仍然随着季节的节奏跳动。你呢,先生?是雇佣兵吗?我注意到你目光锐利。我见过的来去的人比山坡上的石头还多。不过别指望我会热情款待。只是……想要安静的生活而已。
Player: 是的,我是雇佣兵。你怎么知道的?
NPC: 这倒是相当……大胆的说法,年轻人。可以说我的眼睛注意到你步伐中的一丝不安。你举止中带有练习过的警觉,一种准备好的姿态,透露出你已经踏上旅程。像你这样的人,寻找超越收获的意义,不会在这里浪费时间于闲聊。
Player: 确实,我的目的超越了收获。我有一面需要修理的盾牌。村里有铁匠吗?
NPC: 是的,村里有个铁匠,就在井的那边。不过他脾气倔强,动作慢。他只做简单的铁板,手艺不错,虽然价钱不低。你要不要让我在等候时给你端点啤酒?
Player: 我对你心存感激。
NPC: 谢谢你,先生。这其实是个很简单的请求。来点温热的啤酒会很受欢迎。今天在田里忙了一整天,人需要稍作休息。
正如你所见,提示需要进行一些微调,但这已经是个不错的开始。更好的模型将比 gemma3:1b 表现得更好。
详细展开
你可以从这里出发,探索许多有趣的方向。你可以:
- 添加防护措施,让玩家不太可能破坏 AI(这是 AI 安全的一个很好的入门)。
- 添加功能,使 AI 的回应能够影响游戏状态(授予物品、治疗角色、发布任务)。
- 让 LLM 根据诸如时间等变量决定每个 NPC 在任何时刻应该做什么。
- 提示模型在 D&D 剧本中充当一种类似 DM 的角色,而不是单一的 NPC。
…等等。可能性基本上是无穷无尽的——祝你玩得开心,尽情探索!