语音指挥农夫自动寻路!用行空板 K10 + MCP + Three.js 打造一个 AI 3D 农场

2026-11-016

语音指挥农夫自动寻路!用行空板 K10 + MCP + Three.js 打造一个 AI 3D 农场(创客社团教学实战)

本文同步记录了一个完整的创客教学项目:从 2D 到 3D,用一个网页把 MCP 协议调用、AI 语音对话、A 自动路径规划、WebGL 三维渲染* 串到了一起。项目可用于创客社团课堂演示,学生可以直接对着行空板 K10 说话,屏幕里的农夫就会自己绕开障碍、走到目标旁边。


语音指挥农夫自动寻路!用行空板 K10 + MCP + Three.js 打造一个 AI 3D 农场_image_1.webp

一、项目背景:为什么要做这个项目

在创客社团的教学中,我一直在找一个能把几个"时髦但抽象"的知识点一次性讲清楚的载体:

  • MCP 到底是什么,AI 是怎么"调用外部工具"的?
  • AI 语音对话不只是聊天,能不能让它真正去"做事"?
  • 游戏里的角色是怎么自动寻路、自动找目标的?
  • 网页除了平面 UI,还能做真 3D 实时渲染吗?

如果分开讲,学生很难建立整体认知。于是我设计了"小智农场 MCP 控制台"这个项目:

  • 屏幕里是一个 20 × 14 的农场,有农舍、谷仓、池塘、水渠、农田、树木、岩石,还有南瓜、绵羊、苹果树、母鸡、干草捆 5 个目标;
  • 一个戴草帽的农夫站在农场大门;
  • 学生对行空板 K10 说一句"让农夫去找南瓜",云端把语音意图转成 MCP 工具调用,网页收到后用 A 算法自动规划路径*,农夫就会绕开水渠和障碍,一步一步走到南瓜旁边,并冒出对话气泡。

整个过程没有任何人工点击,学生能亲眼看到"语音 → AI → 工具调用 → 路径规划 → 角色动作 → 结果回报"的完整闭环。
演示视频


二、整体方案与工作原理

先上一张系统架构图,理解数据是怎么流动的:

学生语音
     │  "让农夫去找南瓜"
     ▼
┌──────────────┐   WebSocket    ┌────────────────┐
│  行空板 K10   │ ◀───────────▶ │   小智 AI 云端   │
│ (小智固件)    │                │  语音识别/大模型 │
└──────────────┘                └────────┬───────┘
                                        │ MCP (JSON-RPC 2.0)
                                        │ initialize / tools/list / tools/call
                                        ▼
                            ┌────────────────────────┐
                            │  网页(MCP Server)      │
                            │  7 个农场工具 + A* 寻路   │
                            │  Three.js 3D 渲染 + 动画  │
                            └────────────────────────┘

这里有一个反直觉但非常关键的设计点,也是课堂上我重点强调的:

网页并不是 MCP 客户端,而是扮演 MCP Server(工具提供方)。网页主动连小智的 WebSocket 接入点后,是小智云端反过来向网页发起 initialize 握手、tools/list 拉取工具清单、tools/call 调用工具。网页把"移动农夫"的能力作为工具暴露出去,AI 才能调用它。

技术选型上刻意保持简单,方便教学和学生课后复现:

  • 纯原生 HTML + CSS + JavaScript,不使用任何框架、不做构建、不用 npm,浏览器直接双击打开即可运行;
  • 所有第三方库(Three.js、OrbitControls)都下载到本地 lib 目录,离线可用,教室网络不稳定也不影响;
  • 采用普通 <script> 标签(非 ES Module),对初学者更友好。

工程目录结构如下:

xiaozhi-farm/
├─ index.html          # 唯一入口
├─ css/styles.css      # 三栏布局、木纹相框风格
├─ js/
│   ├─ map.js          # 地图数据、实体、目标、邻格计算
│   ├─ pathfinder.js   # A* 寻路算法
│   ├─ farm.js         # 2D/3D 渲染与角色动画(两次迭代的核心)
│   ├─ mcp.js          # MCP 服务端行为、握手、心跳、重连
│   └─ app.js          # 7 个行为实现、对话、指令解析
├─ lib/                # three.min.js、OrbitControls.js(3D 阶段新增)
└─ assets/             # 2D 阶段的 10 张棋子图片

语音指挥农夫自动寻路!用行空板 K10 + MCP + Three.js 打造一个 AI 3D 农场_image_2.webp


三、材料清单

名称 说明 数量
行空板 K10 集成 ESP32-S3、2.8 寸 LCD、双麦克风、扬声器、摄像头、光感、温湿度 1
USB-C 数据线 用于烧录和供电 1
Windows 电脑 推荐 Win10/11,需能上网 1

行空板 K10 是 DFRobot 专为 AI 教育设计的高集成开发板,100% 采用国产芯片,主要规格:

  • MCU:ESP32-S3,32 位双核 240MHz;
  • 2.8 英寸彩色 LCD,板载摄像头、双麦克风、扬声器、数字环境光与温湿度传感器;
  • 支持 2.4G Wi-Fi、蓝牙 5.0,支持 MicroPython 和 Mind+ 图形化编程;
  • 原生支持 MCP、DeepSeek、边缘计算,非常适合本项目。

语音指挥农夫自动寻路!用行空板 K10 + MCP + Three.js 打造一个 AI 3D 农场_image_3.webp

第一阶段:先做一个能跑通的 2D 版本

我没有一上来就追求 3D,而是先用 Canvas 2D 把整条业务链路跑通。这也是教学上的建议——先验证逻辑,再升级表现层,能把问题分层、降低排错难度。
语音指挥农夫自动寻路!用行空板 K10 + MCP + Three.js 打造一个 AI 3D 农场_image_4.webp

四、地图与数据结构设计

地图是 20 列 × 14 行,用一串字符来描述地形,每种字符对应不同的可通行性和移动代价:

字符 地形 通过代价 说明
. 草地 1 普通地面
R 土路 1 大门道路
F 农田 3 可走但更耗时(泥泞)
W 水渠 0(阻挡) 不可通行
P 池塘 0(阻挡) 不可通行
B 建筑位 0(阻挡) 农舍/谷仓占地
T 障碍位 0(阻挡) 树木/岩石占地

地图地形布局:

var TERRAIN_ROWS = [
  "....................",
  "....................",
  "..BB...........BB...",
  "..BB...........BB...",
  "....................",
  "....................",
  "..T.................",
  "......WWWWWWWW......",
  ".......FFFFFFF......",
  "..PP...FFFFFFF......",
  "..PP...FFFFFFF......",
  "....................",
  "......T......TT.....",
  "..........R........."
];
var COST = { ".": 1, "R": 1, "F": 3, "W": 0, "P": 0, "B": 0, "T": 0 };

除了地形阻挡,实体(如南瓜、绵羊这些本身占据格子的物体)也会被加进一张"动态阻挡表" EXTRA_BLOCKED,农夫同样不能穿过它们。

农夫的起点设在大门 (10, 13),5 个目标和 6 个地名到达点都在数据里写死,方便检索。

2D 阶段的 10 张棋子图片(农夫、绵羊、母鸡、南瓜、苹果树、干草捆、谷仓、农舍、大树、岩石)用 AI 生成为统一的等距风格,Canvas 负责把它们绘制到对应格子上。

语音指挥农夫自动寻路!用行空板 K10 + MCP + Three.js 打造一个 AI 3D 农场_image_5.webp

角色图片:
语音指挥农夫自动寻路!用行空板 K10 + MCP + Three.js 打造一个 AI 3D 农场_image_6.webp

语音指挥农夫自动寻路!用行空板 K10 + MCP + Three.js 打造一个 AI 3D 农场_image_7.webp

五、核心算法:A* 自动路径规划

这是整个项目最"硬核"的部分。我让学生手写了一个 A(A-Star)算法*,而不是直接调用现成库。

5.1 A* 的基本思想

A* 是一种启发式搜索算法,每个格子用一个综合评分 f 决定优先探索谁:

f = g + h
  • g:从起点走到当前格子已经付出的真实代价(考虑地形,农田一格代价 3);
  • h:从当前格子到终点的估计代价(启发值),这里用曼哈顿距离;
  • 每次优先扩展 f 最小的格子,直到找到终点,再沿"父节点"回溯出完整路径。

因为只允许上下左右移动,启发函数用曼哈顿距离:

var h = Math.abs(nc - goalCol) + Math.abs(nr - goalRow);

5.2 用最小二叉堆提升性能

为了高效地每次取出 f 最小的节点,我实现了一个最小二叉堆(而不是每次都遍历整个数组排序),这也是一个很好的数据结构教学点:

MinHeap.prototype.push = function (node) {
  var a = this.a, i = a.length;
  a.push(node);
  while (i > 0) {
    var parent = (i - 1) >> 1;
    if (a[parent].f <= a[i]) { break; }
    var tmp = a[parent]; a[parent] = a[i]; a[i] = tmp;  // 上浮
    i = parent;
  }
};

5.3 带地形代价的主循环

主函数返回一条由 {col, row} 组成的路径,不可达则返回 null。核心扩展逻辑:

for (var i = 0; i < 4; i++) {
  var nc = current.c + DIRS[i][0], nr = current.r + DIRS[i][1];
  if (!Map.isWalkable(nc, nr)) { continue; }          // 阻挡格跳过
  var step = Map.tileCost(nc);                         // 农田代价 3
  var tentative = gScore[ck] + step;
  if (gScore[nk] === undefined || tentative < gScore[nk]) {
    gScore[nk] = tentative;
    came[nk] = ck;                                     // 记录父节点用于回溯
    var h = Math.abs(nc - goalCol) + Math.abs(nr - goalRow);
    open.push({ c: nc, r: nr, f: tentative + h });
  }
}

5.4 一个真实的坑:目标格本身是阻挡的

这是 2D 阶段最有价值的一次"踩坑",强烈建议在课堂上让学生先自己踩一遍。

问题现象:调用 find_target 找南瓜时,A* 直接返回"不可达"。

原因分析:南瓜所在的格子被设成了阻挡(农夫不能站到南瓜身上),而最初的逻辑把"目标格本身"当成了路径终点。A* 发现终点不可走,自然判定不可达。

修复思路:农夫不需要站到目标上,只要走到目标旁边就可以。于是:

  1. 在 map.js 增加 approachPoints(),返回目标上、下、左、右四个邻居中可通行的格子;
  2. 在 app.js 增加 approachMove(),对每个候选邻格分别算路径,用 findNearest 选出路径最短的那个邻格走过去。
function approachPoints(col, row) {
  var result = [];
  [[1, 0], [-1, 0], [0, 1], [0, -1]].forEach(function (d) {
    var nc = col + d[0], nr = row + d[1];
    if (isInside(nc, nr) && isWalkable(nc, nr) && !EXTRA_BLOCKED[nc + "," + nr]) {
      result.push({ col: nc, row: nr });
    }
  });
  return result;
}

"巡逻"功能就是复用这个逻辑:对 5 个目标依次异步执行 approachMove,农夫会一个接一个地走到每个目标旁边。

这个 bug 的教学意义在于:真实项目里,数据约束和算法假设往往对不上,需要在两者之间做适配。

六、MCP 网页:把"移动农夫"注册成 7 个工具

6.1 七个农场行为

我在网页里实现了 7 个行为,每个都对应一个带标准 JSON Schema 的 MCP 工具:

工具名 功能 主要参数
goto_place 按地名前往(农舍/谷仓/池塘/麦田/水渠/大门) place
goto_coordinate 按网格坐标前往 x、y
find_target 自动查找目标并走到旁边 target
patrol 自动巡逻,依次访问全部 5 个目标 无
stop 停止当前移动 无
get_status 获取农夫位置与农场状态 无
say 让农夫说话(气泡+对话面板) text

工具定义里用 inputSchema 描述参数,AI 才知道该怎么填:

{
  name: "goto_place",
  description: "指挥农场中的农夫前往指定地点,自动规划路径并沿路径走到目标。",
  inputSchema: {
    type: "object",
    properties: {
      place: { type: "string", enum: ["农舍", "谷仓", "池塘", "麦田", "水渠", "大门"] }
    },
    required: ["place"]
  }
}

6.2 MCP 握手与消息分发

网页连上 WebSocket 后,按 MCP 协议(版本 2024-11-05,基于 JSON-RPC 2.0)处理云端的请求:

  • 收到 initialize → 返回协议版本、能力和服务端信息;
  • 收到 notifications/initialized → 握手完成,进入就绪状态;
  • 收到 tools/list → 返回 7 个工具的完整定义;
  • 收到 tools/call → 根据工具名找到对应的异步处理函数执行,再把结果通过 JSON-RPC 回传。
if (msg.method === "initialize") { this.handleInitialize(msg); return; }
if (msg.method === "tools/list") { this.handleToolList(msg); return; }
if (msg.method === "tools/call") { this.handleToolCall(msg); return; }

此外还实现了心跳保活(每 30 秒发一次 ping)和断线自动重连(指数退避,最多 30 秒),保证长时间演示的稳定性。

6.3 移动的节奏控制

农夫在格子间移动时做了平滑插值,而不是生硬地"跳格"。并按地形区分耗时:

  • 普通格(草地/土路):约 250ms 走一格;
  • 农田格:约 470ms 走一格,体现"泥泞走得慢"。

新指令到来时,如果农夫正在移动,会先 stop 旧任务再接管,避免动作冲突。


行空板 K10 小智配置(硬件环节)

2D 逻辑跑通后,就可以配置硬件,让学生用语音来驱动了。

七、刷入小智固件

小智 AI 来自开源项目 xiaozhi-esp32(GitHub:https://github.com/78/xiaozhi-esp32)。官方为行空板 K10 提供了免编译固件,直接用烧录工具刷写即可。

  1. 长按 K10 背面的 Boot 键,同时用 USB-C 连接电脑;
  2. 打开 Flash Download Tool,芯片类型选 ESP32-S3,工作模式 Develop,加载模式 UART;
  3. 选择解压出的 .bin 固件,起始地址填 0x00,勾选对应行;
  4. 选择正确的 COM 口,波特率设为 1152000;
  5. 先点 ERASE 擦除原有固件,完成后再点 START 开始烧录。

语音指挥农夫自动寻路!用行空板 K10 + MCP + Three.js 打造一个 AI 3D 农场_image_8.webp

八、配网与设备激活

  1. 烧录完成后按一下背面 RST 键重启,K10 会进入 Wi-Fi 配置模式,屏幕显示热点名称和配置地址(192.168.4.1);
  2. 用电脑或手机连接 K10 的热点,在弹出的配置页面填入教室 Wi-Fi 的 SSID 和密码(注意:K10 只支持 2.4GHz Wi-Fi);
  3. 连接成功后 K10 自动重启,屏幕显示一个 6 位设备验证码;
  4. 浏览器打开 xiaozhi.me,用手机号注册并进入控制台,点击「添加设备」,输入屏幕上的 6 位验证码完成绑定;
  5. 在「配置角色」中可以选择角色音色、语言模型(如 Qwen 实时、DeepSeek V3)和角色介绍,保存后重启设备生效。

九、获取 MCP 接入点并连接

这是把硬件和网页连起来的最后一步:

  1. 在小智控制台进入智能体的「配置 → 扩展能力 → MCP 接入点」,复制 WebSocket 地址,格式形如:
    wss://api.xiaozhi.me/mcp/?token=你的专属令牌;
  2. 把这个地址粘贴到网页左侧的输入框,点击「连接并注册行为」;
  3. 当状态显示「就绪」、工具卡片圆点变绿,说明 7 个农场行为已经注册成功。

之后用唤醒词「你好,小智」唤醒 K10,就可以直接语音对话了。板载两个按键:A 键短按可中断/唤醒、长按调音量加;B 键短按中断/唤醒、长按调音量减。


第二阶段:升级为真 3D,让角色动起来

逻辑和硬件都验证完毕后,第二阶段专注表现层升级:用 Three.js 把 Canvas 2D 替换成真正的 WebGL 三维场景,并给角色加上丰富的动态效果。

十、Three.js 本地 3D 场景

10.1 为什么选 Three.js r128

我选用 Three.js r128,原因是这个版本提供非模块的 UMD 构建(three.min.js),可以直接用普通 <script> 引入,和现有工程结构完全一致,不需要 importmap 或打包工具。

index.html 中把库放在 farm.js 之前加载:

<script src="lib/three.min.js"></script>
<script src="lib/OrbitControls.js"></script>

10.2 坐标系统与场景搭建

3D 世界里每格对应 1 个单位,y 轴向上,网格坐标和世界坐标互相转换:

function wx(col) { return col - COLS / 2 + 0.5; }
function wz(row) { return row - ROWS / 2 + 0.5; }

场景包含:

  • 透视相机斜俯视,配合 OrbitControls 实现拖拽旋转、滚轮缩放,并限制极角(不会转到地面以下)、开启阻尼;
  • 灯光:半球光 + 环境光 + 带阴影的平行光(阴影贴图 2048),地面接收阴影、物体投射阴影,立体感很强;
  • 地形:每格一个薄方块,按地形用不同材质,农田加垄沟、水面半透明,再点缀小草和小花;
  • 实体全部用几何体搭建,不再依赖图片贴图:农舍/谷仓用墙体 Box + 四棱锥坡屋顶,大树用圆柱树干 + 多面体树冠,岩石用十二面体,南瓜用压扁的球体加棱,干草捆用横放圆柱加绳环……

10.3 保持接口不变,渲染层无缝替换

这是工程上的一个关键设计:重写 farm.js 内部实现,但对外的方法签名全部保留(start、moveAlong、stop、setBubble、markTarget、getFarmer 等),所以 app.js 的业务逻辑几乎不用改,大大降低了升级风险。

十一、角色动态效果(本项目的亮点)

3D 农夫是一个组合 Group,由腿、身体、手臂、头、草帽等部件组成,每个可动部件都保留引用,用一个 walkPhase 变量驱动动画。

11.1 行走动画

移动时 walkPhase 随时间累积,双腿、双臂绕关节用正弦函数反相摆动,身体随步伐上下弹跳:

u.walkPhase += dt * 0.013;
var p = u.walkPhase;
u.legL.rotation.x =  Math.sin(p) * 0.85;
u.legR.rotation.x = -Math.sin(p) * 0.85;
u.armL.rotation.x = -Math.sin(p) * 0.7;
u.armR.rotation.x =  Math.sin(p) * 0.7;
u.inner.position.y = Math.abs(Math.sin(p)) * 0.12;   // 步伐弹跳

11.2 平滑转向

移动时根据格子间的方向计算目标朝向,再用最短角度差做插值,农夫会自然地转身而不是瞬间换向:

var targetYaw = Math.atan2(dx, dz);
root.rotation.y = lerpAngle(root.rotation.y, targetYaw, 0.18);

11.3 待机与动物动画

  • 农夫待机时有轻微的呼吸浮动,四肢缓缓回正;
  • 母鸡会周期性低头啄米,绵羊慢慢晃头,树木和树冠随微风摇摆;
  • 路径用白色路径点 + 一个红色流动光点沿路径循环移动,目标用脉冲圆环标记;
  • 对话气泡用 Canvas 生成纹理、以 Sprite 跟随农夫头顶,始终面向相机、不被遮挡。

这些动态效果我在验证时通过实时读取关节角度确认:移动中腿臂角度在 ±0.85 之间持续变化、身体弹跳高度最高约 0.12,数据和画面一致。

语音指挥农夫自动寻路!用行空板 K10 + MCP + Three.js 打造一个 AI 3D 农场_image_9.webp

十二、迭代中的两个小需求

课堂演示后又根据反馈做了两处实用迭代,这里一并记录:

  1. 苹果树上要真的结苹果:最初苹果被埋在树冠球体内部看不见。我把单个树冠改成三个错落的叶球留出缝隙,把苹果增加到 10 个,分三层长到树冠外表面,并用带高光的鲜红材质,从任何角度都能清楚看到果实。
  2. 农场场景全屏显示:用标准 Fullscreen API(兼容 webkit 前缀)给地图框加了全屏按钮,点击后场景以 1920×1080 填满整个屏幕、去掉边框和侧栏,方便在投影仪上做沉浸式演示,按 Esc 或再点按钮即可退出。

十三、课堂教学应用

这个项目在创客社团里的实际用法非常灵活:

  • 演示模式:全屏投影,老师或学生语音下达指令,大家一起观察农夫如何规划路径、绕开障碍;
  • 互动提问:让学生预测"农夫会走哪条路""为什么农田走得慢""目标挡住了怎么办",再实际验证;
  • 分层拓展任务:
    • 入门:修改地图字符,增加一堵墙或一条水渠,观察寻路变化;
    • 进阶:新增一个目标实体和对应的 MCP 工具;
    • 挑战:优化 A*(如支持斜向移动、加入更多地形代价),或给角色设计新的动画。

一个项目就能覆盖数据结构(二叉堆)、算法(A* 搜索)、网络协议(MCP/JSON-RPC/WebSocket)、人工智能(语音+大模型)、计算机图形学(WebGL/3D) 五大模块,知识点密度很高,又足够有趣,学生参与度明显比纯讲理论高。


十四、总结与展望

回顾整个制作过程,最大的体会是分层推进的价值:

  1. 先用 2D 跑通 MCP + A* + 业务逻辑,把"目标格阻挡"这类逻辑问题解决掉;
  2. 再配置行空板 K10 小智固件,打通语音链路;
  3. 最后用 Three.js 无缝升级 3D 表现层和角色动画。

每一步都可独立验证,出了问题能快速定位,这对教学和初学者尤其重要。

后续还可以继续拓展的方向:

  • 接入板载摄像头,让 AI 结合视觉识别来下判断;
  • 加入"种植、浇水、收获"等可改变地图状态的行为,形成完整的经营玩法;
  • 支持多角色、多设备协同;
  • 把场景对接到 VR/AR,进一步提升沉浸感。

希望这篇教程能给同样在做 AI 创客教学的老师和同学一些启发。代码已整理,欢迎大家在 DF 创客社区交流、复现和二次创作!


附:参考资源

  • 小智 AI 开源项目:https://github.com/78/xiaozhi-esp32
  • 小智官网与控制台:https://xiaozhi.me
  • 行空板 K10 小智配置官方教程:行空板官网 Wiki(搜索"K10 小智 AI")
  • Three.js 官网:https://threejs.org

创作许可协议

本项目采用 CC BY(署名) 进行许可。

评论(0)
- 没有更多了 -