把《春》搬进一座能走的园林:AI 生成的 3D 网页 + 行空板课堂教具

2026-11-055
精华项目

把《春》搬进一座能走的园林:AI 生成的 3D 网页 + 行空板课堂教具

统编版语文七年级上册第一单元 · 第 1 课时 · 一套能真的上课的软硬件教具

教师端入口:文园寻芳牌坊与三个任务点

一、创作背景

备《春》第 1 课时的时候,我手里的教案写着一条动线:「游园先寻宝藏」→「芳径徐徐,吟赏春声」→「移步春郊,纵观全貌」。很多 AI 课件把「情境」做成一张背景图,加个古风边框就算情境教学了。这份教案不一样,它自己就是一条游园路线,而且寻宝、走小径、登高望远,刚好对上字词、朗读、结构这个由小到大的认知顺序。

所以这次想试的其实是一件事:能不能让情境当骨架,而不是当包装。借行空板 M10 做学生终端,让 AI 帮忙把统编版语文七年级上册第一单元的《春》做成一套能上课的教具 —— 教师在浏览器里的 3D 春日园林走动,走到任务点,题目自动飞到每组学生手上的行空板,学生触摸作答,结果实时回到教师端看板。

从拆教案到端到端联调通过,前后大约 17.5 小时。其中真正写代码不到一半,剩下全花在跟 AI 反复改视觉、改排版、改真机适配上。这篇把过程拆成可复制的提示词,把踩过的坑也一并写出来。要照着复刻,按顺序走就行。

先说明一句:下面的提示词是按成品代码和当天制作记录整理出来的可复现版本,不是当初对话的逐字稿。原始对话没留档。每一轮我都保留了真实约束(端口、字段、屏幕尺寸、那个让人头疼的字号单位),因为少了这些,AI 给的东西跑不起来。另外别指望一轮出成品,我几乎所有轮次都改了两三遍。

二、作品简介

2.1 两个部分,中间一根线

本作品主要分为两个部分:教师端的 3D 网页和学生端的行空板终端。两边技术栈完全不同(JavaScript 对 Python),中间放了一个 Node 中继当胶水,不算独立部分,但少了它整条链路就断了。

教师端 中继 学生端
技术 Vite + Three.js Node 22 + ws Python 3.7 + unihiker
运行 浏览器 :5173 WS :8801 / HTTP :8800 行空板 M10 × 8
屏幕 1600 × 900 无界面 240 × 320 触摸
职责 3D 场景、任务卡、作答看板 保存状态、转发消息 等待、答题、反馈、完成

三个任务点分别对应教案的任务一到任务三:春园拾珍(字词,3 题)、芳径吟春(重音与停连,2 题)、漫步春郊(五幅春景图与描写顺序,3 题)。题目全部取自教案原文,答案和解析放在 teacher/src/tasks.js,换课改这一个文件就行。教案里的任务四「亭下品芳」属于第 2 课时,这次没做,场景里的六角亭先摆着。

三个任务点:一条路走到底

2.2 作品视频演示

▶ 观看作品演示视频(约 92 秒,中文配音 + 逐句字幕)

视频用微软 Edge 的在线语音合成配的旁白,按小句切分、字幕窗口取每句音频的真实时长,所以字幕不会飘。画面全部是本文这些实拍截图。如果论坛不方便外链视频,可以按视频里的脚本自己重出一版,改旁白只需要动 tts_gen.cjs 的 SCENES。

三、创作步骤

3.0 先别急着让 AI 写代码

这一步最容易被跳过,也最不该跳。我先让 AI 读教案,只输出结构判断,不许写代码。

提示词 0 · 拆教案,定动线

我给你一份《春》第 1 课时的 AI 赋能情境教学设计,四个任务:
任务一 春园拾珍·积词觅识(字词)
任务二 芳径吟春·诵悟声韵(重音与停连)
任务三 漫步春郊·纵览全景(结构、五幅春景图)
任务四 亭下品芳·细赏锦句(第 2 课时,本次不做)

请先不要写任何代码,只回答三件事:
1. 这份教案里的「游园」是包装还是骨架?把原文里描述空间移动的句子摘出来,
   说明它和认知顺序的对应关系。
2. 如果做成一个 3D 场景,任务点该怎么摆?给出一条从入口到终点的动线草图
   (文字版即可),并说明地形为什么要有高差。
3. 由此推出三条不能妥协的设计约束。

注意:我不要「创设沉浸情境」「激发学习兴趣」这类空话,每条结论都要能落到
具体的场景坐标或交互行为上。

拿到的是三条硬约束:场景必须是一条路走到底,不能是三个孤立房间;三个点地形要有变化(平地 → 竹径 → 山坡);任务下发必须是「走到才触发」,不能让老师点按钮发。最后一条最关键,否则「移步换景」就白做了。

顺手在纸上画了草图:牌坊入口 → 桃林 → 平地石桌(任务一)→ 弯竹径 → 六角亭遮挡转折(任务二)→ 石阶上行 → 高处观景台(任务三)。竹径必须弯,直路没有移步换景;终点必须高,平地上讲不出「纵览」。

3.1 先把协议定死(公共的一轮)

浏览器页面不能当 WebSocket 服务端,行空板也不能直接连浏览器,所以必须有第三方进程做中间人。两端对着合同写,联调才快。这一轮单独给 AI,先出 server/server.js。

提示词 1 · 中继服务与消息协议

用 Node 22 + ws 写一个单文件中继服务 server/server.js,要求:

端口:WebSocket 8801,HTTP 8800(提供 /health 和 /api/state 快照,
若 teacher/dist 存在则顺带静态托管)。启动时打印本机局域网地址。

消息全部是 JSON,用 type 区分。需要支持:
- hello:role 为 teacher 或 terminal;terminal 带 groupId(1~8) 和 name
- welcome:回 sessionId;**握手时若已有 currentTask,必须补发给终端**
- task:push:教师端下发任务,中继广播成 task 给所有在线终端
- answer:终端回传 taskId / qIndex / choice / correct / elapsed,
  中继转发给教师端并附一条最新 state
- state:全量状态快照(终端表 + 当前任务 + 最近日志)
- task:nudge / session:reset / reset / ping / pong

状态一律放中继内存,不要放浏览器:教师端刷新、终端断线重连都要能恢复。
终端表用 Map<groupId, term>,同组号重连直接覆盖,不能出现两条记录。
心跳 15 秒一次,30 秒判离线;同组号被顶替时,旧连接的 close 事件不能把
新终端误标离线(判断 terminals.get(gid) === t && t.ws === ws)。

只输出 server.js 和一份 markdown 协议表,不要写前端和终端代码。

这里有两个决定后面省了大麻烦:状态放中继,所以刷新页面不丢数据;握手补发当前任务,学生开机慢半分钟也不会错过下发。协议一旦反复改,JS 和 Python 两端都要跟着改,非常痛苦,宁可这一步多花半小时。

3.2 Web 端

轮次 W1:先跑通最小链路

提示词 2 · 教师端骨架

基于上一轮的 server.js,用 Vite + Three.js 写教师端,目录 teacher/:

文件拆开,不要一个大文件:
  terrain.js   地面/路径/池塘/远山,导出 terrainH(x,z) 高度函数
  flora.js     树、竹、草、花瓣
  structures.js 牌坊、亭、台、灯、路标
  scene.js     天空、光照、相机、主循环、外部模型加载
  ui.js / net.js / tasks.js / main.js / style.css

第一版只要能跑:一个方块当地面、一个三角当树、一座牌坊、三个任务点路标。
先把「点击路标 → 相机飞过去 → 到达后弹出任务卡 → 通过 net.js 连上中继」
这条链路跑通,视觉细节这一轮一律不要做。

技术要求:
- 所有物体的 y 坐标必须从 terrainH(x,z) 取,别各写各的,否则山坡上的树会悬空
- 主路径用 13 个控制点串 Catmull-Rom 曲线,按弧长均匀取点铺石板
- 相机不要用 WASD 第一人称自由行走。改成点击路标后沿样条飞行 2.35 秒到达,
  中途 y 加一个 sin(e*PI)*0.55 的轻微抬升,让人读成「走过去」而不是「坐滑轨」
- 留调试入口:?point=0/1/2 直接跳站,键盘 1/2/3 切换,R 重新下发,
  Esc 收起任务卡,window.__springClass 暴露 world/ui/net/goToPoint
- 初始机位退到 (0, 3.9, 27),看向 (0, 2.4, 12),别站在牌坊门洞里,
  否则匾额「文园寻芳」四个字根本看不见

入口实景:牌坊完整入画,右侧是终端在线与作答看板

注意:第一版我没有花、没有云、没有花瓣,但整条链路是通的。这个顺序很重要。先美化再联调,很容易陷在视觉细节里出不来。另外「别用坐标想象画面」,我第一版把机位设在 z=20,牌坊在 z=20.6,自以为正对匾额,截图一看人站在门洞里。改完立刻截图,一眼就知道对不对。

轮次 W2:移步换景与自动下发

提示词 3 · 到达触发

在最小链路上补三件事:
1. 三个任务点数据放 tasks.js,每点含 id/title/subtitle/poem/cam/marker/taskId,
   机位分别是平地石桌、弯竹径六角亭、高处观景台。
2. goToPoint(pid) 到达后才触发:world.labelsHidden = true → ui.showTask(task)
   → net.pushTask(task) → 任务卡里的「重新下发」「催办未答」按钮接上协议。
   不要做成老师手动点按钮下发。
3. 任务点上方是 Sprite 文字牌,走近 7.5 米内按 smoothstep 淡出;
   任务卡打开时整体隐藏,避免半透明大字压在卡片后面。

三个场景:平地、竹径、山坡高差本身就是「纵览」的隐喻

注意:pushedTasks 这个 Set 别省,用来记录本次会话已经下发过的任务,同一站重复点击只重新弹卡、不重复计分。

轮次 W3:题目数据与任务卡

提示词 4 · 题目与任务卡

把《春》第 1 课时教案的三组题目写成 tasks.js 的 TASKS 对象:
T1 三题(朗润/酝酿/卖弄),T2 两题(重音、停连),T3 三题
(五幅春景图、描写顺序、结尾三段)。
每题含 id / stem / options[4] / answer(下标) / explain,explain 一句话讲清。

任务卡 UI:左侧插画、任务序号与标题、intro、有序题目列表,
正确答案的选项加 .correct 高亮(给老师看,不下发给学生端)。

教师端任务卡:三道题与正确答案高亮

注意:任务卡第二版才修好。第一版 max-height: 66vh,三道题的长选项一撑,第三题跑到可视区外;Windows 的滚动条很细,老师根本不知道下面还有内容。改法是三处一起动:max-height 提到 76vh、内边距 22px 收到 18px、选项从「药丸自动换行」改成「一行一个 + text-overflow: ellipsis」,再把滚动条加粗到 7px 并给淡色轨道。滚得动,得让人看得见。

轮次 W4:作答看板与终端在线

提示词 5 · 实时反馈

右上角做「终端在线」8 个格子,右下角做「作答看板」:
每组一行,按当前任务的每题显示绿(对)/红(错)/灰(未答),带用时。
收到 answer 弹一条 toast,格式「第N组 第X题 ✔ 正确 · 3.1s」。

toast 必须节流:队列上限 4 条、超出删最早、存活 2100ms、
white-space: nowrap + 省略号。8 台同时作答时不能堆成瀑布盖住任务卡。
全部 8×题数 答完触发「全班闯关完成」提示。

作答看板:8 组在线,绿对红错,用时一目了然

作答看板特写

注意:这里踩过一次来回。8 台同时答题,一瞬间弹十几条 toast 把任务卡标题整个盖住;移到「左侧标题卡下方」后在宽屏又和卡片重叠,最后回到顶部居中才干净。实时反馈的默认行为在多人场景下一定会崩,这一步别偷懒。

轮次 W5(可选):视觉迭代

提示词 6 · 模型与贴图

程序化几何体打底之后,接入 8 个外部 GLB 模型(牌坊/桃树/垂柳/六角亭/
石桌/灯笼柱/竹丛/太湖石)。要求:
- 按目标高度归一化、贴地、随机转向;加载成功后隐藏对应的程序化对象
- 匾额文字单独按 GLB 包围盒挂 Sprite,不要挂在程序化牌坊上
  (否则牌坊一隐藏,四个字跟着消失,变成「无字牌坊」)
- 匾额贴图用固定画布尺寸 + 居中绘制,别用 measureText 算宽度
  (字体回退会测窄,首字「文」会被裁掉)
- 每张贴图、每个模型都要有兜底:加载失败自动退回程序化纯色,
  把整个 textures / models 目录删掉场景照样能跑

GLB 用 gltf-transform 减面:
  --compress quantize --simplify-ratio 0.05 --simplify-error 0.01
  --texture-compress webp --texture-size 512
(quantize 是 KHR_mesh_quantization,Three.js 原生支持,不用挂 wasm 解码器)

注意:8 个原始 GLB 共 231.9 MB,减面后 5.67 MB,省 98%。这一步不做,网页根本加载不出来。桃花那次也别用 IcosahedronGeometry 当花簇,不管调多小、调多密,看起来都是「粉色石头」;最后改成 canvas 画的五瓣小花贴图,600 片花瓣合成 1 个 InstancedMesh,1 次 draw call。

3.3 行空板端

轮次 M1:四屏骨架与网络线程

提示词 7 · 学生终端

用 Python + unihiker 给行空板 M10 写学生终端,目录 m10/:
main.py(四屏 UI)、relay.py(WebSocket 客户端)、config.json。

屏幕只有 240×320。算一笔账:一行 9 号字行高 17px,题干最多 5 行;
四个选项按钮各 33px + 7px 间距;题头 50px;底部提示。一格都不能浪费。

四个界面:等待页 / 答题页 / 即时反馈页 / 本关完成页。
流程:连上中继 → 等待 → 收到 task → 逐题作答 → 对错反馈(2.5 秒后自动继续)
→ 本关完成 → 等下一个任务。

技术要求:
- unihiker 每次改 GUI 都会重绘,不要做局部刷新。
  采用整屏切换:clear() → 重建全部元素,切页约 150ms,效果像翻页
- 网络必须单独线程:relay.py 只往 queue 里塞消息,main.py 主线程
  每 80ms 取一次。把 websocket.recv() 放主线程里,网络一卡触摸就没反应
- on_choice 用 busy 标志位防重复点击,从点击保持到下一题渲染完成
- config.json 的 relay_ws 支持候选地址数组 + 1 秒 TCP 预探测
- 答完回传 answer 消息(taskId/qIndex/choice/correct/elapsed)

行空板四屏:等待 / 答题 / 反馈 / 完成(1:1 预览页横向并联)

注意:没有真机也能调 UI。我做了 m10/preview.html,用浏览器按 1:1 把四屏画出来,坐标和 main.py 一一对应。完成页「答对题数」压在红印章上、反馈页把「发酵」拆成两行、答题页选项 B 被截断,都是在这个预览页里看出来的,不用反复烧录到板子上试。

轮次 M2:真机适配,两个必修的坑

提示词 8 · 按真机字体度量重做答题页

真机验证后按下面的实测数据重做答题页,不要照浏览器预览的 px 排版:

unihiker 的 font_size 单位是 point 不是 px。真机 tkinter 实测:
size 9 → 行高 17px / 每中文 13px;size 12 → 22px / 17px;
size 15 → 28px / 21px(每字宽约 1.4×size,行高约 1.85×size)。
之前按 px 排,13 字题干要 221px 塞进 198px 卡片,行距 17px 小于行高 22px,
结果题干顶边、选项硬切断、行间叠字。

重做要求:
- 题干/选项统一 9 号、标题 11、辅助 8~9;题干每行 13 字、行距 18px
- 选项用 wrap(label, 11) 折成两行完整显示,不要截成省略号
  (学生看不全选项,反馈页却给了全文,这不合理);超过 22 字才末行截断
- 右侧留 >20px 安全边距
- **不要用 gui.add_button(color=)**:底层是 ttk.Button,传 color 会抛
  TclError: unknown option "-color",四个选项在真机上全部不显示。
  改成 fill_round_rect + draw_text,并把 onclick 绑到整张卡片上
- 配色:宣纸米白 #F7F3E8 / 墨 #2A2B26 / 朱红 #B4483C

行空板答题页:题干、四个选项卡、朱红序号徽章

注意:add_button 那个坑特别阴。异常被 except 吞掉,print 又没 flush,日志里什么都看不见,只表现为「真机上四个选项不显示」。改完我用 XTest 模拟触摸点 A,中继日志出现「第1组 第1题 ✔ 正确」,才算真的通过。

轮次 M3:四屏统一

提示词 9 · 视觉语言统一

把等待页、反馈页、完成页全部改到答题页的视觉语言:

- 抽一个 paper(title) 通用方法:铺 quiz_bg.png 水墨横幅底 →
  4 字标题 + 副标题 + 朱红方章组号(round_box r=3,方章比胶囊像印章)
- 四页共用 paper(),答题页也改用它
- 插画贴图按 216×150 / 216×120 预烘圆角和描边,输出不透明 PNG。
  unihiker 的 GUI 没有图层 alpha,文字要压在图上,只能把暗区烘进图片本身
- 等待页状态文字放在插画卡下方,不要压画面
- 反馈页答案与解析用 9 号字、13 字一行,左右边距 28
- 同步更新 m10/preview.html 四屏,坐标与 main.py 一一对应

等待页
答题页
反馈页
完成页

注意:这一轮我当时只说了三个字「统一,改」,AI 就把四页全改了。能这么省事,是因为前面几轮已经把配色变量、坐标、paper() 的复用点都定死了。提示词越到后面越短,靠的是前面的结构没塌。

3.4 两个环境对起来

同一道题,两个环境:教师端任务卡与行空板答题页

提示词 10 · 模拟器与验收

写 tools/simulate.mjs,模拟第 2~8 组共 7 台终端:
连中继、收到 task 后随机延迟作答、按 --acc 控制正确率、--speed 控制节奏。
再写 tools/push_task.mjs,不开 3D 页面也能直接下发 T1/T2/T3 联调。

给我一份验收清单,覆盖:走到任务点自动弹卡、8 台同时收到、看板变绿/红、
用时正确、全部答完提示、断开一台变灰重连恢复、教师端刷新状态不丢、
重新下发不重复计分。

真机是第 1 组,模拟器占 2~8 组,看板正好 8/8。局域网实测下发到终端 < 50ms,终端切页约 150ms,8 台同时作答看板不卡。

联调注意事项(这部分花的时间比写代码多):

  • Windows 把行空板 USB 网卡识别成「公用网络」时会拦掉入站 TCP,表现是 ping 得通但 8801 超时。右键 tools/allow_firewall.bat 以管理员运行,或者干脆让板子和教师机连同一个 WiFi。我最后走的是 WiFi 直连,不用放行、不用隧道。
  • 板上 restart.sh 要用 pgrep -f 取 PID 后 kill -9。原来用 pkill -f 漏杀了 pyenv 进程,板上同时跑着 3 个 main.py,同组号互相顶替,日志一直刷 replaced by new connection。
  • 中继重启后,welcome 里没有 currentTask 时要让终端回等待页,别停在旧题上。
  • 每题都用 tools/push_stress.mjs 那种最长题干 + 最长选项压一遍排版,看有没有裁切重叠。

教师端与行空板同屏:右侧看板实时回收八组作答

四、作品改进

写完能上课,不代表写完了。下面几条是我留给自己下一版的,每条都标了改哪儿:

  1. 补第 2 课时「亭下品芳」。六角亭模型已经在场景里,tasks.js 加一组 T4、给 POINTS 加第四个点就行。当时只做三个,是因为不想四个都半吊子。
  2. 反馈页 2.5 秒太短。main.py 的 draw_feedback 后面挂的定时器改成可配置,或者在反馈页加一个「再看解析」的触摸区。现在解析一闪而过,错题学生来不及读。
  3. 按看板数据分层。state.terminals 里每组每题的对错和用时都在,server.js 里加一个按组下发不同 taskId 的分支,就能做「答得快的组接拓展题」。协议不用改,只加一个字段。
  4. 状态落盘。现在 state 在内存里,中继一重启全没。加一个每 30 秒写 session-<id>.json 的定时器,课后才能拿去做学情分析 —— 对七年级的分层教学,这份数据比课堂上的热闹有用。
  5. 朗读题别只做选择题。T2 现在考的是「重音该落在哪儿」,行空板有麦克风和音频库,可以录一句学生朗读传回教师端播放。这比选对 ABCD 更贴近教案里的「诵悟声韵」。
  6. 3D 资源再降一档。8 个 GLB 共 5.67 MB,教师机独显 60fps,集显 40~50fps。给低配机器加个 ?lite=1,关掉花瓣实例和部分贴图 repeat,课堂上更稳。
  7. 换课做成配置。现在换一篇课文要改 tasks.js 加换插画。下一步做一个 lesson.json,把动线、任务点、题目、插画路径全放进去,AI 读教案直接生成这个 JSON,程序和课文彻底解耦。

最后一条最实在:这套东西值钱的不是 3D 园林,是「走到才下发」这条规则。换一篇课文、换一块板子,规则还成立。


附:提示词复制清单

轮次 给谁 一句话 不能漏的约束
0 无代码 拆教案定动线 不许输出空话,结论要落到坐标或交互
1 中继 Node + ws 协议 状态放中继、握手补发任务、Map 覆盖同组号
W1 教师端 最小可跑版本 先链路后美化、terrainH 单一高度源、不做第一人称
W2 教师端 到达触发下发 走到才触发、Sprite 距离淡出、调试入口
W3 教师端 题目与任务卡 题目取自教案、76vh、选项单行省略号
W4 教师端 看板与 toast 队列上限 4 条、存活 2100ms
W5 教师端 模型与贴图 自动替换 + 程序化兜底、匾额单独挂字
M1 行空板 四屏骨架 240×320、整屏切换、网络单独线程、busy 防连点
M2 行空板 真机适配 font_size 是 point、禁用 add_button(color=)、长选项两行
M3 行空板 四屏统一 paper() 复用、贴图预烘暗角、同步预览页
10 联调 模拟器与验收 真机 1 组 + 模拟 2~8、防火墙、压力题排版

附:目录与运行

chun-3d-teaching/
├── server/     中继(node server/server.js)
├── teacher/    教师端(npm --prefix teacher run dev → :5173)
├── m10/        行空板(python3 main.py;preview.html 是四屏预览)
├── tools/      模拟器 / 下发 / 贴图与模型加工脚本
├── assets/     插画、贴图原稿、GLB
└── docs/       架构、部署、行空板配置、课堂手册、制作全记录

三步跑起来:起中继 → 起教师端 → node tools/simulate.mjs 模拟 2~8 组,然后在 3D 场景里点路标,或直接按 1 2 3。没有真机也能把整条链路看完整。

硬件清单

创作许可协议

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

所需材料/产品

加载全部

    行空板M10(UNIHIKER)Python教学主控板(含屏幕保护壳) - 蘑菇云科创教育

    行空板M10(UNIHIKER)Python教学主控板(含屏幕保护壳) - 蘑菇云科创教育

    行空板M10(UNIHIKER)Python教学主控板(含屏幕保护壳) - 蘑菇云科创教育

相关推荐

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

创作许可协议

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

相关推荐

所需材料/产品

行空板M10(UNIHIKER)Python教学主控板(含屏幕保护壳) - 蘑菇云科创教育

行空板M10(UNIHIKER)Python教学主控板(含屏幕保护壳) - 蘑菇云科创教育

行空板M10(UNIHIKER)Python教学主控板(含屏幕保护壳) - 蘑菇云科创教育