语音指挥农夫自动寻路!行空板K10+MCP打造AI 3D农场

2026-11-03 15:10:56 30阅读

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

3D 农场全景:戴草帽的农夫站在田格地形中,周围有农舍、树木与目标实体

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

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

  • 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 目录,离线可用,教室网络不稳定也不影响;
  • 采用普通 标签(非 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 张棋子图片

xiaozhi-farm 工程目录结构截图></p><h2>三、材料清单</h2><table><thead><tr><th>名称</th><th>说明</th><th>数量</th></tr></thead><tbody><tr><td>行空板 K10</td><td>集成 ESP32-S3、2.8 寸 LCD、双麦克风、扬声器、摄像头、光感、温湿度</td><td>1</td></tr><tr><td>USB-C 数据线</td><td>用于烧录和供电</td><td>1</td></tr><tr><td>Windows 电脑</td><td>推荐 Win10/11,需能上网</td><td>1</td></tr></tbody></table><p>行空板 K10 是专为 AI 教育设计的高集成开发板,<strong>100% 采用国产芯片</strong>,主要规格:</p><ul><li>MCU:ESP32-S3,32 位双核 240MHz;</li><li>2.8 英寸彩色 LCD,板载摄像头、双麦克风、扬声器、数字环境光与温湿度传感器;</li><li>支持 2.4G Wi-Fi、蓝牙 5.0,支持 MicroPython 和 Mind+ 图形化编程;</li><li>原生支持 MCP、DeepSeek、边缘计算,非常适合本项目。</li></ul><p><img data-media-id=

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

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

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

2D 农场地图地形布局与棋子摆放效果></p><p>角色图片:<br><img data-media-id=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 开始烧录。

Flash Download Tool 烧录小智固件的界面截图></p><h2>八、配网与设备激活</h2><ol><li>烧录完成后按一下背面 <strong>RST</strong> 键重启,K10 会进入 Wi-Fi 配置模式,屏幕显示热点名称和配置地址(<code>192.168.4.1</code>);</li><li>用电脑或手机连接 K10 的热点,在弹出的配置页面填入<strong>教室 Wi-Fi 的 SSID 和密码</strong>(注意:K10 只支持 <strong>2.4GHz</strong> Wi-Fi);</li><li>连接成功后 K10 自动重启,屏幕显示一个 <strong>6 位设备验证码</strong>;</li><li>浏览器打开 <code>xiaozhi.me</code>,用手机号注册并进入<strong>控制台</strong>,点击「添加设备」,输入屏幕上的 6 位验证码完成绑定;</li><li>在「配置角色」中可以选择角色音色、语言模型(如 Qwen 实时、DeepSeek V3)和角色介绍,保存后重启设备生效。</li></ol><h2>九、获取 MCP 接入点并连接</h2><p>这是把硬件和网页连起来的最后一步:</p><ol><li>在小智控制台进入智能体的「<strong>配置 → 扩展能力 → MCP 接入点</strong>」,复制 WebSocket 地址,格式形如:<br><code>wss://api.xiaozhi.me/mcp/?token=你的专属令牌</code>;</li><li>把这个地址粘贴到网页左侧的输入框,点击「<strong>连接并注册行为</strong>」;</li><li>当状态显示「<strong>就绪</strong>」、工具卡片圆点变绿,说明 7 个农场行为已经注册成功。</li></ol><p>之后用唤醒词「<strong>你好,小智</strong>」唤醒 K10,就可以直接语音对话了。板载两个按键:A 键短按可中断/唤醒、长按调音量加;B 键短按中断/唤醒、长按调音量减。</p><h2>第二阶段:升级为真 3D,让角色动起来</h2><p>逻辑和硬件都验证完毕后,第二阶段专注<strong>表现层升级</strong>:用 Three.js 把 Canvas 2D 替换成真正的 WebGL 三维场景,并给角色加上丰富的动态效果。</p><h2>十、Three.js 本地 3D 场景</h2><h3>10.1 为什么选 Three.js r128</h3><p>我选用 <strong>Three.js r128</strong>,原因是这个版本提供非模块的 UMD 构建(<code>three.min.js</code>),可以直接用普通 <code><script></code> 引入,和现有工程结构完全一致,不需要 importmap 或打包工具。</p><p><code>index.html</code> 中把库放在 <code>farm.js</code> 之前加载:</p><pre><code><script src=

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,数据和画面一致。

3D 农场动态效果截图:农夫行走、路径光点与目标标记圆环

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

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

  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 创客教学的老师和同学一些启发。代码已整理,欢迎交流、复现和二次创作!

附:参考资源

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


作者:云天
链接:https://www.aiddithome.com/p/18f433381855f.html
本作遵循 [CC BY 4.0 署名] 许可,版权归作者所有。若存在违规行为,您可以点击 “举报”。


登录 后发表评论
4条评论
触底反弹的Tony
1楼 · 2026-11-03 15:11:34

先把 2D 跑通再升级 3D 这个思路很实用,我们社团上学期直接上 3D,学生光调渲染就劝退了一半。分层推进确实是对的。

锡点
2楼 · 2026-11-03 15:11:34

A* 手写而不调库这点赞,二叉堆那段讲得清楚,正好给学生补数据结构。目标格本身是阻挡那个坑我也踩过,走目标旁边才是正解。

第三次终于亮了
3楼 · 2026-11-03 15:11:34

网页端做 MCP Server 而不是客户端这个设计点一开始真没想到,细想确实合理,工具是网页提供的。收藏了周末复现一下。

线比代码多
4楼 · 2026-11-03 15:11:34

K10 只支持 2.4G Wi-Fi 这个提醒很重要,教室里全是 5G 频段的话配网会卡住,去年吃过这个亏。