前言
这两年最火的是什么?当然是大模型,人工智能划时代的突破。MUD游戏怎么充分利用人工智能呢?大模型是生成文本内容,而MUD游戏也是纯文字的,天然的契合,可玩的创意是非常多的,这里只抛转引玉,实现一个让NPC接入人工智能实现智能对话的功能。
示例
先看示例,大家可以直接进我开源的炎黄MUD体验:https://mud.ren/websocket.html 在游戏扬州客栈(新玩家出生地请选扬州)和示例NPC周不通(zhou butong)对话:

可以看到和NPC可以随意对话,而NPC本身的代码非常简单,一切都是用AI处理的。不仅可以随意闲聊,还可以直接问游戏中的内容,NPC直接调用知识库解答,虽然因为AI本身的问题不完全准确,但大部分内容都是正确的。而已NPC有记忆功能,知道你和他聊过什么,聊游戏无关的内容也会以自己的角色风格回答。
现在把已实现的功能简介如下:
- 基于人工智能的对话,可以聊任何内容。
- 每个NPC可以基于自己的角色扮演,使用符合身份的语气和玩家沟通。
- NPC自动记忆和玩家的聊天记录(下线也没事),而且是永久记忆所有历史对话数据(自动总结对话的历史记忆+详细聊天的最近记忆)。
- 实现了基本的好感度功能,和NPC聊天可以增加好感度(你可以继续扩展实现类似燕云十六声中的功能,比如好感度足够高会收到NPC的礼物)
- 实现了知识库的功能,允许NPC根据游戏help文档和你对话,避免胡说八道,而且实现了2套知识库,一套是基于关键词搜索的简单向量知识库,一套是基于千问
text-embedding-v4向量模型的知识库,优先使用语义理解的向量知识库,如果没配置向量API则转用本地基于关键词的基本知识库。 - 向量知识库支持缓存机制,对相同问题会使用内存缓存而不是调千问
text-embedding-v4向量模型,提升查询速度降低调用成本。
功能接入
怎么在你的MUD中接入AI功能?好消息是只需三步,对MUD代码几乎无改动,坏消息是只支持utf-8编码的MUD,因为AI响应的是UTF-8数据,我的配置也用的是json文件,老旧MUD赶紧升级吧,不要抱着GBK编码等死。
复制AI功能代码
- 把炎黄代码中的ai_service目录复制到你的MUD根目录中(主要是和HELP目录平级就行)
- 把炎黄代码中的AI_CLIENT_D守护进程和talk指令复制到你MUD的守护进程和指令目录中
修改NPC代码
在你的NPC中接入以下代码
在现有NPC的create()函数中添加:
set("ai_npc_id", "zhou butong"); // 对应json中的键名,如果不设置则默认npc id
在现有NPC中添加AI对话方法(talk指令调用,你也可以自己修改):
int accept_talk(object me, string topic) {
string player_id = me->query("id");
string player_name = me->name();
string context;
// 构造简洁的上下文情境信息(可自由灵活配置)
context = sprintf(
"时间:%s | 地点:%s | 天气:%s\n"
"玩家性别:%s | 玩家年龄:%s | 玩家门派:%s | 玩家***:%s",
NATURE_D->game_time(),
environment(this_object())->query("short") || "未知",
trim(remove_ansi(NATURE_D->outdoor_room_description())),
me->query("gender") || "未知",
me->query("age") ? sprintf("%d岁", me->query("age")) : "未知",
me->query("family/family_name") || "无门派",
me->query("family/master_name") || "无***"
);
if (!topic || topic == "") {
topic = "你好";
}
AI_CLIENT_D->send_chat_request(
query("ai_npc_id") || query("id"),
player_id,
player_name,
topic,
context
);
return 1;
}
这里代码就不用解释了,非常简单,你可以自己进一步改造,其中context部分附加给AI的自定义内容,你可以根据自己的MUD灵活提供,也可以不提供任何内容。
初始化AI数据并启动服务
以下都在ai_service目录下执行:
- 参考
config/npc_roles.json中的NPC配置,加入你的NPC配置,注意键名就是set("ai_npc_id", "zhou butong");指定的名称。 - 配置大模型环境变量
cd ai_service
echo "OPENAI_API_KEY=你的AI大模型API密钥" > .env
echo "OPENAI_BASE_URL=你的AI大模型API地址" >> .env
echo "OPENAI_MODEL=你的AI大模型名称" >> .env
echo "DASHSCOPE_API_KEY=你的千问API密钥" >> .env
- 初始化知识库
# 基础关键词搜索
python scripts/setup_basic.py
# 千问向量搜索(推荐)
python scripts/setup_qwen.py
运行脚本会自动读取help目录下所有文本文档生成向量知识库,如果你要更多内容就自己丰富你的HELP文档,然后删除data/basic_knowledge.db和qwen_knowledge.db重新运行脚本。
示例项目使用了最简单的实现,只通过 SQLite+NumPy 的组合轻量化的向量数据库,但对MUD项目完全满足需求。
- 启动AI服务
python main.py # 正常模式
python main.py -d # 调试模式(显示详细日志)
现在你在游戏中就可以和NPC交谈(talk)了。试试吧~
提示:请根据你自己游戏特色修改
ai_service\src\npc_manager.py中系统提示词游戏世界背景资料。
如果你对相关知识感兴趣,请继续看下面的内容
知识讲解
方案说明
从接入可看到对MUD的改动非常少,核心功能是独立封装的ai_service服务,为什么这样做,因为是这我个人认为MUD中接入AI的最佳方案,纯在MUD中接入AI也可以,但是需要自己写的代码就多了,而如果你让AI帮你写你会崩溃的,因为AI对LPC语言并不专业,就一个变量声明必须符合c89规范变量声明规则:必须在作用域开头声明变量,不能在代码中间声明变量。 AI都能一直犯错,写在提示词中让它记住也没用。而AI非常擅长Python和Javascript,用这二门语言写AI相关的功能也非常简单,所以你完全可以用AI用这二门语言实现AI功能,并用socket接口和MUD互通。不管是用TCP、UDP还是HTTP通讯都可以。
相关教程:LPC与Python使用SOCKET(HTTP模块)通信教程
MUD关键代码
本地服务使用udp通讯是更简单的,所以我这里AI提供的是UDP服务器 (udp_server.py): 监听127.0.0.1:9999,处理游戏客户端请求。而MUD游戏以客户端的身份和UDP服务请求数据,这个客户端以守护进程ai_client_d.c的方式提供服务。其中的代码也非常简单
void create() {
// 创建UDP socket
socket_fd = socket_create(DATAGRAM, "read_callback");
if (socket_fd < 0) {
debug_message("AI客户端守护程序: 无法创建socket - " + socket_error(socket_fd));
return;
}
// 绑定到任意端口
if (socket_bind(socket_fd, 0) < 0) {
debug_message("AI客户端守护程序: 无法绑定socket");
socket_close(socket_fd);
socket_fd = -1;
return;
}
}
// 数据接收回调
void read_callback(int fd, mixed message, string addr) {
mapping response;
string npc_id, player_id, ai_response;
object npc, player;
catch {
response = json_decode(message);
};
if (!response || !mapp(response)) {
debug_message("AI客户端守护程序: 空或无效响应");
return;
}
npc_id = response["npc_id"];
player_id = response["player_id"];
ai_response = response["response"];
// 处理响应
if (!npc_id || !player_id || !ai_response)
return;
npc = find_living(npc_id);
player = find_player(player_id);
if (npc && player) {
// 换行输出AI响应内容给玩家
tell_object(player, sort_string(ai_response, 78));
}
}
// 发送对话请求
varargs string send_chat_request(string npc_id, string player_id, string player_name, string message, string context) {
mapping request = ([
"type": "chat",
"npc_id": npc_id,
"player_id": player_id,
"player_name": player_name,
"message": message,
"context": context || "无内容"
]);
string json_str = json_encode(request);
int result;
if (socket_fd < 0) {
debug_message("AI客户端守护程序: socket未初始化");
return "AI服务未启动";
}
result = socket_write(socket_fd, json_str, AI_SERVER_HOST + " " + AI_SERVER_PORT);
if (result < 0) {
debug_message("AI客户端守护程序: 发送失败 - " + socket_error(result));
return "AI服务通信异常";
}
return "处理中...";
}
通过socket_create(DATAGRAM, "read_callback");创建socket客户端,指定read_callback回调接收AI发过来的消息,使用send_chat_request(string npc_id, string player_id, string player_name, string message, mapping context)发送消息给UDP服务器。
这里需要强调的是MUD中一定要socket_bind(socket_fd, 0);,熟悉socket接口的朋友都知道,UDP通信只需要服务端绑定端口接收消息,但是客户端如果不绑定端口只能发消息给服务端,收不到服务端发回来的消息,所以这里我们用socket_bind(socket_fd, 0);让系统自动选择一个可用端口,保证read_callback回调生效。
socket基础教程:LPC 语言基础教程:9.3 socket 接口介绍及游戏开发实战
一切就是这么简单~你学会了吧?
AI服务端
而关于ai服务的相关代码,是python的语法知识了,这里不过多介绍,只简单说明
- udp_server.py - 网络网关:接收游戏客户端请求,返回AI回复
- npc_manager.py - AI大脑:加载配置、生成回复、管理记忆
- memory_store.py - 长期记忆:存储NPC对玩家的关系数据
- history_manager.py - 对话记录:永久保存所有聊天记录,查询时返回最近指定数量作为上下文
- knowledge_qwen.py - 语义搜索:向量知识检索(千问)
- knowledge_basic.py - 关键词搜索:基础文本匹配回退方案
如果你对具体实现感兴趣,可以自己问AI,最简单的是用豆包,选AI编程,把炎黄github代码提供给它
然后有啥不懂的直接点开文件问豆包

需要说明的是NPC配置文件
{
"huang rong": {
"name": "黄蓉",
"title": "丐帮帮主",
"role": "女侠",
"personality": "聪明伶俐,古灵精怪,爱憎分明",
"background": "东邪黄药师之女,丐帮帮主,精通厨艺和打狗棒法。",
"greeting": "这位公子,要不要尝尝我亲手做的叫花鸡?",
"topics": ["美食", "武功", "江湖", "爱情", "厨艺"],
"speech_style": "活泼俏皮,机智幽默",
"knowledge_base": ["烹饪", "武功", "江湖秘闻", "奇门遁甲"],
"knowledge_threshold": 0.6,
"memory_capacity": 80,
"relationship_tips": {
"gifts": ["美食", "新奇玩意", "珍贵食材"],
"topics": ["各地美食", "江湖趣事", "爱情故事"],
"taboos": ["虚伪", "欺负弱小", "不讲义气"]
}
}
这里设置了NPC的身份,用于系统提示词,具体使用参考npc_manager.py代码:
# 构建系统提示(只有静态内容,用于缓存命中优化)
system_prompt = f"""# 角色设定
## 基本信息
**姓名**: {npc_config['name']}
**身份**: {npc_config['title']}
**角色**: {npc_config['role']}
## 人物性格
- **性格特征**: {npc_config['personality']}
- **背景故事**: {npc_config['background']}
- **说话风格**: {npc_config['speech_style']}
- **初次问候**: {npc_config['greeting']}
### 📚 人物专属知识
{person_knowledge}
## 互动偏好
| 类别 | 内容 |
|------|------|
| **擅长话题** | {', '.join(npc_config['topics'])} |
| **喜好礼物** | {', '.join(npc_config.get('relationship_tips', {}).get('gifts', []))} |
| **喜欢话题** | {', '.join(npc_config.get('relationship_tips', {}).get('topics', []))} |
| **忌讳话题** | {', '.join(npc_config.get('relationship_tips', {}).get('taboos', []))} |
**表达要求**:
- **角色化**: 始终用{npc_config['name']}的身份和语气讲述
- **流畅叙述**: 用流畅的口语化叙述表达,避免生硬列表
- **准确表述**: 准确使用参考知识中具体门派、人物、地点、技能名称等内容
- **颜色运用**: 在关键门派、武功、指令等处可巧妙使用ANSI颜色增强可读性
"""
其中knowledge_base可以提供更详细的内容做NPC本人特有的知识,这里只是简单的示例,实际上你可以自己写更详细的专属知识内容,NPC会利用这些知识来解答问题。
而knowledge_threshold影响的不是这个基本知识,而是从向量知识库中查找的命中阈值,比如如果你不希望这个NPC用向量知识库中的内容回答玩家,把这个阈值改到1,这样就不会用到向量知识库了。
另外memory_capacity用来指定这个NPC记忆容量,就是记忆和玩家的多少条对话内容,配置少可减少token消耗,记忆容量外的更多历史对话NPC只能记忆概要,如果配置为0代表无记忆的单轮对话。
关于向量知识库的知识,如果你感兴趣,可以看:文本嵌入(Embedding)技术完全教程
有问题请在本贴留言,有任何好的创意想法也可以在此交流。