更新 AI03 课程内容,替换为 AICODE-03 和 AICODE-06 课程体系

- 删除旧的 AI03 课程文件(第4-5课)
- 新增 AICODE-03 课程体系(AI是怎么想的、AI训练师、我是大作家等)
- 新增 AICODE-06 课程体系(从扣子到代码、个人主页、涂鸦PK等)
This commit is contained in:
chengzi
2026-04-14 21:31:13 +08:00
parent 7487e8451a
commit e542f013b9
29 changed files with 13051 additions and 2999 deletions

View File

@@ -0,0 +1,87 @@
# AICODE-06 课程大纲
> AI编程创新课 · 06路线 · 春季学期前5课独立合流时间点待定
> 适用对象小学6年级起点有扣子/低代码经验
---
## 独立阶段代码编程衔接第1-5课
| 课时 | 课程主题 | 学习目标 | 知识点复习 | 知识点新知 |
|:----:|---------|---------|-----------|-----------|
| 1 | 从扣子到代码 | • 理解"扣子智能体"和"AI编程"的本质区别:配置 vs 创造<br>• 掌握 Trae IDE 界面布局和核心操作流程<br>• 理解 Vibe Coding用自然语言驱动AI写代码 | 扣子智能体搭建经验 | Trae IDE 操作、Builder/Chat 模式、Vibe Coding 概念 |
| 2 | SOLO模式初体验 | • 理解三种模式的区别Chat问问题、Builder从零创建、Solo Coder精准修改<br>• 能根据场景选择正确模式<br>• 掌握跟随模式实时观看AI编写代码 | Trae IDE 操作、Builder 模式 | Chat/Builder/Solo Coder 三种模式、跟随模式、模式选择策略 |
| 3 | Solo Coder 迭代深化 | • 掌握内置浏览器预览和元素选择功能<br>• 能用"点击元素→描述修改"的方式精准迭代<br>• 完成从基础版到完善版的多轮迭代 | 三种模式、跟随模式 | 内置浏览器预览、元素选择、点击式精准修改、多轮迭代策略 |
| 4 | 我的个人主页(上) | • 理解"内容先行"原则先想清楚展示什么再让AI做<br>• 掌握板块化设计思维:把页面拆成独立区域<br>• 能在 Trae 中创建自定义智能体并编写系统提示词 | 三种模式、元素选择、迭代 | 内容清单方法、板块化设计、Trae 自定义智能体、占位内容替换 |
| 5 | 我的个人主页(下) | • 能根据同学反馈有针对性地优化页面<br>• 能用自然语言让AI实现交互效果hover、动画、轮播<br>• 能用3分钟路演展示作品并讲解设计思路 | 自定义智能体、内容清单、Solo Coder 迭代 | 交互效果实现、设计一致性、发布前检查、产品路演 |
---
---
## 项目实战阶段魔幻俄罗斯方块第6-7课
> 面向已完成前5课的 AICODE-06 学员。以俄罗斯方块为载体系统训练工程师思维Plan Mode 先行、需求审核、自动测试、新窗口原则。
| 课时 | 课程主题 | 学习目标 | 核心概念 | 核心工具 |
|:----:|---------|---------|---------|---------|
| 6 | 魔幻俄罗斯方块(上)— Plan Mode 先行 | • 掌握 Plan Mode 三步流程:整理需求 → 需求审核 → 确认需求<br>• 理解需求质量 = 输出质量需求越详细AI 执行越准确<br>• 建立新窗口原则:审核必须在新窗口进行,避免上下文污染<br>• 能独立完成「需求文档 → 审核 → 生成 → 验收 → 结果溯源」完整闭环 | Plan Mode、需求文档、需求审核、结果溯源、新窗口原则、上下文污染 | Kimi 2.5 |
| 7 | 魔幻俄罗斯方块(下)— 魔改升级 + AI 自动测试 | • 掌握增量需求文档:在已有基础上只写新增功能<br>• 理解自动化测试:让 AI 生成测试脚本替代手动验收<br>• 能读懂测试脚本 ✅❌ 结果并溯源修复<br>• 建立「测试通过才算完成」的质量意识,利用测试脚本安全做第二版、第三版 | 自动化测试、测试覆盖、边界条件、增量需求、新窗口原则 | Kimi 2.5 |
**两课核心工作流:**
```
Plan Mode新窗口A整理需求
需求审核新窗口BAI扮演审核工程师
执行生成新窗口CKimi生成代码
手动验收 → 感受手动测试的局限
AI生成测试脚本新窗口D自动测试
测试全部 ✅ → 有了安全网 → 放心做第二版、第三版
```
---
## 项目实战阶段涂鸦PK第8-11课
> 在工程流程Plan Mode + 测试)已内化的基础上,以「自绘角色对战游戏」为载体,训练数据驱动设计、增量需求迭代、和设计决策表达力。
| 课时 | 课程主题 | 学习目标 | 核心能力 | 核心产出 |
|:----:|---------|---------|---------|---------|
| 8 | 涂鸦PK— 画图工具 + 角色设计 | • 能用需求文档驱动生成自己的HTML5画图工具<br>• 能画出两帧角色Spritesheet帧1待机+帧2攻击<br>• 理解20分属性预算制能根据打法定位分配属性 | 拆解力、审美力 | 自制画图工具 + 角色Spritesheet128×64 PNG+ 角色属性JSON |
| 9 | 涂鸦PK— 基础对战系统 | • 能用需求文档描述战斗规则(公式/先手/特技让AI生成完整对战系统<br>• 理解边界情况的重要性:需求文档必须覆盖所有异常情况<br>• 能用AI在新窗口生成测试脚本验证伤害公式和胜负判定 | 拆解力、韧性力 | 可对战的PK系统有血条/四种行动/AI对手+ 测试脚本验证报告 |
| 10 | 涂鸦PK— 动画 + 音效 + 特技 | • 能用自然语言描述动画「感觉」让AI实现Phaser Tween动画<br>• 理解Web Audio API用代码合成音效零外部素材依赖<br>• 掌握增量需求写法:只写新增部分,不重写已验收功能 | 审美力、提问力 | 有完整动画+音效+特技特效的战斗体验版 |
| 11 | 涂鸦PK— 班级锦标赛 | • 理解数据驱动设计加JSON文件=加角色,不改代码<br>• 能用增量需求实现roles角色系统从文件夹读取所有角色<br>• 能用3分钟路演清晰表达设计决策定位+意图+复盘) | 表达力、共创力 | roles系统 + 班级角色锦标赛 + 设计决策路演 |
**四课核心工作流延伸自第6-7课工程流程**
```
需求驱动窗口A整理 → 窗口B审核 → 窗口C执行
测试验证窗口D生成测试脚本 → 验证核心逻辑)
增量迭代(只写新增需求 → 已验收功能不重写)
数据驱动扩展(加文件=加功能 → 代码与数据分离)
```
---
## 合流说明
> **合流时间点待定。** 原计划第5课合流但考虑到 AICODE-03 学生打字和表达能力的成长节奏,合流点可能后延。
>
> AICODE-06 前5课的衔接设计确保学生具备以下基础
>
> | AICODE-03 前期培养的能力 | AICODE-06 前5课对应的能力 |
> |:---|:---|
> | AI基本原理认知Token、幻觉等 | 扣子→代码的认知跃迁(配置 vs 创造) |
> | 提示词五要素模型 | 三种模式选择策略 + 自定义智能体系统提示词 |
> | AI编程工具操作 + 审查三问 | Trae IDE 深度操作(跟随模式、元素选择、内置浏览器) |
> | Bug描述三要素 + 迭代修复 | 多轮迭代 + 内容规划 + 交互效果 + 产品路演 |
>
> 合流后的课程内容详见 [AICODE-03课程大纲](../AICODE-03/AICODE-03课程大纲.md)。

View File

@@ -0,0 +1,644 @@
---
课时: 1
主题: 从扣子到代码
核心能力: [提问力, 拆解力]
核心工具: [Trae IDE]
时长: 90分钟
透明化层级: 结果层
适用路线: AICODE-06有扣子/低代码经验的学生)
---
### 1. 课程目标
**知识目标:**
- 理解"扣子智能体"和"AI编程"的本质区别:配置 vs 创造
- 掌握 Trae IDE 的界面布局和核心操作流程
- 理解"Vibe Coding"的含义用自然语言驱动AI写代码
**能力目标:**
- 能独立完成 Trae 的完整操作流程:新建项目 → AI对话 → 接受代码 → 预览结果
- 能使用 Trae 的 Builder 模式让AI生成一个完整的交互网页
- 能在 Trae 中对AI生成的代码进行基本的迭代修改
**情感目标:**
- 感受到"从配置到创造"的自由度跃迁
- 对 Trae 工具产生"这个东西好用"的正面第一印象
- 建立"我能用代码做出扣子做不到的东西"的信念
---
### 2. 核心概念与误概念预设
**核心概念认知层级:**
| 概念 | 六年级类比 | 认知层级 |
|------|----------|---------|
| Trae IDE = AI编程工作台 | 像一个超级画室:画布(编辑器)+颜料(代码)+AI助手都在里面 | 识别层 |
| Builder 模式 = AI创建整个项目 | 像跟助手说"帮我画一幅画",它直接画出来 | 识别层 |
| Chat 模式 = 跟AI聊代码问题 | 像问助手"这笔颜色对不对",它给你建议 | 识别层 |
| 扣子 = 配置型工具 | 像乐高说明书,按步骤拼,只能拼出预定造型 | 识别层 |
| AI编程 = 创造型工具 | 像拿到一堆零件+超强助手,想做什么都行 | 理解层 |
| Vibe Coding | 用自然语言告诉AI你要什么AI帮你写代码 | 理解层 |
**典型误概念表:**
| 编号 | 误概念 | 正确认知 | 激发策略 |
|------|--------|---------|---------|
| M1 | "Trae和扣子差不多换了个界面" | 本质不同扣子产出对话Trae产出可运行的程序 | 同一任务两个工具对比 |
| M2 | "写代码要背很多语法,很难" | Vibe Coding用自然语言你说中文AI写代码 | 当场演示一句话生成网页 |
| M3 | "这个界面好复杂,我学不会" | 90%的按钮你不需要碰只用3个区域就够了 | 界面引导时只聚焦核心区域 |
| M4 | "AI帮我写代码那我什么都不用学了" | 你是"产品经理"AI是"程序员",你要会提需求、会验收 | 展示模糊指令导致的糟糕结果 |
| M5 | "代码我完全看不懂也没关系" | 不需要写,但需要能看一眼大概知道在干嘛 | 后续课程深入,本课只点到为止 |
---
### 3. 教学准备
**工具与环境:**
- 每台电脑已预装 Trae IDE校区统一部署教师课前验证可正常启动
- Trae 已登录校区账号(学生无需注册)
- 网络环境正常Trae AI功能需联网
- 浏览器 Chrome用于预览生成的网页
**教学资源:**
- 教师准备1个用 Trae 做的交互网页成品(开课展示用)
- 教师准备1个预制的 index.html 文件AI翻车时的备用
- 教师准备3个经过验证的"保底提示词"(学生想不出来时用)
- 学生资源:无需提前准备
**教师备课体验任务:**
> 备课前,教师必须亲自完成以下操作:
> 1. 在 Trae IDE 中完整走一遍:打开 → 新建项目 → Builder 对话 → 接受代码 → 预览网页
> 2. 用 Trae 做一个"反应力测试游戏",记录全过程和遇到的问题
> 3. 故意给 Trae 一个模糊指令(如"帮我做个游戏"),观察生成质量,准备对比演示
> 4. 确认校区每台电脑的 Trae 可正常启动、AI功能可用
---
### 4. 教学流程
---
**第一幕:联系 (Connect) — 10分钟**
*本幕目标:用成品展示制造"哇"的冲击,激发学习动机*
**情景导入 (10分钟)**
**师:** 同学们,在正式上课之前,我先给你们看一个东西。
(教师打开浏览器,展示一个课前做好的交互网页——比如"反应力测试游戏"或"英语单词闯关"
**师:** 来,谁来玩一下?
邀请1-2位学生上台操作全班观看
**师:** 好玩吧?这个小游戏有按钮、有计时、有动画效果。你们觉得,做这个东西难吗?
**生:** (预期:难吧/不知道)
**师:** 告诉你们一个秘密——做这个东西,我**一行代码都没写**。我只是用中文跟AI说了我想要什么AI就帮我生成了全部代码。整个过程不到5分钟。
(停顿)
**师:** 你们之前用扣子做过智能体对吧?扣子很好用,但它只能做"对话型"的东西——你问它答。而刚才这个游戏,有界面、能点击、有动画——这个在扣子里做得到吗?【诊断点:检测学生对扣子能力边界的认知】【识别层】
**生:** (预期:做不到)
**师:** 做不到。这就是我们今天要解锁的新工具——**Trae**。
**师:** 我跟你们说一下这学期我们要干什么。给你们剧透一下:
- **第3课**,你们每个人会做出一个完整的网页作品,可以分享给同学
- **第8课**,你们会做出真正的游戏——有关卡、有计分、有特效的那种
- **期末**,我们会开一场**发布会**——你上台展示你的作品,像真正的产品经理一样
这些软件全是你自己设计的,你想怎么设计就怎么设计。做完之后可以给你的爸爸妈妈用、给同学用、给老师用。
**师:** 你们只要跟着老师好好学我会把你变成一个编程大师。你不需要记住任何代码你只要能把你想要的东西说清楚——你想做什么、长什么样、怎么用——AI就帮你实现。**能说清楚 = 能做出来。** 这就是我们这门课的超能力。
**师:** 今天是第一步——你们每个人都会用 Trae 做出一个自己的交互网页。我们开始。
---
**第二幕:建构 (Construct) — 65分钟**
---
**【分段一Trae 是什么 + 界面快速导览】(10分钟)**
*本段重点:最快速度认识 Trae 界面,消除恐惧感,把更多时间留给动手创作*
**讲解与演示 (5分钟):**
**预设误概念:**
- M3: 界面好复杂我学不会
- M1: 跟扣子差不多
**师:** 大家打开桌面上的 Trae。
(等待全班打开,教师投屏同步展示)
**师:** 第一眼看到这个界面,你可能觉得"好复杂"。别担心——90%的按钮你都不需要碰。今天只需要认识**三个地方**
(教师投屏,快速逐一指出)
**师:** 跟我一起看:
**① 左边:文件管理器**
**师:** 左边这个面板就是你的"文件夹"——你做的网页文件会出现在这里。
**② 中间:代码编辑器**
**师:** 中间最大的区域AI帮你写的代码会显示在这里。五颜六色的文字不是乱码是正常的。现在不需要看懂。
**③ 右边AI对话面板最重要**
**师:** 这是今天最重要的地方——**AI对话面板**。点左边的AI图标打开它。
教师点开AI面板
**师:** 这里有两个模式,很关键:
- **Builder模式**让AI帮你创建整个项目。像跟装修公司说"帮我装修整个房间"。
- **Chat模式**跟AI聊天、问问题。像问"这面墙颜色好不好看"。
**师:** 今天主要用 **Builder模式**——我们要从零开始让AI帮我们写整个网页。
**师:** 还有一个关键操作——AI写完代码后你会看到绿色的 **Accept接受** 按钮。点它就行,就像验收装修成果,满意就签字。
**学生实践 (3分钟):**
**师:** 现在跟我做三步,确认你找到了这些地方:
1. **点左边的AI图标** ——看到AI对话面板了吗
2. **在AI面板上方找到 Builder 和 Chat 两个标签**——点 Builder确认切换到了 Builder 模式
3. **再点左边的文件图标** ——看到文件管理器了吗?
教师走动检查重点关注找不到AI图标或分不清Builder/Chat的学生
**师:** 有人找不到的举手,我来帮你。
**进度同步 (2分钟):**
**师:** Builder模式打开的同学举手好。记住**最重要的是右边的AI面板用Builder模式**。其他的先不管。【诊断点:检测学生是否记住了核心区域】【识别层】
---
**【分段二:新建项目 + 第一次AI对话】(15分钟)**
*本段重点:走完"新建项目 → AI生成代码 → 预览网页"的完整流程*
**讲解与演示 (5分钟):**
**预设误概念:**
- M2: 写代码很难
**师:** 好,在我们动手之前,先搞清楚一个非常重要的概念——**"项目"**。
**师:** 你们平时在电脑上用文件夹存东西对吧?一个文件夹里放几张图片、几个文档。你也经常用浏览器上网,打开各种网页。那 Trae 里的"项目"和这些有什么区别?
(停顿,让学生思考)
**师:** 这样理解——文件夹就是一个抽屉,你把东西往里面一放就行。浏览器是一个窗户,你通过它看别人做的东西。但**项目**不一样,项目是一个**工作间**——你在里面创造东西。
**师:** 打个比方你做手工的时候需要一张工作台把材料、工具都放在台上然后在这上面做出你的作品。Trae 的"项目"就是这张工作台。你不在里面开一个项目AI就不知道把代码往哪里放你也没法预览你做的东西。
**师:** 所以记住:**每次做新东西,第一步永远是开一个新项目。** 不开项目就跟AI说话就像站在空地上跟装修工人说"帮我装修"——装修哪里?没有房子你装修什么?
**师:** 好,现在我们来走一遍完整的流程。从零开始,做出一个网页。跟我一步一步来。
**第一步:新建项目文件夹**
**师:** 在 Trae 里,我们需要先给项目建一个"工作台"——就是在桌面上新建一个文件夹,然后让 Trae 打开它。
操作步骤:
1. 点击菜单 **文件 → 打开文件夹**(或者用快捷键 Ctrl+K Ctrl+O
2. 在弹出的窗口里,导航到**桌面**
3. 点右键 → **新建文件夹**,命名为 `我的第一个网页`
4. 选中这个文件夹,点**确认/打开**
**师:** 看左边的文件管理器——现在应该显示了你的文件夹名字,里面还是空的。这就是你的"工作台"准备好了等着AI往里面放东西。
(教师投屏同步演示每一步)
**第二步:在 Builder 里跟AI说话**
**师:** 现在切换到AI面板的 **Builder 模式**。在对话框里输入你想做的东西。
(教师演示输入)
**师:** 我先来示范。我输入这段话:
```
帮我创建一个 index.html 网页文件,内容是一个自我介绍页面:
- 大标题显示"你好,我是穹狼老师"
- 下面有一段自我介绍文字
- 背景用好看的渐变色
- 要有一个按钮,点击后弹出一句鼓励的话
```
发送后等待AI响应
**师:**AI回复了。它帮我写了一段代码还告诉我它做了什么。现在关键来了——
**第三步:接受代码**
**师:** 看到代码区域上方的按钮了吗?有一个绿色的 **Accept All全部接受**。点它——这就是告诉AI"好的,我接受你写的代码"。
点击Accept文件出现在左侧文件管理器中
**师:** 看左边——`index.html` 文件出现了!
**第四步:预览网页**
**师:** 最后一步——在文件管理器里右键点击 `index.html`,选择 **"在默认浏览器中打开"**。或者你可以直接去桌面的文件夹里双击这个文件。
(浏览器打开,显示生成的网页)
**师:** 搞定从打开Trae到看到网页四步**建文件夹 → Builder说话 → 接受代码 → 浏览器预览**。
**学生实践 (8分钟):**
**师:** 现在轮到你们了。按照刚才的四个步骤,做你自己的第一个网页。
**你的任务:** 在 Builder 里输入下面这段话(把名字换成你自己的):
```
帮我创建一个 index.html 网页文件,内容是一个自我介绍页面:
- 大标题显示"你好,我是[你的名字]"
- 下面写几句你想说的话
- 背景用你喜欢的颜色
- 加一个按钮,点击后显示一句你喜欢的话
```
**师:** 做完四步建文件夹→Builder说话→接受代码→浏览器预览的同学举手让我知道。
(教师走动协助)
(教师走动观察,重点关注:)
- 有没有学生卡在"新建文件夹"步骤
- 有没有学生没切换到 Builder 模式用了Chat模式
- 有没有学生不知道点 Accept
- 有没有学生找不到怎么预览文件
- AI是否成功生成了代码网络问题
**Plan B** 如果某个学生的AI没有正确生成代码网络超时/报错),教师直接帮忙在文件夹中创建 index.html粘贴预制代码先保证学生看到结果不掉队。
**进度同步 (2分钟):**
**师:** 看到自己网页的同学举手!
统计人数目标至少80%的学生成功)
**师:** 你们刚才完成了一件事——**没写一行代码只用中文跟AI说了你想要什么就得到了一个能在浏览器里打开的网页。** 这就是 Vibe Coding。
**师:** 跟扣子比比——在扣子里你能做出一个"有背景色、有按钮、能点击弹出消息"的网页吗?
**生:** (预期:不能)
**师:** 这就是区别。扣子做对话Trae做产品。而我们才刚刚开始。
---
**【分段三:做一个交互式网页游戏】(25分钟)**
*本段重点:用 Trae 完成一个真正有交互、有视觉效果的作品——这是本课的核心"哇"时刻*
**讲解与演示 (5分钟):**
**预设误概念:**
- M4: AI帮我写了就行我什么都不用管
**师:** 刚才的自我介绍页面是"入门级"。现在来点真正酷的——我们做一个**交互式网页游戏**。
**师:** 先看我怎么做。注意看我给AI的指令有多具体
(教师在 Builder 中输入)
```
帮我重新创建 index.html这次做一个反应力测试小游戏
1. 页面中央有一个大圆形区域,初始是红色,上面写"等待..."
2. 随机等待2-5秒后圆形变成绿色文字变成"点我!"
3. 玩家点击绿色圆形后,显示反应时间(精确到毫秒)
4. 如果在变绿之前就点了,显示"太早了!重新开始"
5. 下方有"再来一次"按钮
6. 页面整体深色主题,有渐变背景,圆形有阴影效果
7. 显示历史最佳成绩
```
等待AI生成
**师:** AI给我写好了。我先看看它的说明——它告诉我代码做了什么。没问题的话我点 **Accept All**
(接受代码,在浏览器中打开)
**师:** 来,谁来玩一下?
(邀请一个学生试玩,全班看效果)
**师:** 注意我刚才的提示词——我没说"帮我做个游戏"我说了7条具体要求。**越具体AI做出来的东西越接近你想要的。** 这就是你们之前学的提示词技巧,现在用在真正的编程上。
**师:** 但如果AI做的不完美怎么办很简单——**继续跟它说**。关键是你要说清楚**哪里不对、你要什么效果**。比如:
- 觉得字太小?→ 跟AI说"把标题文字调大一倍,圆形区域也调大"
- 颜色不好看?→ 跟AI说"背景换成深蓝色渐变,按钮换成橙色"
- 功能没做对?→ 跟AI说"点击按钮后没有反应,我要的是点击后显示一段文字"
**师:** 注意——不要只说"不好看"或者"有问题",要说清楚**哪里不好、你想要什么样的**。越具体AI改得越准。这就是**迭代**——一遍遍优化直到满意。
**学生实践 (18分钟):**
**师:** 现在轮到你们了!你要在 Trae 里做一个自己的小作品。
**三个方向选一个:**
| 方向 | 说明 |
|------|------|
| **小游戏** | 有规则、有输赢、能玩起来的游戏 |
| **小工具** | 解决一个小问题的实用工具 |
| **创意页面** | 好看的、有动画效果的展示页面 |
**师:** 选好方向后,按这个框架写提示词:
```
帮我创建一个 index.html 网页文件,做一个[你选的东西]
1. [最核心的功能是什么]
2. [用户怎么操作——点什么、输入什么]
3. [操作之后会发生什么]
4. [界面风格——什么颜色、什么感觉]
```
**师:** 比如你选"小游戏"方向,你可以写:"做一个猜数字游戏1. 电脑随机一个1-100的数字2. 玩家输入数字点猜按钮3. 显示猜大了还是猜小了猜对了显示恭喜4. 界面用蓝色主题。"
**师:** 操作流程跟刚才一样:**Builder说话 → Accept代码 → 浏览器预览**。
**关键提醒:**
1. 提示词写到4条以上——越具体AI做出来的东西越接近你想要的
2. 做完后如果不满意继续在Builder里跟AI说要改什么
3. 做完的同学可以叫旁边的同学来试用你的作品
**师:** 时间18分钟。开始
(教师走动观察和协助)
(教师走动观察,重点关注:)
- 提示词质量:是否足够具体?太模糊的学生引导他们加细节
- AI生成结果是否能正常运行不能的话帮助学生跟AI说"这里有问题"
- 迭代意识:做完第一版的学生是否在继续优化?
- 情绪状态有没有学生因为AI翻车感到挫败
**快学生应对:** 做完基础版后试着跟AI说"加一个XX新功能"——比如加排行榜、加音效、加难度选择。体验"迭代"的感觉。
**慢学生应对:** 如果10分钟还没有产出教师提供保底提示词
```
帮我做一个猜数字游戏:
1. 电脑随机一个1-100的数字
2. 玩家输入猜的数字,点"猜"按钮
3. 如果猜大了显示"太大了",猜小了显示"太小了"
4. 猜对了显示"恭喜你用了X次猜对了"
5. 有"重新开始"按钮
6. 界面好看,有颜色
```
**进度同步 (2分钟):**
**师:** 时间到!做出作品的同学举手!
(统计)
**师:** 你们用不到20分钟做出了一个扣子里根本做不出来的东西。你现在回忆一下整个过程——你写代码了吗
**生:** (预期:没有)
**师:** 你做了什么?
**生:** 预期跟AI说了我想要什么
**师:** 对!你做的是**想清楚要什么 → 告诉AI → 验收结果 → 不满意就迭代**。这就是 Vibe Coding 的核心循环。你是"产品经理"AI是"程序员"。
---
**【分段四:回顾操作 + 掀开盖子看一眼】(10分钟)**
*本段重点:巩固操作流程,对代码建立初步感知*
**讲解与演示 (5分钟):**
**师:** 我们来回顾一下,今天你学会了 Trae 的核心操作流程:
```
① 新建项目文件夹(文件 → 打开文件夹)
② 在 Builder 模式跟AI说你要什么
③ 点 Accept 接受AI写的代码
④ 在浏览器中预览效果
⑤ 不满意?回到 Builder 继续说要改什么(迭代)
```
**师:** 这五步就是你以后每节课都会用到的基本流程。记住了吗?
**师:** 最后我们"掀开盖子看一眼"。点开你的 index.html 文件——这个文件就是你作品的**源代码**。什么是源代码?就是你做的这个网页的"配方"。浏览器拿到这个配方,就能做出你看到的页面。
**师:** 看看这些五颜六色的文字——这就是代码。你不需要看懂它。但我教你们一个以后永远用得上的方法——**不懂就问AI**。
(教师演示)
**师:** 比如我在代码里看到一个 `<style>` 标签,我不知道它是什么意思。怎么办?我不去百度,不去翻书——我直接复制这个标签,切换到 **Chat 模式**粘贴进去问AI
教师在Chat里输入
```
<style> 这个标签是什么意思?用一句话解释。
```
展示AI的回答
**师:** 看到了吗AI一句话就告诉你了。你在代码里看到任何不懂的东西都可以这样做——复制、粘贴、问AI。**万事不懂问AI**,这是你们以后最重要的习惯。
**学生实践 (5分钟):**
**师:** 两个小任务,做完一个做下一个:
**任务一问AI一个问题**
在代码里随便找一个你不认识的东西复制它切换到Chat模式问AI它是什么意思。
**任务二:手动改一个颜色**
**师:** 接下来做一件更酷的事——你亲手改一下代码。
1. 在代码编辑器里按 **Ctrl+F**(搜索)
2. 输入 `color`,看看代码里有几个跟颜色相关的地方
3. 找到一个颜色值(比如 `#3498db``blue``red` 这样的词)
4. 把它改成你喜欢的颜色(比如改成 `pink``gold``#ff6600`
5.**Ctrl+S** 保存,然后去浏览器**刷新**页面
**师:** 看到变化了吗?你刚才做了一件事——**手动改了一行代码**。这证明代码不是黑盒子你是可以碰它的。以后AI写的代码不满意你甚至可以自己动手微调。
(教师走动协助,帮助找不到颜色值的学生)
**进度同步 (2分钟 - 从分段一省出的时间):**
**师:** 谁成功改了颜色?改成了什么颜色?
让1-2个学生分享
**师:** 记住两个方法——代码看不懂?**问AI**。代码想微调?**Ctrl+F搜索手动改Ctrl+S保存刷新看效果**。【诊断点:检测学生是否掌握了"问AI"和"手动微调"两个习惯】【识别层】
---
**第三幕:反思 (Contemplate) — 10分钟**
*本幕目标:展示成果,巩固 Trae 操作流程和核心认知*
**成果展示 (6分钟)**
**师:** 请3位同学来前面展示你的作品。展示时说两件事
1. 你做了什么,简单演示一下
2. 你跟AI说了什么展示你的提示词
选3位学生展示每人2分钟。尽量选不同方向——游戏、工具、创意页面各一个
> **拍照发群:** 学生展示时教师用手机快速拍1-2张学生作品屏幕画面。课后选最佳的配上文字"第一节课同学们用AI做出了自己的第一个网页作品"发到家长群。这是家长感知课程价值的关键触点——第一课就有产出,比任何宣传都有说服力。
**互评与讨论 (4分钟)**
**师:** 看完了三位同学的作品。我们今天用了一个新工具——Trae做出了扣子里做不到的东西。
**师:** 最后一个问题:你觉得 Trae 跟扣子最大的区别是什么?一句话说。【诊断点:检测学生是否抓住了核心区别】【理解层】
收集3-4个回答
**师:** 总结得很好。用一句话说:**扣子让你做"对话"Trae让你做"产品"。**
---
**第四幕:延续 (Continue) — 5分钟**
*本幕目标:总结核心操作流程,制造下节课期待*
**抽象总结 (3分钟)**
**师:** 今天我们认识了 Trae 这个新工具,学会了核心操作流程:
(板书/投屏)
```
Trae 五步操作法:
① 打开文件夹 → ② Builder对话 → ③ Accept代码 → ④ 浏览器预览 → ⑤ 不满意就迭代
你的角色 = 产品经理(想清楚要什么、说清楚、验收)
AI的角色 = 程序员(根据你的描述写代码)
```
**师:** 今天你们已经从"扣子用户"升级成了"Trae用户"——从"配置者"变成了"创造者"。记住今天最重要的一件事:**你不需要会写代码,你只需要能说清楚你想要什么。** 能表达 = 能实现。这学期我们会做出越来越厉害的东西,而你的"编程能力"其实就是你的"表达能力"。
**5分钟挑战发布 + 下节预告 (2分钟)**
**师:** 本周课外小挑战:
> **5分钟AI挑战**
> 回家用 Trae 做一个"家庭小工具"——给你的家人用。
> 比如:家务分配转盘、做作业倒计时器、每日心情记录器。
> 拍个截图或录个屏,下节课分享。
>
> **评价标准:**
> - 基础:做出一个能运行的网页
> - 进阶:网页有交互功能(能点击、能输入)
> - 挑战:给家人实际用了,并收到他们的反馈
**师:** 下节课我们学"提示词工程进阶"——今天你们可能发现有时候AI做的不是你想要的。下节课我们就来解决这个问题让你成为更厉害的"产品经理"。
---
### 5. AI助教使用指南
**教师提示词模板(课前准备演示用):**
```
你是一个前端开发专家。请帮我创建一个 index.html 文件,包含一个简单的交互式网页游戏。
要求:
1. 所有代码在一个 index.html 文件中内联CSS和JS
2. 界面简洁美观,有渐变背景
3. 适合在浏览器中直接双击打开运行
4. 不使用任何外部库或CDN
5. [具体游戏描述]
```
**学生提示词模板(参考):**
```
帮我创建一个 index.html 文件,做一个[什么东西]
1. [功能1]
2. [功能2]
3. [功能3]
4. 界面要好看,用[你喜欢的风格]
```
---
### 6. 教师指南
**技术备注(教师备课知识补充):**
1. **Trae 的 Builder vs Chat 区别**Builder 模式可以直接创建/修改文件,代码变更需要点 Accept 确认Chat 模式只是聊天,不会直接改文件。本课统一用 Builder。
2. **Accept 机制**AI 在 Builder 中生成代码后,会高亮显示变更的部分(绿色=新增,红色=删除)。学生需要点"Accept All"或逐个文件Accept。如果不满意可以点"Reject"拒绝。
3. **文件预览方式**最简单的方式是在文件资源管理器Windows资源管理器不是Trae内的中找到 index.html 双击打开。也可以在Trae内右键文件选择"在浏览器中打开"。
4. **"一个文件"策略**:本阶段所有代码都放在单个 index.html 中内联CSS和JS避免多文件管理增加复杂度。
5. **网络依赖**Trae 的AI功能需要联网。如果课堂网络不稳定提前准备好离线备用的 index.html 文件。
**常见问题 FAQ**
| 学生问题 | 建议回答 |
|---------|---------|
| "Builder 和 Chat 到底有什么区别?" | Builder 是干活的——它会帮你创建和修改文件。Chat 是聊天的——你问它问题,它回答,但不直接改文件。今天我们用 Builder |
| "为什么AI做的和我说的不一样" | 因为描述还不够具体。下节课我们专门学怎么说得更准确。现在你可以继续跟AI说"这里不对我要的是XX" |
| "代码看不懂怎么办?" | 完全正常!你是产品经理,不需要看懂每一行代码。就像你去餐厅点菜,不需要知道厨师怎么炒的 |
| "Accept 了之后不满意怎么办?" | 在 Builder 里继续说你要改什么。AI会帮你修改。也可以用 Ctrl+Z 撤销 |
| "我能在家里用Trae吗" | 可以Trae 是免费的,你回家可以下载安装继续用 |
| "这个和ChatGPT有什么区别" | ChatGPT 帮你写文字、回答问题。Trae 帮你写可以运行的程序——有界面、能点击、能互动的那种 |
**备课体验任务清单:**
- [ ] 在 Trae 中完整走一遍:新建文件夹 → Builder对话 → Accept → 预览
- [ ] 做一个"反应力测试游戏"(课堂演示用)
- [ ] 准备3个保底提示词猜数字/倒计时器/个人介绍页)并验证可用
- [ ] 准备1个预制 index.htmlAI翻车时的备用
- [ ] 在校区每台电脑上确认 Trae 可正常启动、Builder可用
- [ ] 测试校区网络环境下 Trae AI 的响应速度如果超过30秒需要考虑备用方案
---
### 7. 5分钟日常AI挑战
**本周任务:**
> **"我的家庭小工具"**
>
> **操作步骤:**
> 1. 打开 Trae创建一个新文件夹叫"家庭小工具"
> 2. 在 Builder 里描述你想做的工具(倒计时器、分配转盘、心情日记等)
> 3. 点 Accept 接受代码,在浏览器中打开看效果
> 4. 不满意的地方跟AI说要改什么
> 5. 给家人看你的作品
>
> **评价标准:**
> - 基础:做出一个能在浏览器中打开的网页
> - 进阶:网页有交互功能(能点击、能输入、能显示结果)
> - 挑战:给家人实际使用并获得反馈,截图分享
---
### 8. 拓展任务
**进阶挑战(适合学有余力的学生):**
**挑战一:迭代升级**
> 回到你课堂上做的作品,在 Builder 里跟AI说"帮我加一个XX功能"。比如加排行榜、加音效、加深色/浅色模式切换。体验"迭代"的感觉。
**挑战二:换一个方向**
> 如果课上做的是游戏,试着做一个工具;如果做的是工具,试着做一个创意页面。用同样的 Trae 五步操作法,感受不同类型项目的提示词差异。
**挑战三:代码考古**
> 打开你的 index.html在 `<style>` 区域找到控制背景颜色的代码手动改成你喜欢的颜色Ctrl+S 保存后刷新浏览器看效果。恭喜你完成了一次"手动编程"。

View File

@@ -0,0 +1,646 @@
---
课时: 2
主题: SOLO模式初体验
核心能力: [提问力, 拆解力]
核心工具: [Trae IDE]
时长: 90分钟
透明化层级: 结果层
适用路线: AICODE-06有扣子/低代码经验的学生)
---
### 1. 课程目标
**知识目标:**
- 理解 Trae 三种模式的区别Chat问问题、Builder从零创建、Solo Coder在已有项目上迭代
- 理解"跟随模式"的含义:实时观看 AI 编写代码的过程
- 知道什么场景用什么模式:新项目用 Builder改项目用 Solo Coder问问题用 Chat
**能力目标:**
- 能根据任务场景选择正确的模式Chat / Builder / Solo Coder
- 能使用 Builder 模式从零创建一个有交互功能的网页应用
- 能切换到 Solo Coder 模式,开启跟随模式,观看 AI 实时工作并接受修改
**情感目标:**
- 感受到"跟随模式"带来的"AI在帮我干活"的实时反馈感
- 建立"不同工具解决不同问题"的工具选择意识
- 对自己做出的趣味应用产生成就感和分享欲
---
### 2. 核心概念与误概念预设
**核心概念认知层级:**
| 概念 | 学生类比 | 认知层级 |
|------|---------|---------|
| Chat 模式 = 问问题 | 像微信问朋友一个问题,朋友回答你,但不帮你动手 | 识别层 |
| Builder 模式 = 从零创建 | 像跟装修公司说"帮我从毛坯房开始装修" | 识别层 |
| Solo Coder 模式 = 在已有项目上改 | 像跟装修公司说"我家已经装修好了,帮我把客厅墙换个颜色" | 识别层 |
| 跟随模式 = 实时观看AI工作 | 像站在装修工人旁边看他施工,他刷一笔你看一笔 | 理解层 |
| 模式选择 = 根据场景选工具 | 像吃饭选餐具:喝汤用勺子,吃面用筷子,切牛排用刀叉 | 应用层 |
**典型误概念表:**
| 编号 | 误概念 | 正确认知 | 激发策略 |
|------|--------|---------|---------|
| M1 | "三种模式都差不多,随便用哪个" | 不同场景用不同模式,效果完全不同 | 演示用 Chat 让它改代码——它只回答不动手 |
| M2 | "Solo Coder 就是高级版 Builder" | Builder 从零开始Solo Coder 在已有代码上改。Builder 每次重写Solo Coder 精准修改 | 对比演示同一个修改需求Builder 重写 vs Solo Coder 精改 |
| M3 | "跟随模式没什么用,反正我看不懂代码" | 跟随模式让你实时看到AI在干嘛即使看不懂代码也能看到文件在变化、进度在推进 | 打开跟随模式体验,感受"AI在帮我干活"的实时感 |
| M4 | "用 emoji 做图标会很丑" | emoji 可以放很大、配 CSS 动画效果后非常炫 | 展示成品——大号 emoji + 发光/弹跳效果 |
| M5 | "做完基础版就算完成了" | 基础版只是起点,真正好的产品是一轮一轮迭代出来的 | 展示基础版 vs 迭代版的对比 |
---
### 3. 教学准备
**工具与环境:**
- 每台电脑已预装 Trae IDE已登录校区账号
- 网络环境正常
- 内置浏览器可用Trae 自带预览功能)
**教学资源:**
- 教师准备1个用 Solo Coder 做的"未来职业预测器"成品(课前演示用)
- 教师准备1个用 Builder 做的基础版(展示 Builder → Solo Coder 的切换过程)
- 教师准备2套保底提示词未来职业预测器 + 超能力抽签机各一套)
- 学生资源:上节课的 Trae 操作经验
**教师备课体验任务:**
> 备课前,教师必须亲自完成以下操作:
> 1. 用 Builder 创建"未来职业预测器"基础版,记录提示词和生成结果
> 2. 切换到 Solo Coder 模式,开启跟随模式,给它一个修改需求,观察实时工作过程
> 3. 同样流程做一遍"超能力抽签机",确保两个项目的保底提示词都能稳定产出
> 4. 故意用 Chat 模式让AI修改代码观察它"只回答不动手"的表现,准备对比演示
> 5. 测试大号 emoji + CSS 动画效果是否正常显示
---
### 4. 教学流程
---
**第一幕:联系 (Connect) — 10分钟** 🔗
*本幕目标:回顾 Builder 经验,引出"已有项目怎么改"的需求,制造模式选择的认知冲突*
**【环节】上节课回顾 (3分钟)**
**师:** 上节课我们学了 Trae 的基本操作。谁来帮我回忆一下,用 Trae 做一个网页分几步?
**生:** 预期建文件夹→Builder说话→Accept→预览
**师:** 对!五步操作法。上节课你们都用哪个模式做的?
**生:** 预期Builder
**师:** Builder 模式。它是"从零开始"帮你建一个项目。上节课的5分钟挑战有谁回家做了"家庭小工具"的?来展示一下。
选1-2位学生快速展示课外挑战作品30秒/人)
**【环节】情景导入 (7分钟)**
**师:** 上节课我们用 Builder 做了网页。但是我现在问你们一个问题——
(教师打开一个用 Builder 做的简单网页)
**师:** 比如这个网页,我觉得背景颜色不好看,想换一个。我有两个选择:
- **选择一:** 在 Builder 里重新说一遍"帮我做一个网页,但是背景换成蓝色"
- **选择二:** 有没有一种方式,我只说"把背景换成蓝色"AI就精准地帮我改这一个地方
你们觉得哪种更方便?【诊断点:探测学生对"迭代"和"重建"的区分意识】【识别层】
**【分支A】若学生说"选择二更方便"**
**师:** 对!每次都重新来一遍太浪费了。那你知道怎么做到"选择二"吗?
**【分支B】若学生说"用 Builder 继续说不就行了"**
**师:** Builder 可以继续对话,但有时候它会把整个文件重写一遍。有没有更精准的方式?
**师:** 今天我们就来解锁 Trae 的新能力。上节课我们只用了一种模式——Builder。但其实 Trae 有**三种模式**,就像你手机上有不同的 App干不同的事。今天学完你就知道什么时候该用哪个了。
**师:** 而且今天有一个超酷的功能——**跟随模式**。你可以**实时看到 AI 在帮你写代码**,像看直播一样。一会儿你们就知道有多爽了。
**师:** 今天你们每个人会做一个好玩的东西——你可以选"未来职业预测器"或者"超能力抽签机"。先用 Builder 搭出基础版,再用新模式来升级它。我们开始!
---
**第二幕:建构 (Construct) — 65分钟** 🛠️
---
**【分段一:认识 Trae 三种模式】(12分钟)**
*本段重点:建立三种模式的认知区分,知道各自的用途*
**预设误概念:**
- M1: 三种模式都差不多
- M2: Solo Coder 就是高级版 Builder
**讲解与演示 (7分钟):**
**师:** 上节课我们说过Trae 有 Builder 和 Chat 两种模式。其实还有第三种——**Solo Coder**。今天我们把三种模式搞清楚。
**师:** 我打个比方。你们都去过餐厅吧?
- **Chat 模式** = 问服务员问题。"这道菜辣不辣?""有没有推荐?"——服务员回答你,但不帮你做菜。
- **Builder 模式** = 跟厨师说"帮我做一桌菜"。厨师从头开始做,做完端上来。
- **Solo Coder 模式** = 菜已经端上来了,你说"这个汤太淡了加点盐"。厨师精准地帮你调整,不用重新做一桌。
(教师投屏,逐一打开三个模式,让学生看到界面)
**师:** 我们来做一个实验。我现在有一个上节课做的网页,我想把标题颜色改成红色。
(实验一:用 Chat 模式)
**师:** 先试 Chat 模式。我输入:"把标题颜色改成红色"。
(展示 Chat 的回应——它会告诉你怎么改,给你代码片段,但不会直接修改文件)
**师:** 看到了吗Chat 说了一堆,但文件有没有变?
**生:** (预期:没有)
**师:**Chat 只聊天,不动手。它是"顾问"——给建议但不干活。
(实验二:用 Solo Coder 模式)
**师:** 现在换 Solo Coder。同样的话"把标题颜色改成红色"。
(展示 Solo Coder——它直接修改文件标题颜色变了
**师:**文件直接被改了。Solo Coder 是"施工队"——你说哪里要改,它直接帮你改。
**师:** 那 Builder 呢Builder 也能改,但它更适合"从零开始建"。如果你的项目已经有了,用 Solo Coder 更精准。
(投屏总结表格)
| 模式 | 像什么 | 适合干什么 | 会不会改文件 |
|------|--------|-----------|-------------|
| Chat | 顾问 | 问问题、学知识 | ❌ 不改 |
| Builder | 建筑队 | 从零创建整个项目 | ✅ 创建新文件 |
| Solo Coder | 装修队 | 在已有项目上精准修改 | ✅ 修改已有文件 |
**师:** 记住这个表格。以后选模式就问自己:我是想**问问题**、**从零做**、还是**改已有的**
**学生实践 (3分钟):**
**师:** 快速测试!我说场景,你们告诉我用什么模式:
1. "我想做一个全新的游戏" —— 用什么?
2. "我想问 AI 这段代码是什么意思" —— 用什么?
3. "我的游戏背景颜色不好看,想换一个" —— 用什么?
(学生回答)
**进度同步 (2分钟):**
**师:** 刚才三个场景,答对两个以上的举手?【诊断点:检测学生是否建立了模式选择意识】【识别层】
**【分支A】若大部分学生答对**
**师:** 很好!记住这个判断方法。我们马上用 Builder 做一个好玩的东西,然后切 Solo Coder 来升级它。
**【分支B】若较多学生搞混**
**师:** 没关系,一会儿我们亲手用 Builder 和 Solo Coder 各做一遍,你就彻底分清了。
---
**【分段二Builder 创建基础版】(22分钟)**
*本段重点:用 Builder 模式创建项目基础版,建立"先做出来再说"的意识*
**预设误概念:**
- M4: 用 emoji 做图标会很丑
- M5: 做完基础版就算完成了
**讲解与演示 (5分钟):**
**师:** 好,现在我们用 Builder 做一个好玩的东西。今天你有**两个方向**可以选:
| 方向 | 说明 | 预览 |
|------|------|------|
| 🔮 **未来职业预测器** | 输入你的名字点按钮AI帮你"预测"未来职业 | 有动画、有大emoji、有搞笑描述 |
| ⚡ **超能力抽签机** | 点按钮抽签,随机获得一个超能力 | 有翻转动画、有超能力描述、有稀有度 |
**师:** 两个项目难度一样,结构一样——都是"点按钮→随机结果→好看的动画"。选你觉得更好玩的。
**师:** 我先做一个"未来职业预测器"给你们看。注意看我的提示词怎么写的:
(教师在 Builder 中输入)
```
帮我创建一个 index.html 文件,做一个"未来职业预测器"趣味网页:
1. 页面顶部有一个大标题"🔮 未来职业预测器",用大号 emoji 做图标
2. 中间有一个输入框,让用户输入自己的名字
3. 有一个"开始预测"按钮
4. 点击按钮后,显示一个随机职业结果,包括:
- 一个大号 emoji 代表这个职业(比如 👨‍🚀 太空探险家)
- 职业名称(大字)
- 一段搞笑的职业描述2-3句话
5. 至少准备 10 个不同的职业结果,每次随机显示一个
6. 结果出现时有一个好看的动画效果(比如淡入、缩放弹出)
7. 有"再测一次"按钮
8. 整体用好看的渐变色背景,圆角卡片,有阴影效果
9. 所有视觉效果用 emoji 和 CSS 实现,不要使用外部图片
```
等待AI生成
**师:** 注意我最后一条写了"用 emoji 和 CSS 实现,不要使用外部图片"——因为纯 CSS 做出来的效果最稳定emoji 放大后也很好看。
Accept 代码,在内置浏览器中预览)
**师:** 来,谁来试一下?
邀请1位学生试用——输入名字点预测看结果
**师:** 看到了吗?那个大 emoji 一弹出来,效果很好对吧?这就是 emoji + CSS 动画的威力。不需要图片也能很好看。
**学生实践 (15分钟):**
**师:** 现在你们来做。**选一个方向**——未来职业预测器或者超能力抽签机。
**步骤:**
1. 在桌面新建文件夹("未来职业预测器"或"超能力抽签机"
2. Trae 打开文件夹
3. 切到 **Builder 模式**
4. 输入提示词(可以参考我的,也可以自己写——**你的提示词写得越具体,出来的效果越好**
5. Accept 代码 → 预览效果
**如果你选超能力抽签机,参考这个提示词:**
```
帮我创建一个 index.html 文件,做一个"超能力抽签机"趣味网页:
1. 页面顶部有大标题"⚡ 超能力抽签机",用大号 emoji 图标
2. 中间有一个大按钮"抽取超能力!"
3. 点击按钮后,显示随机抽到的超能力,包括:
- 一个大号 emoji 代表这个超能力(比如 🔥 火焰掌控)
- 超能力名称(大字)
- 超能力等级(用星星表示:⭐⭐⭐⭐⭐)
- 一段有趣的超能力描述2-3句话
4. 至少准备 10 个不同的超能力,每次随机显示一个
5. 抽到结果时有一个炫酷的动画(比如卡片翻转、发光效果)
6. 有"再抽一次"按钮
7. 整体用深色炫酷风格,有光效和渐变
8. 所有视觉效果用 emoji 和 CSS 实现,不要使用外部图片
```
**师:** 15分钟。做完基础版后先让旁边同学试用一下。等会儿我们要用 Solo Coder 来升级它!
(教师走动观察)
(教师走动重点关注:)
- 是否切到了 Builder 模式(不是 Chat 或 Solo Coder
- 提示词是直接照抄的还是有自己的改动
- 生成结果是否正常运行
- 学生是否点了 Accept
- 预览是否成功
**快学生应对:** 基础版做完后,试着在 Builder 里继续说"加一个XX功能"——比如"加一个历史记录"、"加背景音效"。
**慢学生应对:** 如果10分钟还没有产出直接把上面的保底提示词给学生用。
**进度同步 (2分钟):**
**师:** 做出基础版并且在浏览器里看到效果的同学举手!
(统计)
**师:** 好!现在每个人手上都有一个"已经做好的项目"。下面问你们——如果我想给这个项目加一个新功能,或者改一下样式,我应该用什么模式?【诊断点:检测学生能否在实际场景中选择正确模式】【应用层】
**生:** 预期Solo Coder
**师:** 对!项目已经有了,要改就用 Solo Coder。我们现在就切过去而且——要开启一个超酷的功能。
---
**【分段三Solo Coder + 跟随模式初体验】(22分钟)**
*本段重点:切换到 Solo Coder 模式,开启跟随模式,体验实时观看 AI 工作的感觉*
**预设误概念:**
- M2: Solo Coder 就是高级版 Builder
- M3: 跟随模式没什么用
**讲解与演示 (7分钟):**
**师:** 现在我们解锁今天的重头戏——**Solo Coder 模式**。
**师:** 先找到它。看 AI 面板上方——除了 Chat 和 Builder还有一个 **Solo Coder**。点它切换过去。
(教师投屏演示切换到 Solo Coder
**师:** 进来了。看到界面的变化了吗Solo Coder 的界面和 Builder 不太一样——
**师:** 但最酷的功能是这个——**跟随模式**。看面板上有一个眼睛图标(或者"Follow"按钮),点一下。
(教师打开跟随模式)
**师:** 跟随模式打开了。现在我给它一个修改指令,你们注意看——
(教师在 Solo Coder 中输入)
```
给"开始预测"按钮加一个点击后的加载动画效果:
- 点击后按钮文字变成"正在预测中...",加一个旋转的 emoji ⏳
- 等待1.5秒后再显示结果
- 这样看起来更像真的在"计算"
```
(发送后,跟随模式实时展示 AI 修改代码的过程——文件自动跳转、代码实时变化)
**师:** 看到了吗?!这就是跟随模式——你可以**实时看到 AI 在修改你的代码**!它跳到哪个文件、改了哪一行,你都能看到。就像看直播一样。
(等 AI 完成,预览效果)
**师:** 好,改完了。我们在浏览器里刷新看看——
(展示加载动画效果)
**师:** 点"开始预测"——看!先是"正在预测中..."转了一会儿,然后才出结果。是不是比之前直接蹦出来更有感觉?
**师:** 这就是 Solo Coder 的威力——**精准修改**。我没有重新描述整个项目,只说了"给按钮加个动画",它就精准地改了那一个地方。如果用 Builder 呢?它可能把整个文件重写一遍。
**师:** 还有一个关键区别——Solo Coder 改完后也需要你 **Accept**。看到变更了吗?绿色是新加的代码,红色是删掉的。满意就点 Accept。
**学生实践 (12分钟):**
**师:** 现在你们来试!
**步骤:**
1. 切换到 **Solo Coder** 模式
2. 打开**跟随模式**(找到那个眼睛/Follow 按钮,点亮它)
3. 给你的项目提一个修改需求
**修改建议(选一个或自己想):**
如果你做的是**未来职业预测器**
- "加一个点击后的加载动画,显示'正在预测中...'等1.5秒再显示结果"
- "在结果卡片下面加一行小字:'预测准确率 99.9%(仅供娱乐)'"
- "把结果卡片的背景改成跟职业相关的颜色(太空探险家用深蓝,美食家用橙色)"
如果你做的是**超能力抽签机**
- "加一个抽签动画:点击后卡片先快速闪烁几个超能力,最后停在一个上面"
- "在超能力描述下面加一句'拥有率0.01% 的人拥有此超能力'"
- "加一个'收集图鉴'功能:记录你抽到过的所有超能力"
**师:** 记住两件事:
1. **开跟随模式**——看 AI 实时帮你改代码
2. **改完后点 Accept**——然后刷新浏览器看效果
**师:** 12分钟开始
(教师走动观察)
(教师走动重点关注:)
- 是否成功切换到 Solo Coder而不是还在 Builder 里)
- 是否打开了跟随模式
- AI 修改后是否点了 Accept
- 修改结果是否正常——如果 AI 改坏了,引导学生说"刚才的修改有问题,标题显示不出来了,帮我修一下"
- 跟随模式的体验反馈——学生看到实时变化时的反应
**Plan B** 如果某个学生的 Solo Coder 修改导致页面出错,教师引导学生在 Solo Coder 里描述问题:"页面打开后是空白的/按钮不见了/样式乱了,帮我修复"。这本身就是一次很好的"迭代修复"体验。
**进度同步 (3分钟):**
**师:** 成功用 Solo Coder 改了项目的举手!
**师:** 跟随模式感觉怎么样?看到 AI 实时改代码的时候,你什么感觉?【诊断点:检测学生对跟随模式的体验认知】【理解层】
收集2-3个回答
**【分支A】若学生说"很酷/很有意思"**
**师:** 对吧!跟随模式就是让你看到"AI正在帮你干活"。以后做复杂的修改,一定要开跟随模式——既能看进度,也能及时发现问题。
**【分支B】若学生说"代码看不懂"**
**师:** 完全正常!你不需要看懂每一行。跟随模式的重点不是让你读代码,而是让你**看到进度**——AI改到哪了、改了几个地方。就像你看装修工人干活你不用懂每个工序但你能看到"他在刷墙了"、"他在装灯了"。
**【分支C】若学生说"我的改坏了"**
**师:** 太好了!这正是我要说的——**翻车是正常的**。重要的是你怎么跟 AI 说"这里出问题了"。你试着在 Solo Coder 里告诉它"刚才的修改导致XX问题帮我修复"。
**师:** 好,我们来总结一下今天学的三种模式。(投屏)
| 你想做什么? | 用哪个模式? | 为什么? |
|-------------|------------|---------|
| 我有个问题想问AI | **Chat** | 它只回答,不改你的代码 |
| 我要从零做一个新项目 | **Builder** | 它会帮你创建整个项目 |
| 我的项目已经有了,想改一个地方 | **Solo Coder** | 它会精准修改,不重写整个文件 |
**师:** 以后每次用 Trae先问自己这个问题"我是要问问题、从零做、还是改已有的?"答案就告诉你该用哪个模式。
---
**第三幕:反思 (Contemplate) — 10分钟** 🤔
*本幕目标:展示作品,巩固三种模式的区分,强化跟随模式的体验*
**成果展示 (6分钟)**
**师:** 请3位同学来展示你的作品。展示时说三件事
1. 你选的是哪个方向——职业预测器还是超能力抽签机?
2. 简单演示一下你的作品
3. 你用 Solo Coder 加了什么功能?跟随模式的感觉怎么样?
选3位学生展示每人2分钟。尽量选两个方向各有代表
> **拍照发群:** 教师拍学生作品的精彩截图,配文字"第二节课,同学们做出了自己的趣味小应用——未来职业预测器和超能力抽签机!"发家长群。
**互评与讨论 (4分钟)**
**师:** 今天我们学了三种模式。最后做一个快速测试——
**师:** 如果你现在想把"预测器"变成"全班版"——每个同学的名字都预存进去,打开网页自动显示全班同学的名字列表。这个需求,你会用什么模式?为什么?【诊断点:检测学生的模式选择迁移能力】【应用层】
(收集回答)
**师:** 对——用 Solo Coder因为项目已经有了你只是想"加一个功能"。而且别忘了开跟随模式看AI怎么帮你加的。
---
**第四幕:延续 (Continue) — 5分钟** 🚀
*本幕目标:总结三种模式+跟随模式,预告下节课的元素选择功能*
**抽象总结 (3分钟)**
**师:** 今天我们学到的核心能力是什么?
(板书/投屏)
```
Trae 三种模式:
📋 Chat → 问问题(不改代码)
🏗️ Builder → 从零创建
🔧 Solo Coder → 精准修改已有项目
跟随模式:
👁️ 实时看AI工作 → 像看直播一样
```
**师:** 记住一句话:**做新的用 Builder改旧的用 Solo Coder问问题用 Chat。** 这就是工具选择的智慧——不是越高级越好,而是**最合适的就是最好的**。
**5分钟挑战发布 + 下节预告 (2分钟)**
**师:** 本周5分钟挑战
> **5分钟AI挑战**
> 回家用 Solo Coder 给你今天做的作品加**两个新功能**。
> 比如:加背景音乐、加倒计时、加排行榜、加分享按钮。
> 记住开跟随模式!截图你看到的 AI 实时工作画面。
>
> **评价标准:**
> - 基础:成功用 Solo Coder 加了1个新功能
> - 进阶加了2个新功能且效果正常
> - 挑战:给家人试用你的"预测器"或"抽签机",截图他们的反应
**师:** 下节课预告——Solo Coder 还有一个超级厉害的功能我今天没教你们。下节课你可以**直接点击网页上的某个元素**,然后告诉 AI "把这个按钮变大"或者"把这段文字的颜色改了"——不需要知道代码在哪里,直接点就行。想不想学?
**生:** (预期:想!)
**师:** 那下节课见!
---
### 5. AI助教使用指南
**教师演示用提示词(未来职业预测器):**
```
帮我创建一个 index.html 文件,做一个"未来职业预测器"趣味网页:
1. 页面顶部有一个大标题"🔮 未来职业预测器",用超大号 emoji 做图标
2. 中间有一个输入框placeholder "输入你的名字")和一个"开始预测"按钮
3. 点击按钮后,在下方用卡片展示随机职业结果:
- 一个超大号 emoji 代表这个职业
- 职业名称(大字加粗)
- 一段搞笑的职业描述2-3句话幽默风格
4. 预设至少 12 个职业,包括:太空探险家👨‍🚀、时间旅行导游⏰、美食评论家🍜、动物翻译官🐶、云朵设计师☁️、彩虹工程师🌈、恐龙考古学家🦕、海底城市建筑师🏗️、外星语言学家👽、魔法药水调配师🧪、梦境导演🎬、星球命名师⭐
5. 结果出现时有缩放弹出 + 淡入动画
6. 有"再测一次"按钮,点击后清空结果重新预测
7. 整体用渐变背景(紫色到深蓝),卡片白色圆角阴影
8. 所有视觉效果用 emoji 和 CSS 实现,不要使用任何外部图片或图片链接
9. 整个页面适合在浏览器中直接打开运行
```
**教师演示用提示词(超能力抽签机):**
```
帮我创建一个 index.html 文件,做一个"超能力抽签机"趣味网页:
1. 页面顶部有大标题"⚡ 超能力抽签机",深色炫酷风格
2. 中间有一个大按钮"抽取超能力!"(有发光效果)
3. 点击按钮后,用卡片展示随机超能力:
- 一个超大号 emoji 代表这个超能力
- 超能力名称(大字加粗发光)
- 稀有度等级(用 ⭐ 表示1-5星随机
- 一段有趣的超能力描述2-3句话
4. 预设至少 12 个超能力,包括:火焰掌控🔥、时间暂停⏱️、隐身术👻、读心术🧠、瞬间移动🌀、万有引力控制🪐、动物对话🦁、天气操控🌪️、超级记忆📚、金属操控⚙️、光速奔跑💨、植物生长🌿
5. 抽到结果时有卡片翻转 + 发光动画效果
6. 有"再抽一次"按钮
7. 整体深色主题(黑紫渐变),有霓虹灯光效
8. 所有视觉效果用 emoji 和 CSS 实现,不要使用任何外部图片或图片链接
9. 整个页面适合在浏览器中直接打开运行
```
**学生保底提示词(未来职业预测器简化版):**
```
帮我创建一个 index.html 文件,做一个"未来职业预测器"
1. 有一个输入框输入名字
2. 有一个"预测"按钮
3. 点按钮随机显示一个职业,用大号 emoji 做图标
4. 准备10个有趣的职业
5. 有动画效果,界面好看
6. 不要用外部图片,用 emoji 和 CSS
```
**学生保底提示词(超能力抽签机简化版):**
```
帮我创建一个 index.html 文件,做一个"超能力抽签机"
1. 有一个"抽签"按钮
2. 点按钮随机显示一个超能力,用大号 emoji 做图标
3. 显示超能力名称和有趣的描述
4. 准备10个超能力
5. 有动画效果,深色炫酷风格
6. 不要用外部图片,用 emoji 和 CSS
```
**Solo Coder 迭代用提示词示例:**
```
给"开始预测"按钮加一个加载动画:
点击后按钮显示"正在预测中... ⏳"1.5秒后再显示结果
```
```
在结果卡片下面加一行小字:"预测准确率 99.9%(仅供娱乐)"
```
---
### 6. 教师指南
**本课技术备注:**
1. **三种模式的底层区别**Chat 模式只在对话中回复不会修改文件系统中的文件。Builder 模式会创建新文件或完整重写文件。Solo Coder 模式会在现有文件基础上做增量修改diff改动更精准。
2. **跟随模式Follow Mode**开启后Trae 会自动跳转到 AI 正在编辑的文件和行号,让你实时看到变化。关闭后 AI 仍然在工作,只是你看不到实时过程。跟随模式不影响 AI 的工作质量,只影响你的观看体验。
3. **Accept/Reject 机制**Solo Coder 修改后同样需要 Accept。如果不满意可以 Reject 拒绝,代码会恢复到修改前的状态。
4. **emoji 渲染**emoji 在不同操作系统上显示样式略有不同Windows 的 emoji 和 Mac 的不一样),但功能一致。通过 CSS 的 `font-size` 可以将 emoji 放到任意大小,不会模糊。
5. **为什么不用外部图片**:纯 AI 编程模型不能生成图片文件用外部图片链接需要联网且链接可能失效。emoji + CSS 方案完全离线可用、永不失效,是当前阶段最稳定的视觉方案。
**常见问题 FAQ**
| 学生问题 | 建议回答 |
|---------|---------|
| "Solo Coder 和 Builder 到底什么区别?" | Builder 是造房子——从地基开始建。Solo Coder 是装修——房子已经有了,你只改你想改的地方 |
| "跟随模式在哪里打开?" | 进入 Solo Coder 后,在面板上找眼睛图标或"Follow"按钮,点一下就打开了 |
| "我用 Solo Coder 改了之后页面变空白了" | 没关系!先试试点 Reject 撤销修改。如果已经 Accept 了,在 Solo Coder 里说"页面变空白了,帮我修复回正常状态" |
| "我可以在 Solo Coder 里加全新的功能吗?" | 可以Solo Coder 不仅能改,也能加新功能。只要项目已经存在,用 Solo Coder 加功能比 Builder 更精准 |
| "emoji 好小怎么办?" | 在提示词里说"用超大号 emoji"AI 会用 CSS 把 emoji 放大。如果还不够大,跟 AI 说"emoji 再大一倍" |
| "我想用真的图片不用 emoji 行吗?" | 目前我们用 emoji 是最稳定的方案——不需要网络、不会失效。以后我们学到更高级的技术会用真实图片 |
| "三种模式可以随时切换吗?" | 可以!随时切换。但建议养成习惯:做新的用 Builder改旧的用 Solo Coder |
**课堂风险预案:**
- 如果 Solo Coder 不可用(版本问题):退回 Builder 模式做迭代Builder 也能对已有文件追加修改,只是不够精准
- 如果跟随模式打不开不影响核心教学Solo Coder 的核心功能是精准修改,跟随模式是锦上添花
- 如果学生进度差异过大:快学生挑战"用 Solo Coder 加3个新功能",慢学生确保 Builder 基础版能运行即可
**备课体验任务清单:**
- [ ] 用 Builder 做一个"未来职业预测器"完整版
- [ ] 切换到 Solo Coder + 跟随模式,做一次修改
- [ ] 用 Builder 做一个"超能力抽签机"完整版
- [ ] 切换到 Solo Coder + 跟随模式,做一次修改
- [ ] 用 Chat 模式试一次修改请求,确认它"只回答不动手"的表现
- [ ] 故意让 Solo Coder 改出一个 bug练习"描述问题让AI修复"的流程
- [ ] 确认校区每台电脑的 Trae 三种模式都可正常切换
---
### 7. 5分钟日常AI挑战
**本周挑战:**
> **"我的作品 2.0 版"**
>
> **操作步骤:**
> 1. 打开你今天做的"未来职业预测器"或"超能力抽签机"
> 2. 切换到 **Solo Coder** 模式,打开**跟随模式**
> 3. 给它加**两个新功能**(自选),比如:
> - 加背景音效
> - 加预测/抽签次数统计
> - 加"分享给朋友"的文字
> - 加夜间模式切换
> - 加更多职业/超能力选项
> 4. 截图跟随模式的实时工作画面 + 最终效果
>
> **评价标准:**
> - 基础:成功用 Solo Coder 加了1个新功能
> - 进阶加了2个新功能且效果正常
> - 挑战:给家人或朋友试用,截图他们的反应
---
### 8. 拓展任务
**拓展一(推荐):合体版**
> 把"未来职业预测器"和"超能力抽签机"合并成一个页面——先预测未来职业,再抽一个超能力,组合成"你的超级英雄档案"。用 Solo Coder 在已有项目上扩展。
**拓展二(挑战):自定义主题**
> 用同样的"随机结果"框架,做一个完全不同主题的抽签器——比如"今日幸运色"、"晚饭吃什么决定器"、"下课后干什么抽签"。从零用 Builder 创建,然后用 Solo Coder 打磨。

View File

@@ -0,0 +1,662 @@
---
课时: 3
主题: Solo Coder 迭代深化
核心能力: [提问力, 审美力]
核心工具: [Trae IDE]
时长: 90分钟
透明化层级: 结果层
适用路线: AICODE-06有扣子/低代码经验的学生)
---
### 1. 课程目标
**知识目标:**
- 理解 Trae 内置浏览器的作用:不用切换窗口,直接在 IDE 里预览网页效果
- 掌握元素选择功能的原理:点击网页上的元素 → 自动定位到代码 → 发送给 AI 修改
- 理解"迭代深化"的含义:每一轮修改都让作品更完善,而不是推倒重来
**能力目标:**
- 能使用内置浏览器预览网页,替代外部浏览器的手动刷新流程
- 能使用元素选择功能:点击 Select 按钮 → 点击页面元素 → 在 Solo Coder 中描述修改需求
- 能对上节课的作品进行至少 3 轮有效迭代,从"能用"升级到"好用又好看"
**情感目标:**
- 感受到"点哪改哪"带来的掌控感——不需要懂代码也能精准修改
- 建立"好作品是改出来的"迭代意识,不追求一次完美
- 对自己作品的持续进化感到自豪
---
### 2. 核心概念与误概念预设
**核心概念认知层级:**
| 概念 | 学生类比 | 认知层级 |
|------|---------|---------|
| 内置浏览器 = IDE 里的预览窗口 | 像画板旁边放了一面镜子,画一笔就能看到效果 | 识别层 |
| 元素选择 = 点哪改哪 | 像用手指着一幅画说"这朵花的颜色要改",画家立刻就知道你说的是哪里 | 识别层 |
| 迭代深化 = 一轮轮打磨 | 像雕塑家——先雕大形,再雕细节,再打磨光滑 | 理解层 |
| 功能迭代 vs 视觉迭代 | 功能迭代 = 加新能力(加按钮、加功能);视觉迭代 = 变好看(改颜色、加动画) | 理解层 |
| 迭代节奏:功能→体验→美化 | 先确保能用,再确保好用,最后确保好看 | 应用层 |
**典型误概念表:**
| 编号 | 误概念 | 正确认知 | 激发策略 |
|------|--------|---------|---------|
| M1 | "元素选择就是改颜色用的" | 元素选择可以用于任何修改——改文字、改大小、改功能、改布局,不只是颜色 | 演示用元素选择改功能逻辑,不只是改样式 |
| M2 | "改来改去会把网页改坏" | 每次 Accept 之前可以预览效果,不满意可以 Reject。改坏了也能用 Solo Coder 修复 | 故意改出一个问题,演示修复流程 |
| M3 | "迭代就是不停加新功能" | 迭代包括加功能、修问题、改体验、美化视觉,不只是加新东西 | 展示一个"功能很多但很丑"的反面案例 |
| M4 | "一次说很多修改AI能全做对" | 每次只说一个修改最稳定。一次说太多AI容易顾此失彼 | 演示一次说5个修改 vs 分5次各说1个的对比 |
| M5 | "内置浏览器和外面的浏览器一样" | 内置浏览器方便预览,但某些复杂效果可能显示不同。最终验收建议用外部浏览器 | 提一句即可,本课以内置浏览器为主 |
---
### 3. 教学准备
**工具与环境:**
- 每台电脑已预装 Trae IDE已登录校区账号
- 网络环境正常
- 学生上节课的项目文件夹仍在桌面("未来职业预测器"或"超能力抽签机"
**教学资源:**
- 教师准备1个"未来职业预测器"的基础版 → 迭代完善版的对比(展示迭代前后差异)
- 教师准备:元素选择功能的操作录屏或截图(备用,万一投屏看不清)
- 教师准备:一份"迭代清单"模板(打印或投屏)
- 学生资源:上节课完成的作品(未来职业预测器 or 超能力抽签机)
**教师备课体验任务:**
> 备课前,教师必须亲自完成以下操作:
> 1. 打开上节课做的"未来职业预测器"项目,使用内置浏览器预览
> 2. 使用元素选择功能:点 Select → 点页面上的标题 → 在 Solo Coder 中说"把这个标题改大一倍"
> 3. 连续进行 5 轮迭代,记录每轮改了什么、效果如何
> 4. 故意制造一个"改坏了"的情况(比如把布局搞乱),练习用 Solo Coder 修复
> 5. 测试"一次说一个修改" vs "一次说多个修改"的效果差异
---
### 4. 教学流程
---
**第一幕:联系 (Connect) — 10分钟** 🔗
*本幕目标:展示 L02 课外挑战成果,用基础版 vs 完善版的对比引出"迭代深化"的价值*
**【环节】上节课回顾 + 课外挑战展示 (4分钟)**
**师:** 上节课我们学了 Trae 的三种模式——谁来说说分别是哪三个、各自干什么?
**生:** 预期Chat 问问题、Builder 从零做、Solo Coder 改已有的)
**师:** 对!还学了一个酷功能——跟随模式,可以看 AI 实时工作。上周的5分钟挑战是"给作品加两个新功能",有谁做了的?
选1-2位学生快速展示课外挑战成果30秒/人)
**师:** 不错!你们已经在用 Solo Coder 迭代了。
**【环节】情景导入 (6分钟)**
**师:** 现在我给你们看两个版本的"未来职业预测器"。
(教师投屏展示两个版本——左边是 L02 的基础版,右边是经过多轮迭代的完善版)
**师:** 左边是我们上节课做出来的基础版。右边是我花了十几分钟迭代出来的版本。看看区别——
(逐一指出差异)
- 基础版:结果直接蹦出来 → 完善版:先有加载动画,再翻转显示
- 基础版:只有一个"再测一次"按钮 → 完善版:加了预测次数统计、历史记录
- 基础版:背景是简单渐变 → 完善版:有星空粒子动画效果
- 基础版:标题普通大小 → 完善版:标题有发光呼吸效果
**师:** 同样的项目,基础版和完善版差别大不大?
**生:** (预期:差很多!)
**师:** 差别很大。但是——我有没有重新做一个?
**生:** (预期:没有)
**师:** 没有!我就是在原来的基础上,**一轮一轮地改**。每次只改一个地方。改了十几轮,就变成这样了。这就叫**迭代深化**——好作品不是一次做出来的,是一轮轮改出来的。【诊断点:探测学生对"迭代"的理解深度】【理解层】
**师:** 今天我们就用 Solo Coder 的两个超强功能——**内置浏览器**和**元素选择**——来把你上节课的作品从"基础版"升级成"完善版"。
**师:** 上节课我预告过的——今天你可以**直接点网页上的某个东西**,告诉 AI "改这个"。不需要知道代码在哪里,直接指就行。我们开始!
---
**第二幕:建构 (Construct) — 65分钟** 🛠️
---
**【分段一:内置浏览器预览】(10分钟)**
*本段重点:学会用内置浏览器替代外部浏览器,建立更高效的预览习惯*
**预设误概念:**
- M5: 内置浏览器和外面的浏览器一样
**讲解与演示 (4分钟):**
**师:** 上节课你们预览网页的时候,是不是要切到外部浏览器,双击文件,或者刷新页面?
**生:** (预期:是的)
**师:** 每改一次就切出去刷新一次,累不累?
**生:** (预期:累/麻烦)
**师:** Trae 有一个内置浏览器——不需要切出去,直接在 Trae 里面就能预览。我来演示。
(教师操作)
**师:** 打开你上节课的项目文件夹。然后在 Trae 里找到 **内置浏览器预览** 的入口——
操作步骤:
1. 在文件管理器里找到 `index.html`
2. 右键点击 → 选择"在内置浏览器中预览"(或者使用命令面板搜索"Preview"
(教师展示内置浏览器面板出现在 IDE 右侧或下方)
**师:** 看到了吗?网页直接显示在 Trae 里面了以后改代码Accept 之后,这个预览会**自动刷新**。不需要手动切换、不需要手动刷新。
**师:** 从现在开始,我们的工作流变成:**Solo Coder 说修改 → Accept → 内置浏览器自动看效果**。全程不离开 Trae。
**学生实践 (4分钟):**
**师:** 现在你们来做:
1. 用 Trae 打开你上节课的项目文件夹(桌面上的"未来职业预测器"或"超能力抽签机"
2. 找到 `index.html`,右键 → 内置浏览器预览
3. 确认你在 Trae 里面看到了你的网页
**师:** 看到内置预览的同学举手。
(教师走动协助找不到内置浏览器入口的学生)
**进度同步 (2分钟):**
**师:** 打开了的举手?好。从现在开始,我们所有的预览都用内置浏览器。再也不用切出去了。【诊断点:确认学生能打开内置浏览器】【识别层】
**【分支A】若大部分学生打开了**
**师:** 很好!接下来学今天的重头戏——元素选择。
**【分支B】若有学生找不到**
**师:** 没关系,找不到的同学跟着我再做一遍。(快速重新演示入口位置)
---
**【分段二:元素选择——点哪改哪】(20分钟)**
*本段重点:学会使用元素选择功能,体验"不用看代码也能精准修改"的威力*
**预设误概念:**
- M1: 元素选择就是改颜色用的
- M4: 一次说很多修改AI能全做对
**讲解与演示 (7分钟):**
**师:** 上节课用 Solo Coder 的时候,你得自己描述"把标题改大"、"把按钮颜色换了"。但如果你不知道那个东西叫什么呢?比如你想改页面上的某段文字,但你不知道它在代码里叫什么名字。
**师:** 今天学的这个功能就解决了这个问题——**元素选择**。你直接**用鼠标点**网页上你想改的那个东西Trae 会自动把它发给 AI然后你只要说"改成什么样"就行。
**师:** 看我操作:
(教师投屏演示,步骤清晰放慢)
**第一步:打开元素选择**
**师:** 在内置浏览器的工具栏上,找到一个 **Select选择按钮**——通常是一个光标/箭头图标。点它。
(点击 Select 按钮,按钮高亮表示已激活)
**师:** 看到了吗?按钮亮了,说明"选择模式"已经打开。现在你的鼠标变成了"选择器"——你点网页上的任何东西,它都会被选中。
**第二步:点击你想修改的元素**
**师:** 比如我想改这个标题"🔮 未来职业预测器"的大小。我直接**点它**。
(鼠标点击标题文字,元素被高亮框选)
**师:** 看!标题被框选了,而且——注意看 Solo Coder 的输入框——它自动把这个元素的信息发过去了。
**第三步:描述你要怎么改**
**师:** 现在我在 Solo Coder 里输入我的修改需求:
```
把这个标题改大 50%,加一个发光效果,颜色改成金色
```
(发送,跟随模式展示 AI 实时修改)
**师:** 看内置浏览器——标题变大了、变金色了、还在发光!
**师:** 整个过程:**点 Select → 点元素 → 说怎么改 → Accept → 看效果**。你全程不需要知道代码在哪里、代码长什么样。你只需要指着它说"改这个"——就像你指着墙壁跟装修工人说"这面墙换个颜色"。
(再演示一次不同类型的修改)
**师:** 我再演示一个。这次我选中这个"开始预测"按钮——
(点 Select → 点击按钮)
```
把这个按钮改成圆形,加一个鼠标悬停时放大的效果
```
(展示修改后的效果——按钮变圆、悬停放大)
**师:** 两个关键原则:
**原则一:每次只改一个地方。** 不要一次说"改标题、改按钮、改背景、加动画"——AI 容易顾此失彼。一个一个来最稳。
**原则二:说清楚"改成什么样"。** 不要只说"改好看一点"——要说"改成金色发光"、"按钮变圆"、"字体加大50%"。越具体越好。
**学生实践 (10分钟):**
**师:** 现在你们来试!用元素选择改你的作品。
**操作步骤:**
1. 确认已经切到 **Solo Coder** 模式
2. 确认**内置浏览器**预览已打开
3. 点**内置浏览器工具栏的 Select 按钮**
4. **点击**你想改的元素
5. 在 Solo Coder 中**描述修改**
6. **Accept** → 看效果
**练习任务:用元素选择完成至少 2 次修改**
修改建议(每次选一个):
| 选中什么 | 说什么修改 |
|---------|-----------|
| 标题文字 | "把标题字体改大,加发光/阴影效果" |
| 按钮 | "把按钮改成圆角/圆形,加悬停动画" |
| 结果卡片 | "给卡片加一个边框发光效果" |
| 背景 | "把背景改成动态渐变/加星空粒子效果" |
| 任意文字 | "把这段文字改成你想要的内容" |
**师:** 记住——**每次只改一个**改完看效果再选下一个。10分钟内至少完成2次修改。
(教师走动观察)
(教师走动重点关注:)
- 是否找到了 Select 按钮
- 点击元素后是否正确发送到了 Solo Coder
- 修改描述是否够具体("改好看点" vs "加发光效果"
- 是否记得 Accept
- 是否只改一个(有学生可能贪心一次说很多)
**快学生应对:** 2次改完后继续改——挑战完成5次修改让作品焕然一新。
**慢学生应对:** 如果找不到 Select 按钮,教师手把手指一次。如果元素选择不生效,退回到直接在 Solo Coder 里文字描述修改需求。
**进度同步 (3分钟):**
**师:** 用元素选择成功修改了2次以上的举手
**师:** 你们觉得元素选择厉害在哪里?跟上节课直接在 Solo Coder 里打字描述相比,有什么不同?【诊断点:检测学生是否理解元素选择的优势】【理解层】
**【分支A】若学生说"不用知道代码叫什么"**
**师:** 对!这就是最大的优势。你不需要知道"标题"在代码里叫 `<h1>` 还是 `<div>`你直接指着它就行。AI 知道你指的是哪个。
**【分支B】若学生说"更快/更方便"**
**师:** 对!快在哪里呢?——你不用花时间描述"页面顶部的那个大标题",直接点一下就选中了。省了描述的时间。
**【分支C】若学生说"但我还是喜欢打字描述"**
**师:** 两种方式都可以!简单的修改用元素选择最快,复杂的逻辑修改(比如"加一个新功能")用文字描述更合适。工具越多,选择越灵活。
---
**【分段三:迭代深化实战——从"能用"到"好用又好看"】(26分钟)**
*本段重点:运用元素选择 + 文字描述,对作品进行系统性迭代,建立迭代节奏感*
**预设误概念:**
- M2: 改来改去会把网页改坏
- M3: 迭代就是不停加新功能
**讲解与演示 (6分钟):**
**师:** 刚才你们练习了元素选择。现在我们进入今天的核心——**迭代深化**。
**师:** 什么是迭代深化?打个比方——你画了一幅画的草稿,然后你一遍遍地改:第一遍描线条,第二遍上颜色,第三遍加细节,第四遍调光影。每一遍都让画更完善。做产品也一样。
**师:** 迭代有个节奏,不是乱改的。我推荐这个顺序:
(投屏)
```
迭代三步走:
🔧 第一步:修 Bug —— 有没有东西不能用、显示不对?先修好
🎯 第二步:加体验 —— 加载动画、操作反馈、过渡效果
🎨 第三步:美化视觉 —— 颜色、字体、间距、阴影、动画
```
**师:** 为什么这个顺序?因为——如果功能还有 bug你先去美化等 bug 修了界面可能又变了,白美化了。**先确保能用,再确保好用,最后确保好看。**
**师:** 我来演示一轮完整的迭代。(打开教师的基础版项目)
**第一步(修 Bug** 我发现点"再测一次"之后,旧的结果没有清除,新结果直接叠在下面了。
(用元素选择点击"再测一次"按钮)
```
点击"再测一次"按钮后,要先清除之前的预测结果,再重新开始。现在的问题是旧结果没有消失。
```
Accept → 验证修复)
**第二步(加体验):** 结果出来太突然,加个过渡。
```
预测结果显示时加一个从下方弹入的动画效果持续0.5秒
```
Accept → 看效果)
**第三步(美化):** 标题不够突出。
(用元素选择点击标题)
```
给这个标题加文字阴影发光效果,像霓虹灯一样
```
Accept → 看效果)
**师:** 三轮迭代,每轮只改一个点,但作品明显变好了。这就是迭代深化的威力。
**师:** 还有一个重要的事——**如果改坏了怎么办?**
(教师故意做一个会出问题的修改)
```
把整个页面的布局改成左右分栏
```
Accept → 页面布局混乱)
**师:** 看!改坏了。别慌——两个办法:
1. **Reject**:如果还没 Accept直接点 Reject 拒绝这次修改
2. **让 AI 修复**:如果已经 Accept 了,直接在 Solo Coder 里说——
```
刚才的布局修改有问题,页面乱了。请恢复成原来的单列居中布局。
```
Accept → 恢复正常)
**师:** 看到了吗?修回来了。所以不要怕改坏——**改坏了就让 AI 修**,这本身就是迭代的一部分。
**学生实践 (18分钟):**
**师:** 现在是今天最重要的环节——你要对你的作品进行**系统性迭代**。
**任务:按"修 Bug → 加体验 → 美化"的顺序,完成至少 3 轮迭代。**
(投屏迭代清单模板)
```
我的迭代清单:
🔧 修 Bug检查一遍你的作品有没有不对的地方
- [ ] 改动1__________
🎯 加体验(让操作过程更流畅、更有感觉)
- [ ] 改动2__________
🎨 美化(让视觉效果更好看)
- [ ] 改动3__________
⭐ 加分项(如果时间够,再改)
- [ ] 改动4__________
```
**师:** 先花1分钟检查你的作品想好你要改什么然后开始。
**修改灵感参考:**
| 类别 | 未来职业预测器 | 超能力抽签机 |
|------|-------------|-------------|
| 🔧 修 Bug | 重复预测没清空结果?按钮点了没反应? | 抽到重复超能力?动画没播完就显示结果? |
| 🎯 加体验 | 加预测次数统计、加"分享结果"文字、加加载音效描述 | 加"已收集X个超能力"计数、加稀有度排行、加连续抽签动画 |
| 🎨 美化 | 标题发光效果、卡片翻转动画、背景星空/粒子 | 霓虹灯边框、卡片发光脉冲、暗色主题加亮色点缀 |
| ⭐ 加分项 | 加"预测历史"列表 | 加"超能力图鉴"(记录所有抽到的) |
**师:** 记住:
- **用元素选择**改视觉类的东西(颜色、大小、样式)
- **用文字描述**加功能类的东西(计数器、新按钮、新逻辑)
- **每次只改一个**Accept 后看效果再改下一个
- 改坏了不要怕,**告诉 AI 修复**就行
**师:** 18分钟至少完成3轮迭代。开始
(教师走动观察)
(教师走动重点关注:)
- 学生是否按节奏迭代(先修 bug 再加功能再美化),还是一上来就美化
- 修改描述是否具体
- 是否每次只改一个(贪心一次改太多的要提醒)
- 有没有学生"改坏了"但不知道怎么修复——引导他们用 Solo Coder 描述问题
- 进度差异快学生可能已经迭代5-6轮慢学生可能才改了1-2处
**快学生应对:** 完成3轮基础迭代后挑战"加一个全新功能"——比如预测器加"输入生日影响预测结果"、抽签机加"超能力图鉴"。
**慢学生应对:** 如果学生不知道改什么,教师直接给一个具体指令:"用元素选择点你的标题,然后跟 AI 说'把标题加大,加一个发光效果'"。一轮成功后信心就来了。
**进度同步 (2分钟):**
**师:** 时间到迭代了3轮以上的举手5轮以上的呢
**师:** 现在回头看一下你的作品——跟上节课结束时的版本比,变化大不大?【诊断点:检测学生是否感知到迭代的价值】【理解层】
**生:** (预期:大/变好看了/功能多了)
**师:** 你今天重新做了一个新项目吗?
**生:** (预期:没有)
**师:** 没有!你只是在原来的基础上,一轮一轮地改。但结果却好了很多。**这就是迭代的力量——不推倒重来,每次改好一点点,累积起来就是质变。**
---
**第三幕:反思 (Contemplate) — 10分钟** 🤔
*本幕目标:展示迭代前后对比,巩固"元素选择+迭代深化"的核心认知*
**成果展示 (6分钟)**
**师:** 请3位同学来展示你的作品。展示时说三件事
1. 简单演示你的作品(现在的样子)
2. 你做了几轮迭代?印象最深的一次修改是什么?
3. 有没有"改坏了"的经历?你是怎么修复的?
选3位学生展示每人2分钟。尽量选一个改了很多轮效果很好的、一个改坏又修好的、一个做了创新功能的
> **拍照发群:** 拍学生作品迭代前后的对比截图,配文字"第三节课,同学们学会了'迭代深化'——同一个项目,一轮轮打磨,越改越好!"发家长群。
**互评与讨论 (4分钟)**
**师:** 看完了三位同学的作品。我们来想一个问题——
**师:** 你觉得今天学的"元素选择"和"直接在 Solo Coder 里打字",什么时候用哪个更好?【诊断点:检测学生能否根据场景选择合适的交互方式】【应用层】
(收集回答)
**【分支A】若学生能区分场景**
**师:** 总结得很好。简单改视觉——点一下最快。加新功能——打字说清楚。两种方式配合使用,效率最高。
**【分支B】若学生说不清楚**
**师:** 记住这个口诀:**看得见的点一下,想出来的打字说**。你能在页面上看到的东西(标题、按钮、卡片),用元素选择最快。你脑子里想的新功能(加计数器、加历史记录),用打字描述。
---
**第四幕:延续 (Continue) — 5分钟** 🚀
*本幕目标:总结迭代三步走,预告下节课的独立作品*
**抽象总结 (3分钟)**
**师:** 今天我们学到的核心能力:
(板书/投屏)
```
两个新工具:
🖥️ 内置浏览器 → 改完自动刷新,不用切窗口
👆 元素选择 → 点哪改哪,不用知道代码名字
迭代三步走:
🔧 修 Bug → 🎯 加体验 → 🎨 美化视觉
口诀:
"看得见的点一下,想出来的打字说"
"每次只改一个改坏了让AI修"
```
**师:** 今天最重要的一句话是:**好作品是改出来的,不是一次做出来的。** 这个道理不只适用于编程做PPT、写作文、画画都一样。先做出来再一轮轮打磨。
**5分钟挑战发布 + 下节预告 (2分钟)**
**师:** 本周5分钟挑战
> **5分钟AI挑战**
> 把你的"未来职业预测器"或"超能力抽签机"再迭代 **5 轮**,让它变成你觉得"可以拿给全班同学用"的水平。
> 用元素选择 + Solo Coder 配合使用。
>
> **截图清单至少交3张**
> 1. 迭代前的截图
> 2. 迭代过程中的截图(内置浏览器 + Solo Coder 对话)
> 3. 最终版本的截图
>
> **评价标准:**
> - 基础完成了5轮迭代有前后对比
> - 进阶:迭代包含修 Bug + 加体验 + 美化三个维度
> - 挑战给3个以上的同学/家人试用,收集他们的反馈
**师:** 下节课——**我的第一个Web作品**。这次你不是改旧项目了,而是**从零开始做一个全新的作品**。你来决定做什么、怎么做。你前三节课学的所有东西——Builder 从零建、Solo Coder 精准改、元素选择点哪改哪——全部用上。你将独立完成一个可以展示的完整作品。
**生:** (预期反应积极)
**师:** 提前想好你想做什么——游戏、工具、还是创意页面都行。想不到的话下节课我会给你灵感。下节课见!
---
### 5. AI助教使用指南
**教师演示用提示词(元素选择后的修改示例):**
```
把这个标题改大 50%,加一个金色发光文字阴影效果
```
```
把这个按钮改成圆形加一个鼠标悬停时缩放1.1倍的动画
```
```
给这个卡片加一个渐变色边框,从紫色到蓝色,有发光效果
```
**教师演示用提示词(功能迭代示例):**
```
给"再测一次"按钮加一个功能:每次预测后在下方显示"你已经预测了X次"的计数
```
```
加一个预测历史功能:
每次预测结果显示后把职业名称和emoji存到一个列表里。
在页面底部显示"预测历史"区域,列出所有预测过的职业。
```
```
加一个"分享结果"功能:
在预测结果下方显示一行文字:"🔮 [名字] 的未来职业是 [职业emoji] [职业名称]"
这行文字用特殊样式显示,方便截图分享。
```
**学生保底提示词(修 Bug 类):**
```
我的网页有个问题:点击"再测一次"后,旧的结果没有消失,新结果叠在下面了。请修复,让每次预测前清空之前的结果。
```
**学生保底提示词(美化类):**
```
给整个页面加一个动态背景效果——缓慢变化的渐变色,从深紫到深蓝来回循环
```
**修复类提示词(改坏了时用):**
```
刚才的修改导致页面布局出问题了,[具体描述问题]。请恢复正常并保留之前的功能。
```
---
### 6. 教师指南
**本课技术备注:**
1. **内置浏览器**Trae 的内置浏览器基于 Chromium 内核,与 Chrome 渲染效果基本一致。某些高级 CSS 动画在内置浏览器中可能有轻微差异,但对课堂教学无影响。
2. **元素选择原理**:点击 Select 按钮后,内置浏览器进入"检查模式"。点击页面元素时Trae 会识别该元素在 HTML 中的位置(类似浏览器的 DevTools 元素检查器),并将信息传递给 Solo Coder让 AI 知道你要修改哪个元素。
3. **元素选择的局限性**:动态生成的元素(如通过 JavaScript 动态创建的内容)可能在某些情况下选择不到。这种情况下退回文字描述即可。
4. **Accept/Reject 与撤销**Accept 后代码已写入文件。如果需要撤回,可以用 Ctrl+Z 撤销文件改动,或让 AI 修复。Reject 则完全不改动文件。
5. **单文件迭代策略**:当前所有代码在一个 index.html 中。随着迭代次数增多文件会越来越长可能超过500行AI 仍然能正常处理,但响应速度可能略慢。
**常见问题 FAQ**
| 学生问题 | 建议回答 |
|---------|---------|
| "元素选择选不到我想改的东西" | 有些元素可能被其他元素盖住了,或者是动态生成的。没关系,直接在 Solo Coder 里用文字描述你想改什么——比如"页面底部的那个按钮" |
| "我改了好多轮,网页变得越来越慢了" | 代码可能变多了。跟 AI 说"请检查一下代码,去掉重复的或者没用到的部分,优化一下性能" |
| "我想回到几步之前的版本" | 目前最简单的办法是跟 AI 说"请把XX恢复成之前的样子"。以后我们会学更强大的版本管理方法 |
| "内置浏览器显示的和外部浏览器不一样" | 偶尔会有小差异。最终验收用外部浏览器双击 index.html 打开看一下 |
| "我改了一个地方,其他地方也变了" | 这可能是因为 AI 修改了公共样式。跟 AI 说"只修改XX那个元素的样式不要影响其他元素" |
| "迭代清单写不出来" | 试试这个思路:先看有没有 bug → 操作起来顺不顺畅 → 好不好看。每个方向找一个可以改的地方 |
**课堂风险预案:**
- 如果元素选择功能不可用(版本兼容问题):全程使用 Solo Coder 文字描述修改,教学目标不变
- 如果内置浏览器不可用使用外部浏览器手动刷新多花10秒但不影响核心教学
- 如果学生上节课的作品文件找不到了:用 Builder 快速重建一个基础版5分钟使用保底提示词
- 如果学生进度差异过大慢学生确保完成2轮迭代即可快学生挑战"加一个全新功能模块"
**备课体验任务清单:**
- [ ] 打开上节课的"未来职业预测器"项目,使用内置浏览器预览
- [ ] 用元素选择功能修改标题样式、按钮样式、卡片样式各一次
- [ ] 按"修 Bug → 加体验 → 美化"顺序完成5轮迭代
- [ ] 故意制造一个"改坏了"的情况,用 Solo Coder 修复
- [ ] 测试"一次说一个修改" vs "一次说三个修改"的效果对比
- [ ] 确认校区每台电脑的 Trae 内置浏览器和元素选择功能可正常使用
---
### 7. 5分钟日常AI挑战
**本周挑战:**
> **"我的作品 3.0 版"**
>
> **操作步骤:**
> 1. 打开你今天迭代过的"未来职业预测器"或"超能力抽签机"
> 2. 按"修 Bug → 加体验 → 美化"顺序,再迭代 **5 轮**
> 3. 目标:做到你觉得"可以拿给全班同学玩"的水平
> 4. 截图保存:迭代前 → 迭代中 → 最终版本
>
> **评价标准:**
> - 基础完成了5轮迭代有前后对比截图
> - 进阶:三个维度(修 Bug、加体验、美化都有涉及
> - 挑战给3个以上的人试用收集反馈并根据反馈再改一轮
---
### 8. 拓展任务
**拓展一(推荐):极限迭代挑战**
> 对你的作品再进行 **10 轮迭代**,看看你能把它打磨到什么程度。每轮迭代前先想清楚"这一轮要改什么",不要随便乱改。记录每轮改了什么,做一份"迭代日志"。
**拓展二(挑战):给别人做迭代**
> 跟一个同学交换作品。用元素选择和 Solo Coder 帮**别人的作品**迭代 3 轮。你需要先理解别人的作品"在做什么",然后判断"哪里可以更好"。改完后跟同学分享你改了什么、为什么这样改。

View File

@@ -0,0 +1,689 @@
---
课时: 4
主题: 我的个人主页(上)
核心能力: [拆解力, 审美力]
核心工具: [Trae IDE]
时长: 90分钟
透明化层级: 结果层
适用路线: AICODE-06有扣子/低代码经验的学生)
---
### 1. 课程目标
**知识目标:**
- 理解"内容先行"原则:先想清楚要展示什么,再让 AI 动手做
- 理解个人主页的板块化结构:把一个页面拆成多个独立区域
- 理解 Trae 自定义智能体的概念:给 AI 设定角色和规则,让它更懂你的需求
**能力目标:**
- 能列出至少 8 个板块的个人信息清单(拆解力)
- 能在 Trae 中创建一个自定义智能体并编写系统提示词(提问力)
- 能用智能体生成个人主页并逐块替换为真实内容(共创力)
**情感目标:**
- 激发"我要向世界展示自己"的表达欲——这个页面真的会被别人看到
- 建立"好作品的核心是内容,不是技术"的认知
- 感受从扣子智能体到 Trae 智能体的能力迁移——"我之前学的东西没白学"
---
### 2. 核心概念与误概念预设
**核心概念认知层级:**
| 概念 | 学生类比 | 认知层级 |
|------|---------|---------|
| 内容先行 = 先想好写什么再让 AI 做 | 像写作文先列提纲,不是拿起笔就写 | 理解层 |
| 板块化设计 = 把页面拆成独立区域 | 像报纸分版面——头版、体育版、娱乐版各管各的 | 理解层 |
| 自定义智能体 = 给 AI 设定角色和规则 | 像在扣子里创建智能体一样,只不过现在是在 Trae 里给 AI 定规矩 | 应用层 |
| 占位内容 vs 真实内容 | AI 先用假文字搭架子,像样板间的假家具,你要换成自己真正的东西 | 理解层 |
**典型误概念表:**
| 编号 | 误概念 | 正确认知 | 激发策略 |
|------|--------|---------|---------|
| M1 | "直接告诉 AI '帮我做个主页'就行了" | 没有内容的主页是空壳AI 不知道你的爱好和经历 | 演示一个"空壳主页"——全是 AI 编的假信息,问学生"这是你吗?" |
| M2 | "内容想到什么写什么就好,不需要组织" | 有结构地组织内容,读者才能快速了解你(板块化思维) | 对比:一篇流水账 vs 一份分板块的个人简介,哪个看得更清楚? |
| M3 | "AI 生成的文字够用了,不需要换成真实的" | 占位内容是 AI 编的假信息,别人看了不知道真实的你 | 展示 AI 生成的"爱好:画画、弹钢琴",问学生"这是你的真实爱好吗?" |
| M4 | "智能体的提示词随便写写就行" | 提示词越清楚AI 出的效果越稳定(跟扣子一样的道理) | 对比:一句话提示词 vs 详细规则的提示词,生成效果差异明显 |
| M5 | "图片太麻烦了,纯文字就行" | 视觉元素让页面生动,可以用 emoji、网络图片、或 AI 生成 SVG 图标 | 展示纯文字版 vs emoji 装饰版的同一个主页,视觉差距一目了然 |
---
### 3. 教学准备
**工具与环境:**
- 每台电脑已预装 Trae IDE已登录校区账号
- 网络环境正常
- 内置浏览器可用Trae 自带预览功能)
**教学资源:**
- 教师准备2-3 个优秀个人主页截图风格各异、1 个"空壳主页"(全是假内容,用于激发误概念)
- 教师准备1 份教师自己的内容清单12 条以上、已验证的智能体提示词、1 份保底 HTML 模板
- 学生资源:上节课的 Trae 操作经验(三种模式、元素选择、迭代深化)
**教师备课体验任务:**
> 备课前,教师必须亲自完成以下操作:
> 1. 在 Trae 中创建自定义智能体,确认创建入口和操作路径
> 2. 用智能体生成个人主页 index.html验证效果正常
> 3. 用 Solo Coder 逐块替换占位内容,确认流程顺畅
> 4. 准备"空壳主页"截图AI 随便生成的假信息页面)
---
### 4. 教学流程
---
**第一幕:联系 (Connect) — 10分钟** 🔗
*本幕目标:回顾前三课技能,用优秀个人主页案例激发"我要展示自己"的表达欲,引出"内容先行"的核心理念*
**【环节】上节课回顾 (3分钟)**
**师:** 前三节课我们学了不少本事。谁能帮我回忆一下——你现在会用 Trae 做什么?
**生:** 预期Builder 从零做项目 / Solo Coder 改代码 / 元素选择点哪改哪 / 跟随模式看 AI 工作 / 迭代深化)
**师:** 说得很全三种模式会用了元素选择会用了迭代也练过了。上节课的5分钟挑战是"把作品再迭代5轮",有谁做了的?
选1位学生快速展示课外挑战成果30秒
**师:** 好,前三节课你们做了"未来职业预测器"和"超能力抽签机"。这些都是——好玩的小工具。今天开始,我们做一个不一样的东西。
**【环节】情景导入 (7分钟)**
(教师投屏展示 2-3 个优秀个人主页截图,风格各异——逐个指着介绍)
**师:** 这个——一打开就知道这个人叫什么、喜欢什么。这个——暗黑科技风,酷不酷?这个——可爱风,满屏 emoji。这些都是什么——**个人主页**。一个专门介绍"你是谁"的网页。
**师:** 现在我问你们一个问题——如果全世界的人都能看到你的网站,你会在上面放什么?【诊断点:探测学生对"自我展示"的初始意愿和内容意识】【识别层】
(让 3-4 个学生快速回答)
**【分支A】若学生说出具体内容"我的名字""我喜欢的游戏""我的荣誉"**
**师:** 很好!你已经在想"内容"了。这就是今天最重要的事。
**【分支B】若学生说"不知道放什么"或"放好看的图片"**
**师:** 放好看的图片当然可以,但别人打开你的页面,最想知道的是——你是谁?你喜欢什么?你做过什么?"好看"是加分项,"内容"才是核心。
**【分支C】若学生说"让 AI 帮我写"**
**师:** AI 可以帮你做页面,但 AI 知道你最好的朋友是谁吗?知道你最喜欢的歌是什么吗?——不知道。所以内容必须你自己想、自己填。
**师:** 告诉你们一个重要消息——**你们做的个人主页,最终会发布到穹狼科创的网站上**。真的会有人看到。所以这不是练习——这是你**面向真实观众的作品**。今天做内容和基础版,下节课做设计升级和正式路演发布。
**师:** 但是——我们今天**不急着打开 Trae**。为什么?先看一个反面教材。
(教师投屏展示 AI 生成的"空壳主页"——全是假内容)
**师:** 这是我让 AI 做的个人主页——名字叫"小明",爱好是"画画、弹钢琴、踢足球"。但我不叫小明,我不会弹钢琴。**AI编的全是假的。** 直接跟 AI 说"帮我做个主页",它就会编假信息。所以今天第一件事不是写代码——而是**先想清楚你要展示什么**。
---
**第二幕:建构 (Construct) — 65分钟** 🛠️
---
**【分段一:我要展示什么?— 个人内容清单】(15分钟)**
*本段重点:用结构化引导帮学生梳理个人信息,建立"内容先行"的意识。不打开 Trae*
**预设误概念:**
- M1: "直接告诉 AI '帮我做个主页'就行了"——没有内容的主页是空壳
- M2: "内容想到什么写什么就好,不需要组织"——缺乏板块化思维
**讲解与演示 (5分钟):**
**师:** 写作文之前,语文老师会让你干什么?
**生:** (预期:列提纲 / 打草稿)
**师:** 对——列提纲。做个人主页也一样。你不能拿起来就做,得先想清楚"我要展示什么"。这就叫**内容先行**——先想好内容,再让 AI 做页面。【理解层】
**师:** 而且内容不能乱写,得分**板块**。什么叫板块?——就像报纸有头版、体育版、娱乐版,每个版面放不同的内容。你的个人主页也一样,每个区域放不同类型的信息。这样别人一看就很清楚。
**师:** 我先给你们看我的内容清单。
(教师投屏展示自己的内容清单,逐条念)
```
我的内容清单:
1. 名字:武老师 / Mr. Wu
2. 一句话介绍:一个相信 AI 能改变教育的编程老师
3. 出生年份 / 星座1990 / 天秤座 ♎
4. 最擅长的三件事:写代码、讲课、做饭
5. 最好的朋友小李因为我们从小学就认识到现在20多年了
6. 最喜欢的歌:《晴天》—— 周杰伦
7. 最喜欢的电影:《星际穿越》
8. 最喜欢的书:《小王子》
9. 荣誉和证书:编程比赛一等奖、优秀教师
10. 我的梦想:让每个孩子都能用 AI 创造自己的作品
11. 我的作品穹狼科创网站、AI编程课程体系
12. 想用什么图片:用 emoji 先占位,课后换真实照片
```
**师:** 12 条。每一条都是**真实的我**。如果 AI 帮我编,它绝对编不出"最好的朋友是小李认识了20多年"这种细节。
**师:** 现在轮到你们了。
**学生实践 (8分钟):**
**师:** 拿出一张纸,或者打开电脑的记事本。**不要打开 Trae** 现在只写内容。
(教师投屏显示问题清单,逐条引导)
**师:** 我一条条问,你一条条写。跟上节奏!
(教师逐条引导,每条停顿等学生写完后再下一条)
**师:** 第一条——你的名字。中文名加英文名或昵称。比如"张小明 / 大明同学"。
**师:** 第二条——用一句话介绍自己。让陌生人听完就能记住你。比如"一个痴迷乐高和编程的六年级男生"。不要写"我是一个好学生"——太无聊了,没人记得住。
**师:** 第三条——出生日期或星座。第四条——你最擅长的三件事,不一定是学习上的!第五条——你最好的朋友,写名字和"为什么是最好的"。
**师:** 第六条——最喜欢的歌或歌手。第七条——最喜欢的电影或动画。第八条——最喜欢的书,漫画也行。
**师:** 第九条——荣誉和证书,没有就写"正在努力中"。第十条——你的梦想。第十一条——你的作品展示(编程/画画/手工/乐高)。第十二条——想用什么图片?今天先用 emoji 占位。
**师:** 写好 8 条以上的举手!
**进度同步 (2分钟):**
**师:** 我来走一圈看看。
(教师走动检查每位学生的内容清单)
【诊断点:谁写了 8 条以上?谁只写了两三条就停了?内容是否足够具体?】
**【分支A】若大部分学生写了 8 条以上:**
**师:** 很好!内容够丰富了。有些同学写了 10 条以上,非常棒。一会儿 AI 做出来的主页才会是"真正的你"。
**【分支B】若有学生只写了三四条就停了**
**师:** (走到旁边)你卡在哪了?是不知道写什么?还是觉得没什么好写的?
——(如果不知道写什么)看看投屏上的问题清单,一条条往下写。不用想太多,真实就好。
——(如果觉得没什么好写)每个人都有值得展示的东西。你最喜欢什么?平时放学后做什么?这些都是内容。
**【分支C】若有学生写的内容太简略比如"爱好:打游戏"一笔带过):**
**师:** 你写了"打游戏"——很好。但能不能再具体一点?打什么游戏?段位多少?最厉害的一次是什么?越具体,别人看了越有感觉。
---
**【分段二:创建"我的个人主页设计师"智能体】(15分钟)**
*本段重点:在 Trae 中创建自定义智能体,迁移扣子时代的"给 AI 设定角色"经验*
**预设误概念:**
- M4: "智能体的提示词随便写写就行"——提示词越清楚出的效果越稳定
- M3: "AI 生成的文字够用了,不需要换成真实的"——占位内容是假信息
**讲解与演示 (7分钟):**
**师:** 内容准备好了。现在可以打开 Trae 了!
(学生打开 Trae
**师:** 但是——我们今天不直接用 Builder 或 Solo Coder。我们先做一件事**创建一个自定义智能体**。
**师:** 你们在扣子里创建过智能体对吧?给它一个名字、一个角色、一些规则,然后它就按照你定的规矩来干活。
**生:** (预期:对 / 做过)
**师:** Trae 里也能做一样的事!你可以创建一个"专属于你的 AI 助手",给它设定角色和规则。这样每次跟它说话,它都会按照你的规则来。
**师:** 打个比方——你去理发店,每次跟不同理发师说"剪短一点",效果都不一样。但你有一个固定理发师,他记住你喜欢的风格,每次都稳定。**自定义智能体就是你的"固定理发师"。**
**师:** 我来演示。(教师投屏操作)
**师:** 第一步——Trae AI 面板上找到"创建新智能体"按钮。第二步——起名字,我叫它"个人主页设计师"。第三步——最关键——写**系统提示词**,告诉 AI "你是谁、遵守什么规则"。
(教师输入系统提示词,投屏逐行念)
```
你是一个专业的个人主页设计师。请遵循以下规则:
- 所有文字必须用中文
- 页面风格:现代简约,清新活泼
- 配色不超过三种主色
- 每个板块之间有清晰的分隔
- 多使用 emoji 作为图标装饰
- 所有内容放在一个 HTML 文件中(内联 CSS 和 JS
- 页面要适合在电脑浏览器中展示
- 页面需要有流畅的滚动体验
```
**师:** 8 条规则,每条都很具体。跟你在扣子里写智能体提示词是不是一模一样的道理?——对!**扣子里学的东西没白学,同一套思路,换个平台照样用。**
**师:** 你的智能体不需要和我的一样!你想做什么风格?
(投屏展示风格方向)
| 风格 | 提示词关键词 |
|------|------------|
| 🌸 可爱风 | 粉色系、圆角卡片、可爱 emoji |
| 🚀 科技风 | 深色背景、霓虹灯效果、代码感字体 |
| 🌙 暗黑风 | 黑色背景、渐变色文字、发光效果 |
| 🌈 彩虹风 | 多彩渐变、活泼动画、大量 emoji |
**师:** 选一个你喜欢的风格,把关键词加到提示词里。比如暗黑风就把"现代简约,清新活泼"改成"暗黑风格、黑色背景、渐变色文字、发光效果"。
**学生实践 (6分钟):**
**师:** 现在你们来创建自己的智能体。打开 AI 面板 → 创建新智能体 → 起名字 → 写系统提示词(参考我的模板,改成你想要的风格)。提示词里**必须保留**:中文、单文件 HTML、内联 CSS 和 JS、emoji 装饰。风格随便改。6分钟
(教师走动重点关注:是否找到创建入口、提示词是否够具体、是否保留技术规则)
**快学生应对:** 加更多个性化规则——"标题用手写字体感觉"、"悬停动画效果"等。
**慢学生应对:** 找不到入口手把手指一次。不知道写什么的直接抄教师模板,改一个风格关键词。
**进度同步 (2分钟):**
**师:** 创建好智能体的举手!
**师:** 我来抽查两位——你的智能体叫什么?提示词里写了几条规则?【诊断点:检测学生是否写了详细的系统提示词,还是只写了一两句话】【应用层】
(抽 2 位学生回答)
**【分支A】若学生写了 5 条以上规则:**
**师:** 很棒规则越清楚AI 出的效果越稳定。跟你在扣子里的经验一样对吧?
**【分支B】若学生只写了一两句M4 误概念暴露):**
**师:** 你只写了"帮我做一个好看的网页"——这跟扣子里只写"你是一个助手"一样太模糊了。AI 不知道你要什么风格、什么颜色、什么布局。加几条具体规则——什么风格?什么配色?要不要 emoji
**【分支C】若有学生忘了保留技术规则**
**师:** 记得加上"所有内容放在一个 HTML 文件中,内联 CSS 和 JS"——不然 AI 可能会生成多个文件,我们现在还没学多文件项目。
---
**【分段三:用智能体生成第一版 + 填入真实内容】(20分钟)**
*本段重点:把内容清单转化为结构化需求,用智能体生成主页,逐块替换占位内容*
**预设误概念:**
- M1: "直接告诉 AI '帮我做个主页'就行了"——需要把内容清单整理成需求
- M3: "AI 生成的文字够用了,不需要换成真实的"——占位内容必须替换
- M5: "图片太麻烦了,纯文字就行"——emoji 让页面更生动
**讲解与演示 (5分钟):**
**师:** 智能体创建好了。现在我们要用它来生成个人主页。但不是直接说"帮我做主页"——还记得那个反面教材吗?全是假信息。
**师:** 正确的做法是:**把你的内容清单整理成一条完整的需求**,发给智能体。我来演示:
(教师投屏,使用自己创建的智能体,输入提示词——把内容清单按【基本信息】【我的特长】【我的好友】【我的最爱】【荣誉成就】【我的梦想】【我的作品】分板块整理,末尾加"每个板块用独立卡片,标题前加 emoji顶部有大名字和一句话介绍作为 hero 区域"。完整提示词见第5章AI助教使用指南。
(等待 AI 生成)
**师:** 看到了吗我把内容清单按板块整理好发给智能体。AI 知道每个板块放什么,生成出来就是**真实的信息**,不是假的。
Accept 代码,在内置浏览器中预览)
**师:** 来看效果——名字对不对?介绍对不对?每个板块内容对不对?
(逐块检查)
**师:** 基本上都对!但是——你们仔细看,有些地方 AI 可能会自己加一些文字。比如这里写了"欢迎来到我的主页"——我没让它写这个。这就是**占位内容**AI 自己编的。我们要用 Solo Coder 把这些假内容替换掉。
(演示用 Solo Coder 元素选择,点击一处占位文字)
```
把这段文字改成:"写代码是我的工作,讲课是我的热爱,做饭是我的解压方式"
```
Accept → 内容更新)
**师:** 这就是完整的流程:**内容清单 → 结构化需求 → 智能体生成 → 逐块检查 → Solo Coder 替换假内容**。
**学生实践 (13分钟):**
**师:** 现在你们来做!
**步骤:**
1. 桌面新建文件夹(用你的名字命名)→ Trae 打开 → 切换到你的智能体
2. 把内容清单整理成结构化需求(分板块、每条写清楚)→ 发送 → Accept → 预览
3. 对照内容清单逐条检查:哪些是真实的?哪些是 AI 编的?
4.**Solo Coder + 元素选择** 逐块替换假内容
**师:** 13分钟。重点放在**内容的真实性**——每一条信息都必须是真的你。好不好看先不管,下一步再调。
**如果你不知道怎么整理需求,用这个简化版:**
```
请帮我创建一个 index.html 个人主页,包含以下板块:
1. 顶部大标题区:名字是[你的名字],一句话介绍是[你的介绍]
2. 关于我:星座[你的星座],最擅长[你的三件事]
3. 我的好友:[好友名字和原因]
4. 我的最爱:歌[你的歌]、电影[你的电影]、书[你的书]
5. 荣誉成就:[你的荣誉]
6. 我的梦想:[你的梦想]
7. 我的作品:[你的作品]
每个板块用独立卡片,标题前加 emoji 图标。
```
教师走动重点关注是否整理了结构化需求M1、是否检查内容真实性M3、替换操作是否顺畅
**快学生应对:** 加更多板块——"我的宠物"、"去过最远的地方"。
**慢学生应对:** AI 跑不起来就**启动兜底方案**:教师发保底 HTML 模板,学生用 Solo Coder 逐块改内容。
**进度同步 (2分钟):**
**师:** 在浏览器里看到自己主页的举手!
**师:** 我来问一个关键问题——你的页面上,有没有 AI 编的假信息还没改过来的?【诊断点:检测学生是否有"内容真实性审查"的意识】【理解层】
**【分支A】若学生说"都改了/检查过了"**
**师:** 好!逐条对照你的内容清单,确认每一条都是真的。
**【分支B】若学生说"有几处还没改"**
**师:** 没关系,等下还有时间改。但记住——最终发布的时候,**每一个字都得是真的你**。AI编的假信息留在上面别人看了会觉得奇怪。
**【分支C】若学生承认"直接用了 AI 编的内容,没换"**
**师:** 你用了 AI 编的爱好那我问你——AI 说你喜欢"画画和弹钢琴",你真的喜欢吗?
**生:** (预期:不喜欢 / 不太准)
**师:** 看吧!这就是为什么要替换。你的主页代表的是"你",不是 AI 想象中的你。现在打开内容清单,一条条改过来。
---
**【分段四:第一轮视觉调整】(15分钟)**
*本段重点:内容到位后开始视觉优化,用智能体 + Solo Coder 做至少 3 轮视觉迭代*
**预设误概念:**
- M5: "图片太麻烦了,纯文字就行"——emoji 和视觉元素让页面更生动
- M4: "智能体的提示词随便写写就行"——通过智能体修改更高效
**讲解与演示 (4分钟):**
**师:** 内容填好了——你的主页上每条信息都是真实的你了。现在进入第二步——**变好看**。
**师:** 你上节课学过迭代三步走——修 Bug、加体验、美化。今天我们主要做"美化"这一步。
**师:** 我演示 3 个视觉修改:
(教师用自己的智能体 + Solo Coder 演示)
**修改一:配色调整**
(用元素选择点击页面背景)
```
把整个页面的背景色改成从深蓝到紫色的渐变,所有卡片改成半透明白色背景(带毛玻璃效果),文字改成白色
```
Accept → 看效果)
**师:** 一改配色,整个感觉就不一样了。
**修改二:标题区域强化**
(用元素选择点击顶部名字区域)
```
把顶部名字区域改大名字字体加到很大加一个文字发光效果。一句话介绍放在名字下面字号稍小用浅色显示。整个顶部区域高度占屏幕的40%,内容垂直居中。
```
Accept → 看效果)
**修改三:板块间距和分隔**
```
给每个板块卡片之间增加间距每个卡片加一个微妙的悬停放大效果鼠标移上去轻微放大1.02倍),板块标题的 emoji 放大一些
```
Accept → 看效果)
**师:** 三轮修改,每次只改一个方面。注意——我用的是**自己创建的智能体**来修改的,因为它已经知道我的设计规则,出来的效果更统一。
**师:** 还有一个小技巧:视觉修改的描述要**具体**。不要说"变好看"——AI 不知道你觉得什么好看。说"背景用深蓝到紫色渐变"、"标题加发光效果"、"卡片加悬停放大"——越具体越好。
**学生实践 (9分钟):**
**师:** 现在你们来美化自己的主页。用你创建的智能体 + Solo Coder完成**至少 3 轮视觉修改**。
**修改灵感参考:**
| 修改方向 | 具体描述示例 |
|---------|------------|
| 🎨 配色 | "背景改成粉色渐变"、"卡片背景用淡黄色"、"标题用金色" |
| 📐 布局 | "顶部区域加大"、"卡片改成两列排列"、"增加板块间距" |
| ✨ 动效 | "卡片加悬停放大"、"标题加发光呼吸效果"、"页面加淡入动画" |
| 🔤 字体 | "标题字体加大"、"介绍文字用斜体"、"板块标题加粗加大" |
| 😊 装饰 | "每个板块加分隔线"、"页脚加一行小字"、"加一个回到顶部按钮" |
**师:** 记住:
- **用你的智能体**来修改(它知道你的设计规则)
- **每次只改一个方面**
- **用元素选择**点你想改的地方
**师:** 9分钟至少完成 3 轮视觉修改。开始!
(教师走动重点关注:是否用自己的智能体、描述是否具体、是否每次只改一个、是否用元素选择)
**快学生应对:** 挑战暗黑/亮色主题切换或滚动动画。**慢学生应对:** 教师给指令:"点页面背景,说'改成渐变色'"。
**进度同步 (2分钟):**
**师:** 做了 3 轮以上视觉修改的举手5 轮以上的呢?
**师:** 你们现在回头看看自己的主页——跟 AI 刚生成的第一版比,变化大不大?【诊断点:检测学生是否感知到视觉迭代的价值】【理解层】
**【分支A】若学生说"变化很大/好看多了"**
**师:** 对!而且你没有重新做,只是一轮轮改。**好作品是改出来的。**
**【分支B】若学生说"改了但感觉还是不太好看"**
**师:** 没关系!这就是为什么我们有两节课。今天把内容和基础视觉搞定,下节课专门做设计升级。你越改越有感觉的。
---
**第三幕:反思 (Contemplate) — 10分钟** 🤔
*本幕目标:同桌互看页面,收集改进建议,为下节课做准备*
**【环节】同桌互看 (6分钟)**
**师:** 今天不做路演——路演留到下节课正式发布的时候。今天做一个更有用的事:**同桌互看**。
**师:** 请你和旁边的同学互相看对方的个人主页。看完之后,每人给对方写**两条建议**
1. **一条内容建议**:内容上还可以加什么?哪里写得不够具体?哪条信息特别有趣?
2. **一条设计建议**:视觉上哪里可以更好?颜色搭不搭?哪个板块看着不舒服?
**师:** 写在纸上或者记事本里等下交给对方。4分钟
(学生互看,教师走动观察讨论情况)
**【环节】建议分享 (4分钟)**
**师:** 时间到。谁收到了一个特别好的建议,愿意分享?【诊断点:检测学生能否给出具体的、有建设性的反馈】【应用层】
(选 2-3 位学生分享收到的建议)
**【分支A】若建议很具体"你可以在'我的梦想'板块加一段解释"、"你的卡片间距太小了"**
**师:** 这个建议非常具体!"卡片间距太小"——这就是一个可以直接交给 AI 的修改需求。下节课用起来!
**【分支B】若建议很模糊"挺好的"、"再好看一点"**
**师:** "再好看一点"不够具体哦。试着说——哪个板块?哪个方面?比如"标题区域可以再大一点"、"背景颜色可以换一个"。具体的建议才能变成具体的修改。
**师:** 把同桌给你的建议记好——下节课第一件事就是按建议优化。
> **拍照发群:** 教师拍 2-3 位学生个人主页的截图,配文字"第四节课,同学们开始制作自己的个人主页!真实的爱好、真实的梦想,每一个字都是他们自己的故事。下节课继续打磨,敬请期待最终发布!" 发家长群。
---
**第四幕:延续 (Continue) — 5分钟** 🚀
*本幕目标:总结"内容先行"核心理念,预告下节课,发布课外挑战*
**【环节】抽象总结 (3分钟)**
**师:** 今天我们做了一件跟前三节课完全不一样的事。前三节课——我们做的是"小工具",职业预测器、抽签机。今天——我们做的是**属于你自己的作品**,每一个字都是真实的你。
**师:** 今天最重要的一课是什么?
(投屏)
```
今日核心:
📝 内容先行 → 先想清楚展示什么,再让 AI 动手做
🧩 板块化设计 → 把一个页面拆成独立区域,分块展示
🤖 自定义智能体 → 给 AI 设定角色和规则,效果更稳定
✅ 真实内容 → 占位假信息必须替换成真实的你
```
**师:** 记住一句话:**好作品的核心是内容,不是技术。** AI 可以帮你做出炫酷的页面,但里面放什么——只有你自己知道。技术是工具,内容才是灵魂。
**师:** 这个道理不只适用于做网页。写作文、做 PPT、拍视频——都是一样的。先想清楚要说什么再想怎么说。
**【环节】下节预告 + 5分钟挑战 (2分钟)**
**师:** 下节课——**我的个人主页(下)**。我们要做三件事:
1. **设计升级**——根据同桌给你的建议做第二轮大优化
2. **交互效果**——加滚动动画、暗黑模式切换等高级效果
3. **正式路演发布**——3分钟路演向全班介绍"你是谁"
**师:** 最终版本会发布到穹狼科创网站。所以下节课之前,你得把内容准备得更充分。
**师:** 本周5分钟挑战回家把内容清单扩充到 **15 条以上**——最喜欢的食物、去过最远的地方、养的宠物……越丰富越好。每条写具体细节,问问家人"你觉得我有什么特别的?"。如果有照片让爸妈发给你下节课用。详见第7章挑战说明
**师:** 记住——内容越丰富,下节课你的主页就越精彩。准备得越充分,路演的时候就越有东西可说。下节课见!
---
### 5. AI助教使用指南
**教师演示用提示词(智能体系统提示词):**
```
你是一个专业的个人主页设计师。请遵循以下规则:
- 所有文字必须用中文
- 页面风格:现代简约,清新活泼
- 配色不超过三种主色
- 每个板块之间有清晰的分隔
- 多使用 emoji 作为图标装饰
- 所有内容放在一个 HTML 文件中(内联 CSS 和 JS
- 页面要适合在电脑浏览器中展示
- 页面需要有流畅的滚动体验
```
**教师演示用提示词(主页生成):**
```
请根据以下个人信息,帮我创建一个 index.html 个人主页:
【基本信息】
- 名字:武老师 / Mr. Wu
- 一句话介绍:一个相信 AI 能改变教育的编程老师
- 星座:天秤座 ♎
【我的特长】最擅长:写代码、讲课、做饭
【我的好友】小李从小学认识至今20多年的铁哥们
【我的最爱】歌:《晴天》周杰伦 🎵 / 电影:《星际穿越》🎬 / 书:《小王子》📖
【荣誉成就】编程比赛一等奖 🏆、优秀教师 🎖️
【我的梦想】让每个孩子都能用 AI 创造自己的作品
【我的作品】穹狼科创网站、AI编程课程体系
请为每个板块设计独立卡片,从上到下排列,标题前用 emoji顶部有大名字+一句话介绍作为 hero 区域。
```
**学生保底提示词(简化版主页生成):**
```
请帮我创建一个 index.html 个人主页,包含以下板块:
1. 顶部大标题区:名字是[你的名字],一句话介绍是[你的介绍]
2. 关于我:星座[你的星座],最擅长[你的三件事]
3. 我的好友:[好友名字和原因]
4. 我的最爱:歌[你的歌]、电影[你的电影]、书[你的书]
5. 荣誉成就:[你的荣誉]
6. 我的梦想:[你的梦想]
每个板块用独立卡片,标题前加 emoji 图标。
背景用渐变色,整体风格清新活泼。
所有代码放在一个 HTML 文件中。
```
**学生保底提示词(替换占位内容):**
```
页面上[具体位置]写的是"[AI编的假内容]",请改成"[你的真实内容]"
```
**进阶提示词(视觉增强,留给下节课或快学生):**
```
给页面加一个暗黑模式切换按钮:右上角放月亮/太阳 emoji点击切换亮色/暗色主题
```
```
给页面加滚动动画效果每个板块卡片滚动到可视区域时从下方淡入滑出持续0.5秒
```
---
### 6. 教师指南
**本课技术备注:**
1. **HTML 个人主页基本结构**:单文件 index.html包含 hero 区域 + 多个 section 板块。CSS 用 `<style>` 内联JS 用 `<script>` 内联。AI 生成的结构通常规范,教师不需要手动调代码。
2. **Trae 自定义智能体**:本质是预设 System Prompt每次对话自动附加。不同 Trae 版本入口可能不同,备课时必须确认。
3. **emoji 作为视觉元素**:直接输入即可,通过 CSS `font-size` 任意放大不失真,配合动画效果很好。
4. **照片方案**:本课用 emoji 占位,课后家长发照片,下节课用 base64 或 URL 替换。
**常见问题 FAQ**
| 学生问题 | 建议回答 |
|---------|---------|
| "怎么加真实照片?" | 今天先用 emoji 占位,课后让爸妈发照片,下节课教你放进去 |
| "页面太长了怎么办?" | 个人主页就是滚动着看的,长一点没关系。实在太长可以删不重要的板块 |
| "AI 生成的打不开/空白" | 检查是否 Accept 了。已 Accept 还空白就让 AI 修复。实在不行用保底模板 |
| "创建智能体的按钮在哪?" | (根据当前 Trae 版本指引,通常在 AI 面板顶部的智能体选择区域) |
**课堂风险预案:**
- 如果 Trae 自定义智能体功能不可用(版本不支持):直接在 Builder/Solo Coder 对话开头手动粘贴系统提示词,效果类似,只是每次都要重复粘贴
- 如果 AI 生成的页面完全不能用:教师把提前准备的保底 HTML 模板文件发给学生,学生用 Solo Coder 逐块修改内容。核心教学目标(内容先行 + 真实内容填充)不受影响
- 如果学生进度差异过大:慢学生确保完成"内容清单 + 基础版生成 + 至少替换3处真实内容"即可。快学生做视觉优化 + 加额外板块
- 如果学生不愿意写个人信息(隐私顾虑):完全尊重。可以用昵称、可以跳过不想写的条目、可以用虚构角色做主页("给你最喜欢的动漫角色做主页")。核心训练目标不变
**备课体验任务清单:**
- [ ] 确认 Trae 智能体创建入口,完整走一遍创建→生成→替换内容流程
- [ ] 准备"空壳主页"截图 + 2-3 个不同风格个人主页截图
- [ ] 测试保底 HTML 模板可正常打开
- [ ] 确认校区每台电脑的 Trae 智能体功能正常
---
### 7. 5分钟日常AI挑战
**本周挑战:**
> **"我的超级内容清单"**
>
> **操作步骤:**
> 1. 把你的内容清单从 8 条扩充到 **15 条以上**
> 2. 参考方向:最喜欢的食物、去过最远的地方、养的宠物、最喜欢的运动、一个关于你的冷知识、你最想学的技能
> 3. 每条写**具体细节**——不是"喜欢吃面",是"最喜欢兰州拉面,加辣加蛋,每周至少吃两次"
> 4. 问家人:"你觉得我有什么特别的地方?"把他们的回答也加进去
> 5. 如果有照片(自拍、作品照、旅行照),让家长发给你备用
>
> **评价标准:**
> - 基础:内容清单达到 12 条以上
> - 进阶:每条都有具体细节描述
> - 挑战:收集了家人对你的评价,并加入清单
**下节课分享:** 下节课开始时选 2-3 位同学展示扩充后的内容清单
---
### 8. 拓展任务
**拓展一(推荐):给好朋友做主页**
> 用同样的方法——先列内容清单(问你的好朋友关于他/她的信息),再用你的智能体帮他/她生成一个个人主页。送给好朋友当礼物!这是一个很好的"板块化设计 + 内容先行"的迁移练习。
**拓展二(挑战):双语版个人主页**
> 给你的个人主页加一个"中英文切换"按钮。点击后所有中文内容切换成英文版本,再点切回中文。需要你先把内容清单翻译成英文,再用 Solo Coder 实现切换功能。

View File

@@ -0,0 +1,693 @@
---
课时: 5
主题: 我的个人主页(下)
核心能力: [审美力, 表达力]
核心工具: [Trae IDE]
时长: 90分钟
透明化层级: 结果层
适用路线: AICODE-06有扣子/低代码经验的学生)
---
### 1. 课程目标
**知识目标:**
- 理解"交互元素"的概念:页面不只是看的,还可以有动态效果和用户操作
- 理解"设计一致性"的概念:整个页面的配色、字体、间距要统一协调
- 理解"发布"的含义:作品从"自己电脑上看"变成"别人也能看"
**能力目标:**
- 能根据同学建议有针对性地优化页面(审美力)
- 能用自然语言描述想要的交互效果并让 AI 实现(共创力)
- 能用 3 分钟向全班展示并介绍自己的个人主页(表达力)
**情感目标:**
- 体验从"半成品"到"完整作品"的蜕变带来的成就感
- 建立"作品要经得起别人看"的产品意识
- 感受"我的作品即将发布到真实网站"的兴奋与自豪
---
### 2. 核心概念与误概念预设
**核心概念认知层级:**
| 概念 | 学生类比 | 认知层级 |
|------|---------|---------|
| 交互效果 = 页面能"动"能"响应" | 像游乐场——不是只能看的风景画,而是能坐的旋转木马 | 理解层 |
| 设计一致性 = 统一风格 | 像穿衣搭配——上衣、裤子、鞋子要搭配,不能运动衫配西裤 | 应用层 |
| 同伴反馈 = 最好的改进来源 | 自己觉得好看不算,要别人也觉得好才是真的好 | 理解层 |
| 路演 = 作品展示 + 设计思路讲解 | 像参加画展——不只是挂一幅画,还要跟观众讲你为什么这么画 | 应用层 |
| 发布 = 从"自己看"到"全世界看" | 像把日记变成公开发表的文章——内容要经得起别人看 | 迁移层 |
**典型误概念表:**
| 编号 | 误概念 | 正确认知 | 激发策略 |
|------|--------|---------|---------|
| M1 | "加特效越多越好,越炫越好" | 好的设计是克制的3个精致的效果胜过10个花哨的 | 展示两个页面对比:一个特效堆满但杂乱,一个精致克制但高级 |
| M2 | "同学的建议不靠谱,还是自己的好" | 用户(读者)的感受比设计者的想法更重要 | 让学生用别人的电脑看自己的页面,发现"我觉得好看"和"别人觉得好看"的差距 |
| M3 | "交互效果很难,我做不了" | 用自然语言告诉 AI 就行,跟之前改样式一样简单 | 教师现场用一句话让 AI 加出 hover 效果,打消畏难情绪 |
| M4 | "发布前不用检查,能打开就行" | 发布前要完整检查:文字有没有错、图片有没有加载、手机上能不能看 | 展示一个"能打开但有明显错误"的反面案例(假文字、空白区域) |
| M5 | "路演就是把页面打开给大家看" | 路演要讲设计思路:为什么选这个主题?为什么这样设计?有什么巧思? | 对比两段路演:一个只展示、一个讲思路,让学生投票哪个更好 |
---
### 3. 教学准备
**工具与环境:**
- 每台电脑已预装 Trae IDE已登录校区账号
- 网络环境正常
- 学生上节课的个人主页项目文件夹仍在桌面
- 学生上节课创建的"个人主页设计师"自定义智能体仍可用
**教学资源:**
- 教师准备1个"半成品→完成版"的 before/after 对比页面(课前用自己的个人主页做好两个版本)
- 教师准备:上节课学生互评时收集到的建议纸条/照片(备用,防止学生忘带)
- 教师准备:发布前检查清单(投屏用,打印几份备用)
- 教师准备2-3 段验证过的交互效果代码片段hover、轮播、滚动动画各一段作为保底方案
- 教师准备路演计时器手机倒计时或投屏计时器3分钟
- 学生资源:上节课的个人主页 HTML 文件 + 收到的同学建议
**教师备课体验任务:**
> 备课前,教师必须亲自完成以下操作:
>
> 1. 用自然语言分别实现 hover、滚动渐入、Tab 切换三种交互效果,记录成功率并准备保底代码片段
> 2. 做一个"半成品 vs 完成品"的 before/after 对比页面,和一个"特效堆满但杂乱"的反面教材
> 3. 练习一遍 3 分钟路演,掐好时间
> 4. 确认校区每台电脑能正常打开上节课的学生项目文件
---
### 4. 教学流程
---
**第一幕:联系 (Connect) — 10分钟** 🔗
*本幕目标:回顾上节课的成果,用 before/after 对比激发"从还行到惊艳"的升级欲望,明确今天的最终目标*
**【环节】上节课回顾 (4分钟)**
**师:** 上节课我们做了一件很重要的事——每个人都有了自己的个人主页。谁来说说,上节课你的页面上有哪些板块?
**生:** (预期:自我介绍、兴趣爱好、喜欢的东西、梦想……)
**师:** 很好。上节课我们做了三件事:第一,用内容清单把你想放的信息都整理出来了。第二,用你自己创建的"个人主页设计师"智能体生成了第一版页面。第三,你们互相看了对方的页面,每个人都收到了同学的建议。
**师:** 现在,把你上节课收到的同学建议拿出来。纸条也好、笔记也好——找到了吗?
(等待学生翻找。如果有人忘带,教师可以说"没关系,一会儿我们有办法"
**【环节】情景导入 (6分钟)**
**师:** 在看你的建议之前,先看一个东西。
(教师投屏,左右分屏展示同一个个人主页的两个版本)
**师:** 左边,是上节课结束时的样子。右边,是经过今天这样的优化之后的样子。大家看看区别在哪里?
(逐一指出差异)
- 左边:鼠标放上去没反应 → 右边:鼠标悬停时照片放大、按钮变色
- 左边:内容一股脑全显示 → 右边:往下滚的时候内容一段段渐入
- 左边:有一段自我介绍还是 AI 编的假内容 → 右边:全部替换成了真实信息
- 左边:配色有点杂 → 右边:统一了色调,整体更协调
**师:** 右边的页面,哪里让你觉得"这个更专业"?【诊断点:探测学生对"完成品质量"的直觉判断】【理解层】
**【分支A】若学生说"鼠标放上去会动"或"有动画"**
**师:** 你注意到了交互效果!页面不是一张静态的海报——它能"动"、能"回应"你的操作。今天我们就学怎么加这种效果。
**【分支B】若学生说"配色更好看"或"更整齐"**
**师:** 你看出了设计一致性!就像穿衣服要搭配一样,页面的颜色、字体、间距也要搭配。今天有一个环节专门帮你检查这个。
**【分支C】若学生说不上来具体区别**
**师:** 没关系,我帮你总结——右边比左边多了三样东西:**会动的效果**、**统一的风格**、**真实的内容**。今天我们就要把你的页面也升级到这个水平。
**师:** 今天的目标很明确:你的个人主页要**从半成品变成完成品**——一个你愿意让全校同学、让你爸妈、让全世界看到的作品。而且——你们的作品最终会发布到我们公司的网站上,真的有人会看到!
**师:** 继续用你上节课创建的"个人主页设计师"智能体。打开 Trae打开你的项目切到 Solo Coder我们开始
---
**第二幕:建构 (Construct) — 60分钟** 🛠️
---
**【分段一:响应同学建议】(15分钟)**
*本段重点:根据真实的用户反馈有针对性地改进作品,建立"用户视角"意识*
**预设误概念:**
- M2: 同学的建议不靠谱,还是自己的好
- M4: 发布前不用检查,能打开就行
**讲解与演示 (5分钟):**
**师:** 你们手上有上节课收到的同学建议。现在我问你们一个问题——你觉得同学的建议有用吗?【诊断点:检测学生是否重视同伴反馈】【理解层】
**【分支A】若学生说"有用,他说的确实是个问题"**
**师:** 很好!你已经有产品思维了。自己觉得好不算好,**别人觉得好才是真的好**。因为你的页面不是做给自己看的——是做给别人看的。
**【分支B】若学生说"我觉得我的已经很好了,不用改"**
**师:** 我理解。但你想一个场景——你写了一篇作文,自己读觉得很棒。但老师帮你看了之后,指出了三个你没注意到的问题。是你眼瞎了吗?不是——是因为**写的人和看的人视角不一样**。同学的建议就是"读者视角"。
**【分支C】若学生建议纸条找不到了**
**师:** 没关系。你现在请旁边的同学看看你的页面给你说一个优点和一个可以改进的地方。30秒搞定。
**师:** 我来演示怎么把建议变成修改。假设我收到的建议是"你的自我介绍太长了,看不完"。
(教师打开自己的个人主页项目,在 Solo Coder 中使用"个人主页设计师"智能体)
**师:** 我在 Solo Coder 里这样说:
```
同学反馈说自我介绍太长了。请把自我介绍板块改成折叠式:
默认只显示前两句话,下面有一个"展开阅读更多"的按钮,
点击后展开显示全部内容。
```
(等待 AI 生成Accept预览效果
**师:** 看!建议变成了一个具体的改进。注意我做了什么——我不是简单地说"把自我介绍改短",而是想了一个**具体的解决方案**"用折叠来解决太长的问题"。这就是把模糊的反馈变成明确的修改需求。
**师:** 现在看你的建议纸条,选 1-2 条你觉得有道理的。然后想想:**怎么改能解决这个问题?** 想好了就告诉你的智能体。
**学生实践 (8分钟):**
学生操作步骤:
1. 看同学建议,选 1-2 条要改的
2. 想出具体的解决方案
3. 用 Solo Coder + 智能体描述修改
4. Accept → 预览 → 确认效果
5. 改完一条再改下一条
**师:** 8分钟。记住——**改完一条验证一条**,不要一口气说五条修改。开始!
(教师走动观察)
(教师走动重点关注:)
- 谁在认真参考建议?谁跳过了建议直接自己改?
- 修改描述是否具体?"改好看点" vs "把标题颜色从红色改成深蓝色"
- 是否改完一条就验证效果?
- 有没有学生建议纸条丢了又不好意思问同学?主动引导
**快学生应对:** 两条建议都改完了?看看还有没有其他建议可以采纳。或者自己再审视一遍页面,找一个你不满意的地方改掉。
**慢学生应对:** 如果学生不知道怎么把建议变成修改需求,教师帮他翻译一条:"同学说你的颜色太花了——你可以跟 AI 说'请把整个页面的配色统一成蓝白色调'"。
**进度同步 (2分钟):**
**师:** 根据同学建议成功修改了至少一处的举手!
**师:** 有没有人改完之后觉得"同学说得确实有道理"的?【诊断点:检测学生是否通过实践认识到同伴反馈的价值】【理解层】
**【分支A】若学生说"确实,改完好多了"**
**师:** 对吧?别人的眼睛就是你的镜子。以后做任何作品,都找人帮你看看再发布。这叫**用户测试**——专业的产品团队每天都在做这件事。
**【分支B】若学生说"我觉得改之前也挺好的"**
**师:** 那也没关系。有些建议你可以采纳,有些可以保留自己的想法。重要的是你**认真考虑过**了,不是没想就拒绝。
---
**【分段二:交互升级】(20分钟)**
*本段重点:学会用自然语言为页面添加交互效果,这是本课最核心的新技能*
**预设误概念:**
- M1: 加特效越多越好,越炫越好
- M3: 交互效果很难,我做不了
**讲解与演示 (7分钟):**
**师:** 现在进入今天的重头戏——**交互效果**。
**师:** 什么是交互效果?简单说就是——你的页面不只是一张"海报",它能"动"、能"回应"你的操作。就像游乐场和风景画的区别——风景画你只能看,游乐场你可以坐旋转木马、滑滑梯、碰碰车。
**师:** 我来演示几种适合个人主页的交互效果,你看看哪个你喜欢。
**效果一hover 悬停**
**师:** 第一种——hover 悬停。就是鼠标放到某个东西上面的时候,它会有变化。
(教师在 Solo Coder 中输入)
```
给页面上所有的兴趣爱好卡片加一个 hover 悬停效果:
鼠标放上去的时候,卡片轻微放大到 1.05 倍,同时加一个淡淡的阴影,
移开后恢复原样。过渡动画要平滑,大概 0.3 秒。
```
Accept → 预览 → 鼠标悬停演示)
**师:**鼠标放上去——卡片稍微放大了、出现了阴影。移开——恢复。就这一句话AI 就做出来了。
**效果二:滚动渐入**
**师:** 第二种——滚动渐入。就是你往下滚页面的时候,内容不是一下子全显示,而是一段段"淡入"出现。
```
给页面加一个滚动渐入效果:
每个板块在用户滚动到它的位置时,从下方淡入出现。
还没滚到的板块先隐藏,滚到的时候用 0.6 秒的动画从下往上滑入并淡入。
```
Accept → 预览 → 滚动演示)
**师:** 看!我往下滚——内容一段段出来,是不是比一股脑全显示更有节奏感?
**效果三Tab 标签切换**
**师:** 第三种——Tab 标签切换。如果你的页面内容很多,可以分类放在不同的标签页里。
```
把"我的兴趣爱好"板块改成 Tab 标签切换的形式:
上面有三个标签按钮:"运动"、"音乐"、"阅读"
点不同的标签显示对应类别的内容。
当前选中的标签有高亮效果,切换时有淡入过渡动画。
```
Accept → 预览 → 点击切换演示)
**师:** 三种效果,我每种只用了**一句自然语言**就让 AI 做出来了。跟之前改颜色、改文字是一样的流程——**说人话就能做**。
**师:** 但是!注意一个重要原则——【诊断点:主动暴露 M1 误概念】
**师:** 你觉得这三种效果,我应该全部都加到页面上吗?【识别层】
**【分支A】若学生说"全加上更酷"**
**师:** 我来给你看一个"全加上"的反面教材。(打开一个特效堆满的页面——到处在闪、到处在动、眼花缭乱)这个好看吗?
**生:** (预期:太花了/看不清/眼睛累)
**师:** 对!好的设计是**克制的**。就像穿衣服——你不会把衣柜里最亮的10件全穿上吧选 1-2 个最适合你页面的效果就够了。**3 个精致的效果胜过 10 个花哨的。**
**【分支B】若学生说"选适合的就行"**
**师:** 你已经有设计师思维了!没错,选 1-2 个**最适合你内容**的效果。不是越多越好,而是越合适越好。
**师:** 现在你来选。你觉得你的个人主页最适合哪种交互效果?选 1-2 个就好。
**学生实践 (10分钟):**
学生操作步骤:
1. 从 hover 悬停、滚动渐入、Tab 标签切换中选 1-2 个
2. 参考教师演示的提示词,根据自己页面的情况修改
3. 用 Solo Coder + 智能体实现
4. Accept → 预览 → 验证效果
**师:** 10分钟。选 1-2 个效果,不要贪多。做完一个验证一个。如果效果不对,跟 AI 说"刚才加的交互效果有问题XX不正常请修复"。开始!
(教师走动观察)
(教师走动重点关注:)
- 学生选了几个效果?超过 2 个的要提醒"先做好这两个再说"
- 交互效果是否正常工作hover 不生效?滚动动画卡顿?
- 有没有学生加了效果后页面布局乱了?引导描述问题让 AI 修复
- 谁还在犹豫不知道选哪个?建议从 hover 悬停开始——最简单、成功率最高
**快学生应对:** 两个效果都做好了?试试加一个更有创意的——比如"点击我的头像,弹出一段自我介绍动画"。
**慢学生应对:** 如果学生的智能体加交互效果失败了,让学生直接在 Solo Coder 里说(不通过智能体):"给页面上的卡片加一个鼠标悬停放大效果放上去放大到1.05倍加阴影0.3秒过渡"。如果还是失败,教师给出保底代码片段让 AI 整合。
**进度同步 (3分钟):**
**师:** 成功给页面加了交互效果的举手!加了一个的?两个的?
**师:** 谁来演示一下你加了什么效果?
(选 1-2 个学生投屏快速演示,每人 30 秒)
**师:** 你们发现了吗?加了交互效果之后,页面的"高级感"一下子就上来了。这就是交互的魔力——页面从一张"图片"变成了一个"产品"。【诊断点:检测学生是否理解交互效果对产品品质的提升】【理解层】
**【分支A】若有学生加了太多效果导致页面卡或乱**
**师:** 看,这位同学的页面加了好多效果——但看起来怎么样?有点乱对吧?这就是我刚才说的——**克制**。现在跟 AI 说"请去掉XX效果只保留XX效果",简化一下。
**【分支B】若有学生效果做得很精致**
**师:** 大家看——这位同学只加了一个 hover 效果,但做得很精致,跟页面风格完全统一。**少而精,胜过多而杂。**
---
**【分段三:最终打磨】(12分钟)**
*本段重点:用检查清单进行系统性最终审查,建立"发布前必检查"的产品意识*
**预设误概念:**
- M4: 发布前不用检查,能打开就行
- M1: 加特效越多越好(残留)
**讲解与演示 (3分钟):**
**师:** 现在你的页面有内容、有交互了。但我问你——它能发布了吗?
**师:** 我给你看一个"能打开但不能发布"的例子。
(教师投屏一个有明显问题的页面)
**师:** 看这个页面——它能打开吗?
**生:** (预期:能)
**师:** 能打开。但你仔细看——
(逐一指出问题)
- 这里写着"在此输入你的姓名"——还是 AI 生成时的占位文字,没换成真名
- 这个板块是空的——"我的作品展示"下面什么都没有
- 标题用了蓝色,按钮用了绿色,卡片用了红色——三种颜色打架
- 这个 emoji 在这台电脑上显示不出来,变成了一个方框
**师:** 如果这个页面直接发布出去,别人看到会怎么想?
**生:** (预期:不认真/没做完/很业余)
**师:** 对。"能打开"和"能发布"是两回事。就像交作业——你不会把草稿纸交上去吧?发布之前,要做一次**完整检查**。
(教师投屏发布前检查清单)
```
📋 发布前检查清单:
1. ✅ 所有文字都是真实的我的信息(没有 AI 编的假内容、没有占位文字)
2. ✅ 图片/emoji 都能正常显示(没有方框、没有裂图)
3. ✅ 每个板块都有内容(没有空白区域)
4. ✅ 配色看着舒服、统一(不刺眼、不打架)
5. ✅ 交互效果正常工作hover 有反应、动画能播放)
6. ✅ 从头到尾浏览一遍,没有明显问题
```
**师:** 这六条,一条条检查。发现问题就修。
**学生实践 (7分钟):**
**师:** 现在对照清单,逐条检查你的页面。发现问题就用 Solo Coder 修。
(教师投屏保持检查清单)
**师:** 9分钟。按清单一条条过。特别注意第一条——翻遍你的页面有没有还是 AI 编的假信息?比如"热爱学习,成绩优异"——这是你自己写的还是 AI 瞎编的?如果是瞎编的,现在改成真实的。开始!
(教师走动观察)
(教师走动重点关注:)
- 有没有学生页面上还有明显的 AI 占位文字?提醒他换成真实内容
- 配色是否统一?如果一个页面用了五六种颜色,建议说"请把整个页面的配色统一成XX色调"
- 交互效果是否正常hover 没反应的、动画卡住的,帮学生排查
- 有没有空白板块?"我的作品展示"下面什么都没有的,要么填内容要么删板块
**快学生应对:** 六条都过了?帮旁边同学检查一遍——你帮他看,往往能发现他自己看不到的问题。
**慢学生应对:** 如果问题太多改不完优先改第1条假内容和第3条空白区域。配色不统一可以用一句话修"请把整个页面的配色统一成蓝白色调,保持简洁。"
**进度同步 (2分钟):**
**师:** 六条清单全过的举手?过了四条以上的?
**师:** 检查的时候有没有发现"天哪原来这里还有问题"的?【诊断点:检测学生是否建立了"发布前检查"的意识】【应用层】
**【分支A】若学生说"发现了好几个没注意到的问题"**
**师:** 这就是检查清单的价值!你以为做完了,其实还有很多细节。专业的产品经理发布前要过几十条检查项呢。我们六条已经是简化版了。
**【分支B】若学生说"我的没什么问题"**
**师:** 很好!说明你上节课就做得很仔细。但养成习惯——每次发布前都过一遍清单,哪怕你觉得没问题。因为有些问题你看习惯了就注意不到了。
---
**【分段四:路演准备与练习】(13分钟)**
*本段重点:学会结构化表达自己的作品和设计思路,为第三幕路演做准备*
**预设误概念:**
- M5: 路演就是把页面打开给大家看
**讲解与演示 (5分钟):**
**师:** 页面做好了、检查过了。最后一步——你要**把它讲给别人听**。
**师:** 这就像你参加画展。你不会只把画挂在墙上就走了吧?你得站在画旁边,跟观众说:我为什么画这幅画?我想表达什么?哪里是我最得意的地方?
**师:** 这叫**路演**。不是"展示"——展示是给你看。路演是给你看,还要**讲给你听**。
**师:** 路演 3 分钟。我给你一个标准结构:
(教师投屏路演结构)
```
🎤 路演结构3分钟
1⃣ 开场15秒我是谁这是我的个人主页
"大家好我是XXX今天给大家看我的个人主页。"
2⃣ 亮点展示90秒最得意的 2-3 个板块,讲设计思路
"我最喜欢的是这个板块——我放了XXX因为我觉得……"
"这里我加了一个悬停效果,鼠标放上去会……"
3⃣ 技术分享45秒用了什么方法踩了什么坑
"做这个页面我用了自己设计的智能体,规则是……"
"中间有一次翻车了——我加了XX效果结果页面乱了后来我跟AI说……"
4⃣ 结尾30秒我最想让大家记住的一点
"如果你只能记住一个关于我的事情,我希望是……"
```
**师:** 我来做一遍示范。
(教师用自己的个人主页做 3 分钟路演示范——完整走一遍四个部分,语气自然,有停顿有互动)
**师:** 看到了吗?我不是在念稿——我是在**讲故事**。讲我为什么做这个、我怎么想的、我遇到了什么问题。这比单纯展示有意思多了。【诊断点:检测学生是否理解路演和单纯展示的区别】【应用层】
**【分支A】若学生说"要讲为什么这样设计"**
**师:** 对!"为什么"比"是什么"重要十倍。观众看到你的页面能自己判断好不好看,但他们不知道你为什么这样设计——这才是路演要讲的。
**【分支B】若学生看起来有点紧张**
**师:** 别紧张!你就当跟好朋友聊天——"来看看我做的东西,这里我觉得特别酷因为……"。不需要背稿,用你自己的话说就行。
**学生实践 (8分钟):**
**师:** 现在准备你的路演。两件事:
1. **花 3 分钟列提纲**——在纸上或脑子里想好:开场说什么、展示哪 2-3 个亮点、分享什么技术经验、结尾说什么
2. **花 5 分钟跟同桌互练**——你讲给他听他讲给你听。互相计时3 分钟。超了就帮对方砍内容,不够就帮对方加内容
**师:** 提纲不用写得很详细——几个关键词就行。重要的是**练一遍**。第一次讲永远比你想象的长或短,练过一次就知道怎么控制时间了。开始!
(教师走动观察)
(教师走动重点关注:)
- 有没有学生只准备了"打开页面给你看"而没有准备讲解?提醒他路演结构
- 互练时有没有超时的?帮他们砍内容——"3 分钟,只讲最精华的 2 个亮点"
- 有没有学生完全不知道讲什么?给他一个起手句:"我的页面主题是XX我最得意的板块是XX因为……"
**进度同步 (2分钟):**
**师:** 跟同桌互练过一遍的举手?很好。
**师:** 练完之后,你觉得 3 分钟够不够?【识别层】
**生:** (预期:有人说够/有人说不够/有人说太长了)
**师:** 3 分钟很短——所以要精挑细选,只讲最精华的。如果你什么都想讲,反而什么都讲不清楚。**少就是多。** 好,准备好了,下面就是正式路演!
---
**第三幕:反思 (Contemplate) — 15分钟** 🤔
*本幕目标正式路演展示通过结构化互评巩固表达力和审美力。时间比标准多5分钟因为这是双课项目的总结*
**成果展示——路演 (10分钟)**
**师:** 现在是激动人心的路演时间!我们选 4 位同学上台,每人 3 分钟。
(选 4 位学生。选人策略尽量涵盖不同风格——1个设计精致的、1个交互效果做得好的、1个内容很有个性的、1个迭代过程值得分享的
**师:** 台下的同学,每个路演结束后,你要准备两样东西:
- **一个赞**:你觉得他的页面或者路演哪里最棒
- **一个建议**:如果他还能再改一轮,你觉得可以改什么
(第 1 位学生路演——3 分钟)
**师:** (路演结束后)谢谢!台下谁来说——你最喜欢他页面的哪个地方?
**生:** (收集 1-2 个"赞"
**师:** 有没有改进建议?
**生:** (收集 1 个建议)
**师:** (教师点评——重点不只看效果,更表扬设计思路和迭代过程)
例如:"我特别喜欢的是XX同学讲了他加滚动动画的过程——第一次翻车了他跟 AI 说了什么修好的。这个**解决问题的过程**比最终效果更值得学习。"
(依次完成 4 位学生的路演 + 互评 + 教师点评。每人约 2.5 分钟路演 + 互评)
> **拍照发群:** 拍学生路演的照片和作品截图,配文字"AI编程创新课第5课同学们完成了自己的个人主页并进行了产品路演每一个页面都独一无二。"发家长群。
**互评总结 (5分钟)**
**师:** 四位同学的路演都很棒。我来总结一下今天看到的几个共同的亮点:
(根据实际情况总结,以下为预设方向)
**师:** 第一,你们的页面**每一个都不一样**——有人走简约风、有人走炫酷风、有人内容为王。这说明什么?说明你们不是在"照着模板做",而是在**表达自己**。这就是个人主页的意义。
**师:** 第二,交互效果大家都加上了。但我注意到——做得好的同学不是效果最多的,而是**效果最合适**的。XX同学只加了一个 hover但跟他的页面风格完美统一。**克制是一种高级的审美能力。**
**师:** 第三,路演讲得好的同学有一个共同点——他们不只是说"你看我的页面",而是说"我为什么这样做"。**讲设计思路比展示结果更有说服力。**
---
**第四幕:延续 (Continue) — 5分钟** 🚀
*本幕目标:收集作品文件,总结双课项目的完整流程,引导方法论迁移*
**收集作品 + 发布说明 (1分钟)**
**师:** 现在最重要的一步——请所有人把你的 index.html 文件提交给我。
说明提交方式拷贝到教师U盘 / 上传到指定文件夹 / 根据校区实际情况调整)
**师:** 你们的个人主页会发布到我们穹狼科创的网站上。**真的会有人看到!** 你的爸妈、你的朋友、其他校区的同学——都能看到你做的页面。所以,最后确认一遍——你页面上的所有信息都是你愿意公开的吗?有没有不想让别人看到的内容?有的话现在改。
**抽象总结 (2分钟)**
**师:** 我们用两节课完成了一个完整的项目。来回顾一下整个流程:
(投屏)
```
两课完整流程:
📝 第4课内容清单 → 设计智能体 → 生成第一版 → 填入真实内容 → 视觉调整 → 收集同学建议
🚀 第5课响应建议 → 交互升级 → 最终打磨 → 路演展示 → 发布
核心方法论:
先规划 → 再创建 → 再打磨 → 再发布
```
**师:** 这套流程——"先规划、再创建、再打磨、再发布"——除了做网页,还能用在哪里?【诊断点:检测学生能否将方法论迁移到其他场景】【迁移层】
**【分支A】若学生说"做PPT"/"写作文"/"画画"**
**师:** 完全正确做PPT也是先想好讲什么规划→做出来创建→改好看打磨→上台讲发布。你已经掌握了一个通用的创作方法。
**【分支B】若学生想不出来**
**师:** 想想你上周交的作文——你是不是先想好写什么、再写初稿、再修改、最后誊抄交上去?其实就是同一套流程。这个方法用在任何创作上都好使。
**5分钟挑战 + 下节预告 (2分钟)**
**师:** 本周5分钟挑战给你的个人主页加一个新板块——"给未来的自己的一封信"。写一段话给10年后的自己再给这个板块加一个特别的视觉效果。具体要求课后看群里发的说明。
**师:** 下节课我们进入新的阶段。前五节课你们学了 Trae 的操作、三种模式、迭代、智能体设计、个人主页项目。接下来要挑战更有野心的东西——不只是展示型页面,而是**有真正功能的应用**。
**师:** 具体做什么?下节课揭晓。这周好好欣赏你发布在网站上的个人主页——那可是你的第一个真正发布的作品。下节课见!
---
### 5. AI助教使用指南
**教师演示用提示词:** 见教学流程分段二中的三段演示提示词hover、滚动渐入、Tab 切换),可直接使用。
**学生保底提示词(智能体不好用时,直接在 Solo Coder 里说):**
```
给我的页面加一个简单的效果:鼠标放到卡片上的时候卡片稍微放大,移开恢复。
```
```
给页面加滚动动画:往下滚的时候内容从下面淡入出现。
```
```
把一个板块改成标签页切换,点不同按钮显示不同内容。
```
**保底代码片段(教师验证过的交互效果代码):**
如果 AI 生成的交互效果不生效,教师可以让学生这样说:
```
请把下面的 CSS 代码加到我的页面的 <style> 标签里,
并给所有需要 hover 效果的卡片加上 class="hover-card"
.hover-card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.hover-card:hover {
transform: scale(1.05);
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
}
```
**路演话术模板(给不知道怎么讲的学生):**
```
"大家好,我是[名字]。这是我的个人主页。
我的主题是[XX],因为我觉得[XX]最能代表我。
我最喜欢的板块是[XX],你们看——[演示操作]。
这里我加了一个[XX效果],鼠标放上去会[XX]。
做这个页面的时候,我踩了一个坑——[XX]
后来我跟 AI 说[XX],就修好了。
如果你只记住一件关于我的事,我希望是:[XX]。谢谢大家!"
```
---
### 6. 教师指南
**本课技术备注(含国产模型成功率):**
| 交互效果 | 技术原理 | 成功率 | 备注 |
| --------- | --------- | -------- | ------ |
| hover 悬停 | CSS `:hover` 伪类 + `transition` | 95%+ | 最稳,优先推荐 |
| CSS 过渡/动画 | `transition`A→B/ `animation`(关键帧) | 85-90% | 纯 CSS不需 JS |
| 滚动渐入 | JS Intersection Observer API | 75% | 偶尔观察器配置不对 |
| Tab 标签切换 | JS 控制显隐 | 70% | 偶尔点击不切换 |
| 图片轮播 | JS 定时器 + 索引管理 | 60% | 失败就退回 hover |
**学生翻车时的应对顺序:** 让 AI 修复 → 换更简单的效果 → 用保底代码片段 → 去掉效果恢复原状
**发布流程说明:**
- 学生提交 HTML 文件后,由教师/技术人员统一部署到公司网站
- 建议课后检查一遍所有学生的 HTML 文件:确认无不适当内容、文件能正常打开
- 发布后将链接发到家长群,配截图和说明文字
**常见问题 FAQ**
| 学生问题 | 建议回答 |
|---------|---------|
| "交互效果加了但没反应" | 先检查——鼠标有没有放到对的元素上?有时候 hover 加在了外层容器而不是卡片本身。跟 AI 说"hover 效果加了但没反应,请检查是不是选择器写错了" |
| "页面在别的电脑上显示不一样" | 正常现象不同电脑的屏幕大小、字体、emoji 样式都不一样。重要的是主体内容和布局没问题。以后我们会学"响应式设计"来解决这个问题 |
| "我想加背景音乐" | 可以试试,但浏览器默认会阻止自动播放音频。可以加一个"播放音乐"按钮让用户手动点击播放。如果实现有困难就暂时不加 |
| "路演 3 分钟太短了讲不完" | 说明你的内容很丰富!但路演的核心是"精选最亮的 2-3 个点",不是面面俱到。练习砍内容——只留最想让人记住的 |
| "我不想在网站上发布" | 完全尊重你的意愿。如果有隐私顾虑,可以把真名换成昵称,或者不发布个人照片 |
| "同学的建议我都不同意" | 这很正常设计没有标准答案。但建议你至少认真思考一下——如果3个人都说"配色太花了",可能真的值得考虑 |
**课堂风险预案:**
- 如果学生上节课的智能体找不到了:直接在 Solo Coder 里操作,不通过智能体。教学目标不变
- 如果学生上节课的项目文件丢失了:用 Builder 快速重建一个基础个人主页5分钟使用保底提示词跳过分段一直接进入分段二
- 如果路演时间不够(学生太多):只选 3 位路演,其余学生课后录 3 分钟视频提交
- 如果AI服务不可用让学生专注做路演准备和互评交互效果留到下节课补
---
### 7. 5分钟日常AI挑战
**本周挑战:**
> **"给未来的自己的一封信"**
>
> **操作步骤:**
> 1. 打开你的个人主页项目
> 2. 用 Solo Coder 加一个新板块,标题是"给未来的自己"
> 3. 写一段话——你想对10年后的自己说什么梦想、期望、现在的心情……
> 4. 给这个板块设计一个特别的视觉效果(信封打开动画?手写字体?老照片风格?你来决定)
> 5. 截图保存最终效果
>
> **评价标准:**
> - 基础:成功加了新板块,内容是自己写的真心话
> - 进阶:视觉效果跟页面整体风格统一,不突兀
> - 挑战:让家人读你写的信,记录他们的反应(文字或截图)
---
### 8. 拓展任务
**拓展一(推荐):手机端适配**
> 用外部浏览器打开你的个人主页,然后按 F12 打开开发者工具,切换到手机视图。你的页面在手机上好看吗?如果不好看,跟 AI 说"请让我的页面在手机上也能正常显示,做响应式适配"。截图对比电脑版和手机版的效果。
**拓展二(挑战):为别人做个人主页**
> 找一个家人或朋友,帮他/她做一个个人主页。你需要先"采访"他——了解他的兴趣、梦想、特长,然后用你学到的全部技能(内容清单→智能体→生成→迭代→交互→检查)做出一个完整的页面。做完后让他看,记录他的反应。这是一次完整的"为用户做产品"的体验。

View File

@@ -0,0 +1,669 @@
---
课时: 6
主题: 魔幻俄罗斯方块(上)— Plan Mode 先行
核心能力: [提问力, 拆解力]
核心工具: [Kimi 2.5]
时长: 90分钟
透明化层级: 过程层
适用路线: AICODE-06
---
### 1. 课程目标
**知识目标:**
- 理解「Plan Mode计划模式」的概念无论做什么都要先开启计划模式把需求写清楚再执行
- 理解「需求质量 = 输出质量」需求写得越详细AI 执行越准确;需求写不清楚,执行过程中就会出问题
- 理解需求是迭代的过程,不是一次写完就能用
**能力目标:**
- 能通过「侦探模式」分析一个游戏的规则,并用文字描述出来(拆解力)
- 能用 AI 对话主动「压力测试」自己的需求文档,发现描述漏洞(提问力)
- 能在看到生成结果后,定位是哪条需求没说清楚,修改并重新生成(共创力)
**情感目标:**
- 建立「先开 Plan Mode再动手」的习惯——这是课程中所有项目的第一步不可跳过
- 对「自己也能做出一个游戏」产生真实的兴奋感
- 建立「结果不对 = 需求没说清楚」而不是「AI 不行」或「我不行」的归因习惯
---
### 2. 核心概念与误概念预设
**核心概念认知层级:**
| 概念 | 学生类比 | 认知层级 |
|------|---------|---------|
| Plan Mode计划模式 | 盖房子前先画图纸——没有图纸直接开工,盖到一半发现方向错了,拆掉重来比画图纸贵十倍 | 理解层 |
| 需求文档 | 装修前给师傅的设计图——口头说「弄好看点」和给一张详细图纸,结果完全不同 | 理解层 |
| 需求压力测试 | 考试前找同学互出题——让别人挑毛病比自己检查更有效 | 应用层 |
| 结果溯源 | 菜做咸了,往回找:是盐放多了,还是酱油放多了?找到根源才能改 | 应用层 |
| 新窗口原则 | 让同一个同学既写作文又给自己改错,他永远改不出来——必须换一个人来审。审核、测试、评审全部要开新窗口,让「新的 AI」来做才不会被原来的上下文带跑偏 | 应用层 |
**典型误概念表:**
| 编号 | 误概念 | 正确认知 | 激发策略 |
|------|--------|---------|---------|
| M1 | 直接告诉 AI「帮我做一个俄罗斯方块」就够了 | 不写需求文档AI 会自己猜所有细节,结果可能跟你想的完全不同 | 课堂实验:先不写需求文档直接生成,再写了需求文档生成,对比两个结果的差距 |
| M2 | 需求文档写完就是写好了,直接提交 | 写完只是第一步,要经过压力测试才能发现漏洞 | 用「压力测试」提示词,让 AI 提出学生自己没想到的问题 |
| M3 | AI 做出来的结果不对是 AI 的问题 | 结果不对说明需求里有没说清楚的地方 | 「你的需求文档里有没有写这一条?」——让学生自己发现根本没写 |
| M4 | 需求文档要写得很长很详细 | 精准 > 冗长。需求要「可测试」:能说出什么情况下算做对了 | 「你这条需求,我怎么知道 AI 做没做对?」 |
| M5 | 先动手做,遇到问题再想 | 跳过 Plan Mode 直接做,出了问题再改来改去,总时间反而更长 | 对比两种路径:「跳过计划 → 做 → 改 → 改 → 改」vs「Plan Mode → 做 → 小改」 |
| M6 | 需求审核是在挑代码的问题 | 需求审核阶段只审需求本身——哪里没说清楚、哪里有歧义,跟代码无关 | 「AI 现在还没写一行代码,怎么可能审代码问题?」 |
| M7 | 在同一个对话框里写需求、审核、执行、迭代全部搞定 | 这叫「上下文污染」——AI 会被之前的对话带着走,审核时会偏向为自己生成的内容辩护。正确做法:写需求在一个窗口,审核开新窗口,执行再开一个窗口 | 演示:在写了需求的窗口里让 AI 审核自己AI 的回答会非常保守换新窗口审核AI 会找出更多问题 |
---
### 3. 教学准备
**工具与环境:**
- 每台电脑已登录 Kimi 2.5,网络正常
- 每台电脑可以正常运行 HTML 文件(浏览器打开)
- 投影可切换至任意学生屏幕
**教学资源:**
- 教师准备:一个真实可玩的俄罗斯方块网页版(课堂演示用,备用链接)
- 教师准备「压力测试」提示词见第5节投屏展示
- 教师准备一份「没写需求文档」直接生成的俄罗斯方块演示提前生成好展示各种「AI 自作主张」的细节)
- 教师准备Level 0 需求文档模板见第5节发给学生
- 学生资源:无需提前准备
**教师备课体验任务:**
> 备课前,教师必须亲自完成以下操作:
>
> 1. 用一句话「帮我做一个俄罗斯方块」提交给 Kimi保存生成结果记录哪些细节是 AI 自己决定的
> 2. 写一份 Level 0 需求文档,走完「压力测试 → 完善 → 提交生成 → 验收 → 发现问题 → 溯源 → 修改 → 第二版」全流程,记录卡在哪里
> 3. 准备好 2-3 个「需求没说清楚」的典型例子(比如「消行规则写了但没写消多行的得分」)
---
### 4. 教学流程
---
**第一幕:联系 (Connect) — 10分钟** 🔗
*本幕目标:通过「侦探模式」玩游戏,让学生主动发现俄罗斯方块的规则;展示「一句话版」对比引出 Plan Mode 的价值,建立「先写需求再动手」的第一印象*
**【环节】侦探模式导入 (10分钟)**
**师:** 今天我们要做一个游戏。但在做之前,先玩两分钟。
【投屏展示俄罗斯方块,或让学生在自己电脑上打开】
**师:** 你们的任务不是拿高分,而是当侦探——把这个游戏所有的规则找出来,写在纸上。你能找到多少条就写多少条。两分钟,开始。
> 教师走动观察:学生在记录哪些规则,是停在「方块会下落」这种表层,还是注意到了「碰到边界会停」「满行才消除」这类细节规则。不要打扰,只观察。
**师:** 好,停。谁来说一条规则?
**生:** (预期:方块会往下落)
**师:** 好,这是一条。写得比较浅,但没错。还有呢?
**生:** (预期:可以左右移动,可以旋转)
**师:** 对,左右移动、旋转。还有没有发现更多的?
**生:** (预期:满一行就消掉)
**师:** 消行——这个很关键。消了之后上面的积木会怎样?
**生:** (预期:往下掉 / 往下移一行)
**师:** 对,会往下补。还有吗?积木碰到边界的时候会怎样?
**生:** (预期:停下来,不能再移了)
**师:** 对,碰到边界就停,不能移出去。那碰到底部呢?
**生:** (预期:就固定在那里了)
**师:** 对,固定住,不动了。那什么时候游戏结束?
**生:** (预期:积木堆到顶了就结束)
**师:** 好——你们刚才说出来的这些,就是俄罗斯方块的基本规则。你们注意没有——这些规则,全是游戏的「需求」。每一条都是「这个游戏应该怎么运行」。
【识别层:让学生意识到「游戏规则 = 需求描述」】
**师:** 现在我问一个问题——如果我让 AI 帮我做这个游戏,我直接说「帮我做一个俄罗斯方块」,你们觉得 AI 能做出来吗?
**生:** (预期:能!/ 应该能?)
**师:** 我们来试试。
【投屏展示教师提前生成的「一句话版」俄罗斯方块】
**师:** 做出来了。但你们来找找看,这个游戏里,有没有什么地方跟你心里想的「俄罗斯方块」不一样?
> 教师给 20 秒让学生仔细看屏幕,甚至可以让一个学生上来玩几秒
【诊断点:学生是否能发现 AI 自作主张的细节——比如得分规则、速度、旋转方向等】【识别层】
**【分支A】若学生发现了不同的地方**
**师:** 你发现了。为什么 AI 做出来跟你想的不一样?
**生:** (预期:因为我没有告诉它……)
**师:**你没说AI 就自己猜了一个。这就是我们今天要解决的问题。
**师:** 从今天开始,我们做任何项目,第一步都是同一件事——**开启 Plan Mode计划模式**。Plan Mode 只做一件事在动手之前把你的需求写得越详细越好。需求写得越详细AI 执行越准确需求写不清楚AI 就自己猜,猜出来的东西可能跟你想的完全不一样。
【识别层建立「Plan Mode 先行」的第一印象】
**【分支B】若学生觉得「都一样没问题」**
**师:** 那我问你——这个游戏消一行得多少分?消四行一次呢?
**生:** (去看)……好像是 100 分?
**师:** 你想要的是这个分数吗?
**生:** 我没想过……
**师:** 你没想过AI 就帮你决定了。Plan Mode 就是在动手之前,把所有「你没想过」的地方都想清楚,写在文档里,让 AI 按你想的来做。
---
**第二幕:建构 (Construct) — 65分钟** 🛠️
*本幕目标:完整走完 Plan Mode 三步——整理需求、需求审核、确认需求;再走完「提交生成 → 验收 → 结果溯源」的执行闭环;建立「需求驱动输出」的核心工作习惯*
---
**【分段一:开启 Plan Mode — 第一步:整理需求】(20分钟)**
*本段重点:学习 Plan Mode 三步总览,填写 Level 0 需求文档模板,建立「需求可测试性」的直觉*
**预设误概念:**
- 误概念 M1「帮我做俄罗斯方块」就够了不需要 Plan Mode
- 误概念 M4需求文档要写得很长很全面
**讲解与演示 (Teach & Demo): (7分钟)**
**师:** 现在我们正式开启 Plan Mode。Plan Mode 分三步走,我们先把三步过一遍,再动手。
**师:** **第一步——整理需求。** 把你想要的游戏写成一份需求文档。这一步的目标是:把你脑子里「模糊的感觉」变成「具体可以测试的文字」。就像设计师画图纸——不是说「大概这个形状」,而是标出每个尺寸。
**师:** **第二步——审核需求。** 写完不等于写好。让 AI 扮演一个审核工程师,专门找你文档里没说清楚的地方。它不帮你回答,只问问题——「这里如果出现了 XXX 情况,怎么处理?」
**师:** **第三步——确认需求。** 把审核工程师问的问题,一条条回答,补充进你的文档。这样你的需求文档才是「经过压力测试的版本」,可以提交给 AI 执行了。
**师:** 三步走完,才动手生成。听起来麻烦?我来给你算一笔账——
【投屏展示对比】
```
路径一(跳过 Plan Mode
说一句话 → AI 做出来 → 不对 → 让 AI 改 → 还不对
→ 再改 → 加乱了 → 从头来 → 又不对……(无限循环)
路径二Plan Mode 先行):
写需求文档 10分钟 → 压力测试 5分钟 → AI 做出来
→ 小幅微调 → 完成!
```
**师:** 路径一的「快」是假的快,路径二的「慢」是真的快。
**师:** 好,现在进入第一步。我给你们一个 Level 0 需求文档模板,里面有 5 个必须填的部分。
【投屏展示 Level 0 需求文档模板见第5节】
**师:** 这 5 个部分,每一个都是 AI「必须知道才能做对」的信息。我带你们看一遍每个字段是什么意思——
**师:** 第一部分「游戏区域」——游戏画面有多大10列×20行是经典俄罗斯方块的标准尺寸你可以改也可以保持默认。这条如果不写AI 可能给你做个 6×6 的迷你版,也可能做个 20×40 的超高版。
**师:** 第二部分「方块移动规则」——注意这里有一个关键字段:「碰到左右边界」。这条要写清楚。什么意思?就是方块走到最边上了,再按左/右键,会发生什么?
**生:** (预期:就停下来不动 / 不能再往那边移了)
**师:** 对。但如果你不写AI 可能给你做出一个方块可以穿过边界、从另一侧出来的效果——像贪吃蛇穿墙那种。你想要这个效果吗?
**生:** (预期:不想!/ 哦那要写清楚)
**师:** 所以每一条都得写。第三部分「消行规则」,这里特别要注意「验收标准」这个概念——每条需求,你都要能说出「什么样的结果算做对了」。
**师:** 比如「消行规则:横向填满一整行就消除」——我怎么测试这条需求 AI 做没做对?
**生:** (预期:把一行填满,看它有没有消掉)
**师:** 对。能被测试的需求,才是写清楚了的需求。反过来,「游戏体验要流畅」——这条能测试吗?
**生:** (预期:不能……怎么叫流畅?)
**师:**「流畅」是感受不是能测试的需求。如果要写你得改成「初始速度每800毫秒下落一格」——这才是可以测试的。
【理解层:建立「需求可测试性」的直觉】
**学生实践 (Practice): (11分钟)**
**师:** 现在开始填。5 个部分,把模板里的空格全填上。不确定的地方保持默认值也可以,但不能空着不填。
> 教师走动观察重点:
> - 是否有学生在「移动规则」里只写了「可以左右移动」,但没写「碰到边界怎么处理」?
> - 是否有学生在「消行规则」里写了消行但没写「消多行的得分是否不同」?
> - 是否有学生把「游戏体验流畅」这类不可测试的需求填进去了?
> - 这些漏洞先不要直接告诉学生,留给下一步「压力测试」去发现
**进度同步 (Checkpoint): (2分钟)**
**师:** 5个部分都填完的举手。
**师:** 好,先不着急提交。我来问你一个问题:按照你写的需求,一个不认识这个游戏的人拿到这份文档,能不能把一局游戏从头玩到结束——开始游戏、移动方块、消行、游戏结束?这个流程能跑通吗?
**生:** (预期:能?/ 好像……方块怎么消行没写清楚?/ 游戏结束之后怎么重新开始没写)
**师:** 对,就检查这一件事——游戏流程能不能跑通。先别想加什么特殊功能,那是后面的事。
【诊断点:学生的需求是否覆盖了「开始→玩→消行→升级→结束」这个基础流程】【理解层】
---
**【分段二Plan Mode 第二步:需求审核】(20分钟)**
*本段重点:用 AI 扮演审核工程师对需求做压力测试,区分「审需求」和「审代码」,发现自己看不见的漏洞*
**预设误概念:**
- 误概念 M2需求文档写完就可以直接提交执行了
- 误概念 M6需求审核是在挑代码的问题
**讲解与演示 (Teach & Demo): (5分钟)**
**师:** 需求文档写完了,进入 Plan Mode 第二步——需求审核。在做这一步之前,我要告诉你们一个非常重要的规则:**审核必须在新窗口进行。**
**师:** 为什么一定要新窗口?因为 AI 有一个特质——它不会主动承认自己的问题。你在同一个窗口里跟它说「帮我看看需求有没有问题」,它会被之前的对话「污染」,倾向于觉得「之前做得挺好的」。这叫**上下文污染**。
**师:** 打个比方——让同一个同学既写作文又给自己改错,他永远改不出大问题。必须换一个没看过你作文的同学来审,他才会发现真正的漏洞。换新窗口,就是换一个「什么都不知道」的新 AI 来看,它没有偏见,才会客观地找出问题。
**师:** 记住这条规则,以后做任何项目都要遵守:**写需求——一个窗口;审核需求——新窗口;执行生成——再新一个窗口。** 不同阶段,不同窗口,互不干扰。
【投屏展示「需求审核」提示词见第5节】
**师:** 现在的操作是:复制你的需求文档内容,打开一个全新的 Kimi 对话,把审核提示词粘贴进去。注意——审核的不是代码,是需求本身。审核工程师只负责问问题,不帮你回答,不帮你修改。
**师:** 我来演示一次。我把自己的需求文档粘贴进去,看 AI 会问什么问题。
【教师用自己的需求文档执行审核,投屏展示 AI 提出的问题列表】
**师:** 你们看AI 问了我这些问题——我们一起来分析分析:
**师:** 第一个问题:「方块旋转到边界时,如果旋转后的形状超出边界,是直接禁止旋转,还是自动向内移动?」——我的文档里有没有写这条?
**生:** (看文档)……没有……
**师:** 对,我没写。这个「踢墙旋转」问题我根本没想到。接着看——
**师:** 第二个问题:「如果方块落下时速度极快(玩家按了加速键),落地瞬间玩家还没松开方向键,方块会继续移动吗?」——我的文档里呢?
**生:** 也没写……
**师:** 再看第三个——「当积木消除多行时,是一行一行消,还是同时消除?视觉效果有没有要求?」——我写了得分,但消除的视觉顺序有没有说清楚?
**生:** 没说……
**师:** 最后一个——「游戏结束画面,除了「游戏结束」和得分,还需要「重新开始」按钮吗?」——这条很关键,游戏结束了玩家怎么重玩?
**生:** 对!要有重新开始。
**师:** 你看——四个问题,我的文档里一条都没写。这不是 AI 在刁难我,是它在帮我找漏洞。这就是「需求审核」的价值。
【识别层:直观感受审核的价值,澄清 M6 误概念——这些问题全是「需求层面」,跟代码无关】
**学生实践 (Practice): (12分钟)**
1. 学生把自己的需求文档粘贴进「压力测试」提示词,提交 Kimi
2. 把 AI 提出的问题复制下来
3. 逐条读问题,判断哪些问题「我真的没想到」,哪些「我已经写了但 AI 没看清楚」
4. 对「真的没想到」的问题,写上自己的回答,补充进需求文档
> 教师走动观察重点:
> - 学生是否对某个问题感到惊讶——「这个我真的没想过」是好的信号,主动靠近问「这个问题你打算怎么回答?」
> - 学生是否对 AI 的问题感到不耐烦「这些问题没必要」——这是 M6 误概念的表现,需要介入
> - 学生是否在逐条认真回答,还是全部跳过直接提交——走过去让学生读出一条来分析
**进度同步 (Checkpoint): (3分钟)**
**师:** AI 审核出来,问了你最意外的问题是哪条?谁来说说?
**生:** (预期 A「方块落到底部前能不能再移动」我没想到
**生:** (预期 B「游戏结束要不要有重玩按钮」
**师:** 很好。这就是 Plan Mode 第二步的价值——你自己看不出来的漏洞,让 AI 来挖。
【诊断点:学生能否说出「因为 AI 问了这个问题,我补充了 XXX 这条需求」】【理解层】
**【分支A】若学生能说出具体被触发的漏洞**
**师:** 对,你补充了这条之后,需求文档就更完整了一步。这就叫「需求迭代」——不是一次写完,是一次次补充完善。
**【分支B】若学生说「AI 问的问题我都已经写了,没有漏洞」:**
**师:** 好,那你现在需求文档里,有没有写「方块旋转到边界时怎么处理」?
(引导学生发现一个真实存在的漏洞,避免学生误以为文档已经完整)
**【分支C】若学生觉得「AI 问的这些问题都不重要,不影响游戏」:**
**师:** 我们来测试一下——现在先不补充这条,等 AI 做出来,如果这个情况出现,游戏会怎样?
(用结果验证的方式让学生自己发现「小漏洞可以让整个游戏崩掉」)
**师:** 现在进入第三步:回答这些问题,把需求补充完整,然后提交生成。
---
**【分段三:提交生成 → 验收 → 结果溯源】(20分钟)**
*本段重点:建立「按需求逐条验收」的习惯;发现问题时用「结果溯源」找需求根源,而非直接改代码*
**预设误概念:**
- 误概念 M3生成完了就算完成了不需要验收
- 误概念 M3结果不对直接改代码
**讲解与演示 (Teach & Demo): (5分钟)**
**师:** 需求文档完善好了,现在提交给 Kimi 生成。但是——生成完不等于完成。生成完之后要做一件事:按需求文档逐条验收。
**师:** 验收方法:你需求文档里写了什么,就测什么。比如你写了「满一行消除」,就在游戏里把一行填满,看它消没消。比如你写了「碰到边界停止移动」,就在游戏里把方块推到最边上,按方向键,看它有没有停。
**师:** 我来示范验收清单是什么样的。你们跟着我一起来建——
【投屏在白板/文档上逐条写出来,让学生也在自己文档里写】
**师:** 第一条——「方块能左右移动」。怎么测?
**生:** 运行游戏,按左右键,看方块有没有动。
**师:**动了就打勾没动就标叉写上「实际表现XXX」。第二条——「方块能旋转」。第三条——「方块碰到左边界就停」。第四条——「满一行自动消除」。第五条——「消行后上方积木下移」。
**师:** 5条验收清单。注意——**每条都要你真的去操作,不能只是「看起来好像对」。** 看起来对,跟测试过是对,是不一样的。
**师:** 验收清单怎么记录?用三列格式:需求描述 → 实际结果 → 通过/不通过。比如:
```
消行规则:横向填满整行就消除
→ 实际测试:把最下一行填满,方块消失了,上方积木下移了
→ 通过 ✓
碰到左边界停止移动
→ 实际测试:方块移到最左边,继续按左键……还能继续移出去
→ 不通过 ✗ → 需要溯源
```
**师:** 写「不通过」的时候,把你看到的现象写清楚——「方块能移出边界」比「边界有问题」好得多,因为这是下一步溯源的线索。
【理解层:建立「验收 = 逐条测试」的意识,不是「感觉还行」】
**师:** 遇到「结果跟预期不一样」时,**先不要让 AI 改代码**。先做一个动作:找回需求文档,找到是哪一句话没说清楚。这叫结果溯源。
**师:** 我给你们看一个具体例子——假如我测到这条:「方块可以移出右边界,一半跑出屏幕了」。我怎么溯源?
**师:** 打开需求文档,找「移动规则」这一节,看「碰到左右边界」这条。
【投屏展示需求文档】
**师:** 发现了——我写的是「碰到边界停止」但没说「停止」是指什么——是方块的中心碰到边界停还是方块的边缘碰到边界停AI 理解成了方块中心碰到边界才停,所以有半个方块出界了。
**师:** 这就是溯源的结果:找到了是需求里的哪句话没说清楚。现在怎么修?
**生:** (预期:把那条需求改得更清楚)
**师:**把需求改成「方块的任意一侧碰到边界就停止移动不能超出游戏区域」然后重新提交生成。不需要动代码需求说清楚了AI 会自己重新做对的。
【应用层:掌握「结果 → 溯源 → 修改需求 → 重新生成」的完整闭环】
**学生实践 (Practice): (12分钟)**
1. 把完善后的需求文档提交 Kimi 生成游戏
2. 运行游戏,对照刚才建立的 5 条验收清单逐条测试
3. 遇到「结果跟预期不一样」时:
- 回到需求文档
- 找到是哪条需求没说清楚,或者根本没写
- 在文档里标注「这里有问题原因___」
- 修改需求文档,重新提交生成第二版
> 教师走动观察重点:
> - 学生测试完一条直接跳过 → 走过去问「这条算做对了吗?你的需求是怎么写的?对比一下」
> - 学生说「不对」但直接让 AI 改代码 → 叫停,引导回到需求文档:「先找找是哪条需求没说清楚,找到了再来修改」
> - 学生说「全部都对了!」但第一版生成很少能完全通过 → 走过去追问「你测了消行那条了吗?消两行得分对不对?」
**进度同步 (Checkpoint): (3分钟)**
**师:** 谁的第二版比第一版有改善?具体改了什么,改完效果怎样?
【诊断点:学生是否能量化进步——不是「感觉好多了」,而是「之前 XXX 不对,改了需求之后 XXX 对了」】【应用层】
**【分支A】若学生能说出具体改善**
**师:** 你找到了根源改了需求结果变对了——这个进步不是运气是因为你走了溯源流程。记住这个感觉——这就是需求驱动输出。需求写清楚了AI 就做对了。
**【分支B】若学生说「改了但没变化还是不对」**
**师:** 说明改的地方不是真正的根源。我们再来找——你改了哪条需求?游戏里不对的现象是什么?一起来对照看看——你改的那条,跟这个现象有没有直接关系?
【帮学生重新溯源,找到真正对应的需求字段】
**【分支C】若学生说「我全部验收通过了没有问题」**
**师:** 很厉害!那我问你——你验收的是哪 5 条?能说一遍吗?
(让学生逐条念,引导回顾验收过程,确认是真的测试过还是只是「感觉对」)
**师:** 如果真的都通过了,你现在已经有一个基础版可玩的俄罗斯方块了!那你可以进下一段——想想你想给它加什么功能,留到下节课来做。
---
**【分段四:自由探索 + 下节课铺垫】(10分钟)**
*本段重点:在基础版可玩的基础上,允许学生做少量视觉微调;同时引导学生开始想下节课的扩展功能。只有基础版已经跑通的学生才进入本段,进度慢的学生继续完善分段三的最小闭环*
**学生实践 (Practice): (7分钟)**
游戏基本跑起来了之后,学生自由探索:
- 改一下游戏配色,让它更好看
- 或者调整一下速度和得分规则
**师:** 你现在手上有一个可以玩的俄罗斯方块了。下节课,你们可以给它加一个你自己想要的功能。现在开始想——你想给你的游戏加什么?
【诊断点:观察学生自然产生的功能需求,作为下节课教学素材】
学生说出 1-2 个想加的功能(不要求写清楚,只是发散):
- 「我想加个炸弹,可以炸掉一行」
- 「我想让方块会变色」
- 「我想加个 Boss」
**进度同步 (Checkpoint): (3分钟)**
**师:** 说说你想加什么,一句话就行。
(每人说一个,教师快速记录在白板/投屏上)
**师:** 下节课,你们每个人选一个功能,用今天学到的方法——写需求文档、压力测试、生成、验收——把这个功能加进去。
---
**第三幕:反思 (Contemplate) — 10分钟** 🤔
*本幕目标:通过成果展示和互评,让学生复述 Plan Mode 流程,并从「需求层面」评价他人作品,而非只评价游戏好不好玩*
**【环节】成果展示 (6分钟)**
**师:** 谁来展示一下你今天做的游戏?不只是展示游戏——要说三件事:
(投屏列出三个展示要点)
```
1. 你的需求文档里,有没有加什么你自己特别设计的规则?
2. AI 压力测试问了你什么最意外的问题?你最后怎么回答的?
3. 你第一版和第二版有什么不同?是溯源到了哪条需求?
```
(邀请 2 名学生上来展示,每人约 2-3 分钟)
**师:** (第一位展示后)你刚才说需求审核问了你「方块是否能踢墙旋转」——你怎么回答这个问题的?
**生:** (预期:我写了「不可以踢墙,碰到边界就禁止旋转」)
**师:** 好,你做了一个具体的设计决定。有没有人觉得另一种方案——可以踢墙——更好玩?
(让其他学生发表意见,引出「需求可以有不同的设计选择」这个意识)
**【环节】互评 (4分钟)**
**师:** 看完 XXX 同学的游戏,我想让你们不只评价「好不好玩」,而是看他的需求文档——能不能找出还有没说清楚的一个地方?
【诊断点:学生是否建立了「挑剔工程师」的眼光,能从需求角度评价作品】【应用层】
**生:** (预期:「他的需求文档里消多行的得分没写具体」/ 「游戏暂停功能没有需求描述」)
**师:** 很好!这不是在批评他,这是工程师的职业习惯——总是看「还有什么没说清楚」。你刚才找到的这条,就是下节课可以迭代的方向。
**师:** 互评用一个结构:**一个优点**——需求文档里什么地方写得很具体、很清楚?**一个建议**——需求层面(不是游戏体验),还有什么地方可以更精准?
**师:** 今天最难描述清楚的规则是什么?你最后是怎么写清楚的?
---
**第四幕:延续 (Continue) — 5分钟** 🚀
**【环节】抽象总结 (3分钟)**
**师:** 今天我们用了一个贯穿整个课程的核心流程——Plan Mode。谁来说说 Plan Mode 的三步是什么?
**生:** (预期:整理需求、审核需求、确认需求……)
**师:** 对。从今天开始,无论做什么项目,第一步永远是开启 Plan Mode。不管你多想直接动手都先走这三步。
**师:** 为什么因为需求写得越详细AI 执行越准确。需求写不清楚,执行过程中就会出问题,改来改去反而更慢。
**师:** 今天做游戏是这样以后做任何东西——网页、工具、应用——都是同一个流程。Plan Mode 是你跟 AI 合作的基础动作。
**【环节】下节预告 + 5分钟挑战 (2分钟)**
**师:** 下节课,每人选一个你想加的功能,用同样的方法——写需求文档、压力测试、生成、验收——把它加进你的俄罗斯方块里。你的游戏会跟任何人的都不一样。
**师:** 本周5分钟挑战想好你下节课要加的功能是什么在脑子里想一想这个功能如果要写需求文档最难描述清楚的是哪一条不用写想一想就行下节课说给我听。
---
### 5. AI 助教使用指南
**Level 0 需求文档模板(发给学生填写):**
```
# 我的俄罗斯方块 · Level 0 需求文档
## 游戏区域
- 宽___列默认10×___行默认20
## 方块移动规则
- 左移按___键
- 右移按___键
- 旋转按___键
- 快速下落按___键
- 碰到左右边界___停止/不能再往那边移)
- 碰到底部或其他方块___固定在原地
## 消行规则
- 触发条件___横向填满整行
- 消行后上方积木___全部下移一行
- 消1行得___分消2行得___分消4行一次得___分
## 关卡与速度
- 初始速度每___毫秒下落一格默认800
- 每消___行升一级
- 每升一级速度加快___毫秒
## 游戏结束条件
- 什么时候结束___积木堆到顶部
- 结束后显示___游戏结束画面 + 最终得分)
```
**⚠️ 新窗口使用规则(必须遵守):**
```
本课三个阶段的窗口规则:
第一步:整理需求 → 窗口 A写需求文档用这个窗口
第二步:需求审核 → 窗口 B全新窗口不要用窗口A
第三步:执行生成 → 窗口 C全新窗口
原因AI 不会承认自己的问题。在同一个窗口里审核AI 会受
到之前对话的影响(上下文污染),很难客观找出真正的漏洞。
换新窗口 = 换一个全新的 AI 来看,没有偏见,更客观。
```
**Plan Mode 第二步:「需求审核」提示词:**
```
我写了一份俄罗斯方块的需求文档,内容如下:
[粘贴你的需求文档]
请你扮演一个需求审核工程师,读完这份文档后,
找出所有你觉得「没有说清楚」「可以有多种理解」「遇到特殊情况不知道怎么处理」的地方。
注意:你审核的是需求本身,不是代码。现在还没有任何代码。
请用问题的形式列出来(每条一行,以问号结尾)。
不要帮我回答这些问题,不要帮我修改文档,只负责提问。
```
**「结果溯源」引导提示词(发现结果不对时使用):**
```
我的游戏运行结果和我的预期不一样。
具体表现:[描述不对的现象]
我预期应该是:[描述你想要的结果]
我的需求文档里关于这个功能写的是:[粘贴相关需求]
请帮我分析:是需求描述不够清楚,还是需求里根本没写这条规则?
不要帮我修改代码,只帮我找出需求文档里的问题。
```
**「提交生成」提示词(正式版):**
```
请根据以下需求文档,用单个 HTML 文件(内联 CSS 和 JS做一个俄罗斯方块游戏。
要求:严格按照需求文档实现,不要添加文档里没有提到的功能。
需求文档:
[粘贴你的完整需求文档]
```
---
### 6. 教师指南
**本课技术备注:**
单文件 HTML 策略:所有代码写在一个 `index.html` 文件里CSS 和 JS 全部内联。学生双击就能运行不需要任何服务器或安装。Kimi 2.5 生成单文件俄罗斯方块稳定性高,是这门课的最佳选择。
俄罗斯方块核心数据结构(教师理解用,不讲给学生):游戏核心是一个二维数组代表游戏区域,每个格子存 0或颜色值有方块。方块用形状矩阵 + 当前位置表示。学生不需要理解这些,只需要知道「我的需求文档决定了游戏的行为」。
「踢墙旋转」技术说明教师理解用标准俄罗斯方块有「超级旋转系统SRS允许方块在边界旁旋转时自动内移。让 AI 默认生成的版本通常不包含这个细节。如果学生需求里没写AI 可能实现的是「碰到边界禁止旋转」或「可以踢墙」两种之一,建议教师在压力测试环节主动提示学生考虑这条规则。
**常见问题 FAQ**
| 问题 | 应对 |
|------|------|
| 「Kimi 生成的代码打开没有反应」 | 检查文件是否保存为 .html 格式;用浏览器(不是记事本)打开 |
| 「方块可以移动出边界」 | 需求文档里「碰到边界怎么处理」这条没写清楚,引导学生溯源并补充「方块任意一侧碰到边界就停止移动,不能超出游戏区域」 |
| 「消行了但积木没有下移」 | 需求文档里「消行后上方积木全部下移一行」没有写,或者 AI 理解成消行后积木消失但不下移,引导溯源并在需求里明确写出「消行后上方所有积木整体下移填补空位」 |
| 「游戏结束了但还能继续操作」 | 需求文档里游戏结束条件对应的处理没写「结束后禁止玩家操作」,引导补充 |
| 「我想直接改代码」 | 「你能看懂这段代码是做什么的吗?如果不能,改了可能引发新的问题。先改需求文档,让 AI 重新生成更安全。」 |
| 「AI 审核出来问了很多问题,我不知道怎么回答」 | 先问学生「这些问题里,哪一条你有自己的想法?」——通常能说出 2-3 条,剩下的保持默认值,在需求文档里写「保持默认行为」也是合法的回答 |
| 「需求文档写了但 AI 生成的完全没有按需求来」 | 检查提交时有没有把需求文档完整粘贴;也可能是 Kimi 单次生成的结果质量波动,重新提交一次,如果还不行,引导学生检查需求是否有歧义 |
| 「我的游戏做好了,同学的还差很多,我要做什么」 | 可以进入分段四自由探索,或者帮教师整理一份「你发现的 AI 自作主张细节」作为下节课的展示素材 |
| 「旋转之后方块超出边界怎么回事」 | 这是「踢墙旋转」的问题,引导学生在需求文档里补充「旋转后如超出边界,禁止该次旋转」或「旋转后自动内移一格」,选择哪个方案让学生自己决定 |
| 「游戏跑起来了但速度太快,根本玩不了」 | 引导溯源:「你的需求文档里初始速度写的是多少毫秒?」——如果填了 100 毫秒(很快),改成 800 毫秒(默认值);如果没填,这就是漏洞,补充进去 |
**课堂节奏控制(重要):**
本课的核心节奏:**先做出基础可玩版本,再做扩展**。教师要守住这条线,不能让学生在第一版都没跑通的情况下就开始想「加炸弹」「加 Boss」。
具体做法:
- 分段三开始前,教师快速巡场确认每个学生的需求文档覆盖了基础流程(开始→移动→消行→结束)
- 发现学生需求里加了复杂功能(比如道具、反重力):引导他先注释掉,等基础版跑通再加
- 分段四的自由探索,只有基础版已经可玩的学生才能进入
**课堂风险预案:**
- 如果 Kimi 一次生成就完全符合预期:恭喜学生,但仍要做需求审核——「这次 AI 猜对了,你能保证下次加新功能时也能猜对吗?」
- 如果学生进度差异很大:进度快的学生进入分段四自由探索;进度慢的学生只要完成「基础版可玩 + 发现一个需求漏洞 + 修复」这个最小闭环即可,不强求扩展功能。
---
### 7. 5分钟日常AI挑战
**本周挑战:** 想好下节课要加的功能
**挑战说明:** 不用动手,只是想一想:你的俄罗斯方块下节课要加什么功能?这个功能最难描述清楚的是哪一条规则?在脑子里想好,下节课说给老师和同学听。
**下节课分享:** 课前每人说出自己想加的功能,以及预感最难写清楚的那条需求
---
### 8. 拓展任务
**拓展一(推荐):** 给你的游戏加一个「下一个方块预览」功能——把这个功能写成需求文档,测试一下 AI 能不能做出来
**拓展二(挑战):** 给你的游戏加一个「最高分记录」功能——玩完之后会记住历史最高分,下次打开还在

View File

@@ -0,0 +1,664 @@
---
课时: 7
主题: 魔幻俄罗斯方块(下)— 魔改升级 + AI 自动测试
核心能力: [拆解力, 共创力, 韧性力]
核心工具: [Kimi 2.5]
时长: 90分钟
透明化层级: 过程层
适用路线: AICODE-06
---
### 1. 课程目标
**知识目标:**
- 理解「自动化测试」的概念:不靠人工玩,让代码自己验证代码,更快更全面
- 理解「测试覆盖」:需求文档里每一条规则,都应该有一条对应的测试
- 理解功能扩展的增量思维:新功能只写「增量」,不重写整体
**能力目标:**
- 能把需求文档转化为测试条件,交给 AI 生成测试脚本(拆解力)
- 能读懂测试脚本的 ✅❌ 结果,把失败的测试溯源到需求文档(韧性力)
- 能独立完成「需求 → 审核 → 生成 → 测试 → 修复」完整闭环(共创力)
**情感目标:**
- 体验「一键跑测试,马上知道哪里有问题」带来的效率感
- 建立「测试通过才算完成,不是能玩就算完成」的质量意识
- 感受从「每次手动点来点去找 bug」到「测试脚本自动找 bug」的升级
---
### 2. 核心概念与误概念预设
**核心概念认知层级:**
| 概念 | 学生类比 | 认知层级 |
|------|---------|---------|
| 自动化测试 | 与其自己每次手动检查作业有没有写对,不如写一个「答案核对程序」,把你的答案输进去,自动判断对错 | 理解层 |
| 测试覆盖 | 需求文档里写了10条规则测试脚本就要测10件事——少测一条那条出了 bug 你就不知道 | 应用层 |
| 边界条件 | 不只测「正常情况」还要测「极端情况」——比如方块刚好在边角旋转、同时消4行、积木堆到最顶 | 应用层 |
| 增量需求文档 | 只写「新加的部分」,不重写整体——就像给手机升级系统,不是重新买一台手机 | 理解层 |
| 新窗口原则 | 让同一个人既写方案又审方案,他永远找不出大问题——审核和测试必须开新窗口,让没有上下文的新 AI 来做,才能客观找出漏洞 | 应用层 |
**典型误概念表:**
| 编号 | 误概念 | 正确认知 | 激发策略 |
|------|--------|---------|---------|
| M1 | 测试就是自己玩一遍,没出问题就算通过 | 手动测试只能发现你刚好测到的问题;自动测试每次都覆盖所有条件,不会遗漏 | 「你刚才玩的时候有没有特意测『同时消4行的得分是不是800』」 |
| M2 | 测试脚本是程序员才会写的东西 | 把你的需求文档给 AIAI 就能帮你把每条需求变成一个测试 | 现场演示需求文档→AI→测试脚本5分钟完成 |
| M3 | 加新功能要把整个游戏重新做一遍 | 增量需求文档只写新加的部分,在已有代码上加,不是重做 | 演示增量需求文档的写法 |
| M4 | 测试通过了就说明没有 bug | 测试只能覆盖你想到的情况;没覆盖的情况仍然可能有 bug——所以需求文档要写详细 | 「你的测试脚本有没有测『反重力期间消行』?」 |
| M5 | 测试失败说明代码写错了 | 测试失败可能是代码问题,也可能是需求文档没说清楚——先溯源,再决定改哪里 | 展示一个「测试失败但代码没错,是需求文档漏写了」的案例 |
| M6 | 需求审核、测试脚本生成可以在写需求的同一个窗口里做 | 上下文污染——AI 会受之前对话影响,不会客观审核自己生成的东西。审核和测试必须开新窗口 | 演示:同一窗口让 AI 审核自己写的需求,回答很保守;新窗口审核,问题暴露更多 |
---
### 3. 教学准备
**工具与环境:**
- 每台电脑已登录 Kimi 2.5,网络正常
- 学生上节课第6课完成的俄罗斯方块 HTML 文件保存在桌面
- 投影可切换至任意学生屏幕
- 准备路演计时器3分钟倒计时
**教学资源:**
- 教师准备增量需求文档模板见第5节
- 教师准备生成测试脚本的提示词见第5节
- 教师准备一份提前生成好的测试脚本演示包含1个 ✅ 和1个 ❌,用于导入展示)
- 教师准备功能灵感参考清单见第5节
- 教师准备路演结构引导卡见第5节
- 学生资源第6课完成的俄罗斯方块 HTML 文件 + Level 0 需求文档
**教师备课体验任务:**
> 备课前,教师必须亲自完成以下操作:
>
> 1. 用自己的俄罗斯方块代码 + 需求文档,让 Kimi 生成测试脚本,实际运行并记录结果
> 2. 故意在需求文档里漏写一条规则,观察测试脚本是否能发现对应的 bug
> 3. 准备一个演示用的「测试结果页面」包含至少1个 ✅ 和1个 ❌,用于课堂导入
> 4. 提前试跑「炸弹方块」功能的增量需求文档完整流程,记录 AI 审核会问什么问题
---
### 4. 教学流程
---
**第一幕:联系 (Connect) — 10分钟** 🔗
*本幕目标:用每人的功能构想激活学生兴趣,制造「今天不只是加功能,还要学一个新技能」的认知悬念*
**【环节】上节课回顾 (4分钟)**
**师:** 上节课结束前,我让你们想一个今天要加的功能,而且在出门前每人说了一句话。我们来回顾一下——上节课我们用了 Plan Mode它有几步谁来说
【诊断点:检测 Plan Mode 三步流程的记忆保持度】【识别层】
**生:** (预期:打开 Plan Mode → 写需求文档 → 让 AI 审核……)
**【分支A】若学生能说出三步写文档、审核、执行**
**师:** 说得很准。今天还是这三步,但是有一点不一样——今天写的是「增量需求文档」,不是重新写整个游戏。等会儿我会解释区别在哪里。
**【分支B】若学生只说出一两步**
**师:** 没关系我帮你补一下。Plan Mode 三步:第一步,写需求文档,把你想做的东西说清楚;第二步,需求审核,让 AI 扮演审核工程师找漏洞;第三步,执行,生成代码。今天还是这三步,我们继续。
**师:** 好,现在说说你想加的功能——每人说一个,我把它们写在黑板上。
【每人快速说一个,教师写在白板/投屏上,列成一列】
**生:** (各自说出功能:炸弹方块、速度加成、冻结方块、彩虹消行……)
**师:** 大家看一下黑板——(指着功能列表)这里有六个不同的功能方向,没有两个人做一样的。你们今天各自在做一个完全不同的版本。
**师:** 我们来做一件有趣的事——互相评一下别人的功能想法。你觉得黑板上哪一个功能听起来最难描述清楚?
**生:** (讨论,指出某个功能)
**师:** 为什么觉得难?
**生:** (例如:「重力反转不知道要怎么写规则」「炸弹不知道爆炸范围算不算消行」)
**师:** 你们刚才说的这些「不知道」就是今天需求文档里必须写清楚的部分。不写清楚AI 就会自己决定,结果可能跟你想的完全不一样。
**【环节】引发认知冲突 + 悬念铺垫 (6分钟)**
**师:** 我再问你们——你刚才说想加的功能,你觉得最难描述清楚的规则是哪一条?比如你想加炸弹方块,「炸弹爆炸」这件事,你要怎么告诉 AI
【诊断点:探测学生对「把直觉转化为精确规则」的困难点认知】【理解层】
**生:** (各自说出难点——爆炸范围?爆炸后消行吗?得分怎么算?)
**师:** 很好,你已经在思考这个问题了。等会儿写需求文档的时候,那条规则要特别仔细。
**师:** 今天除了加功能,我们还会学一个新技能。上节课你们手动测试游戏——自己玩、自己找 bug对吗手动测试有一个问题一会儿让你们自己感受。
【故意不展开,制造悬念】
**师:** 我问你们——你的游戏有5条规则你在手动测的时候你能保证5条都测到了吗
**生:** (预期:不一定……有点难……)
**师:** 等会儿让你们亲身体验这个问题,我们先进建构。
**师:** 打开你上节课的游戏文件,确认能正常运行。我们进入建构阶段。
---
**第二幕:建构 (Construct) — 65分钟** 🛠️
---
**【分段一:增量需求文档 + 需求审核】(15分钟)**
*本段重点:理解「增量」思维,写出可测试的需求文档,尤其是交互规则和验收标准*
**预设误概念:**
- 误概念 M3加新功能要把整个游戏重做
- 误概念:验收标准可以写成「功能正常」这种模糊的话
**讲解与演示 (Teach & Demo): (5分钟)**
**师:** 今天继续用 Plan Mode。但今天写的是「增量需求文档」——只写新加的部分不重写整个游戏。
**师:** 类比一下——你手机升级系统,是重新买一台手机,还是只更新改变的那一部分?
**生:** (预期:只更新改变的部分)
**师:** 对。增量需求文档就是这个意思:你的游戏核心逻辑不动,你只描述「要在这个基础上加什么」。
**师:** 增量需求文档有一个特别重要的部分,叫「与原有功能的交互规则」。我举一个具体例子——你加了炸弹方块。炸弹落地,爆炸了。爆炸之后刚好把整行都消干净了——这算消行吗?得多少分?是按炸弹规则算,还是按消行规则算,还是两个叠加?
**师:** 如果你没写清楚AI 自己决定,结果可能跟你想的完全不一样。所以交互规则必须写。
**师:** 还有一个字段叫「验收标准」。它的格式是这样的:
**「[情况] → [预期结果]」**
比如:「炸弹落地时周围没有积木 → 爆炸动画播放,得分+50无消行」。
验收标准必须是可以测试的——能测试 = 说清楚了具体情况,说清楚了预期结果。不能写「功能正常运行」,那不是验收标准,那是废话。
**师:** 我来考考你们——以下两条验收标准,哪条是可测试的?
- A「炸弹功能运行正常」
- B「炸弹落地后落点周围3×3格内的积木清除得分+50」
**生:** 预期B
**师:**B 是可测试的——它告诉了 AI「清除3×3范围」和「+50分」。A 是废话——AI 不知道「正常」是什么意思。你的验收标准必须全部像 B 这样写。
【投屏展示增量需求文档模板见第5节】
**学生实践 (Practice): (8分钟)**
1. 学生填写增量需求文档(重点:功能规则 + 交互规则 + 验收标准)
2. **⚠️ 开新窗口**:复制需求文档内容,打开全新的 Kimi 对话,执行「需求审核」提示词
- 不能在写需求的同一个窗口里审核——AI 会受上下文影响,不会客观指出问题
3. 回答 AI 的问题,把答案补充进需求文档(回到原来的窗口补充)
> 教师走动:检查以下三项——是否写了「与原有功能的交互规则」?验收标准是否是「情况 → 预期结果」格式验收标准条数是否至少3条
**进度同步 (Checkpoint): (2分钟)**
**师:** AI 审核出了什么?有没有让你意外的问题?
**生:** 分享例如「AI 问我炸弹同时触发消行怎么算」)
**【分支A】若学生说 AI 问出了没想到的问题:**
**师:** 这个问题你之前有想到吗?
**生:** 没有……
**师:** 所以 AI 审核是真的有用的——它问的问题不是废话,是你确实需要回答的问题。
**【分支B】若学生说 AI 没审出什么问题:**
**师:** 把你的需求文档发我看一下。(观察)——你的验收标准里有没有写边界情况?比如「功能触发时恰好也消行」这种情况?如果没有,让 AI 专门针对边界情况再审一遍。
**师:** 好,需求文档基本确认了,进入执行阶段。
---
**【分段二:生成第一版 → 手动测试 → 感受痛点】(15分钟)**
*本段重点:先让学生亲身体验手动测试的局限,再自然引出「让电脑帮我们测」的需求*
**预设误概念:**
- 误概念 M1手动玩一遍没出问题就算通过
- 误概念:测试只需要测「好不好玩」,不需要逐条对照需求
**讲解与演示 (Teach & Demo): (2分钟)**
**师:** 把确认好的增量需求文档提交 Kimi基于上节课的代码增加新功能。
【投屏展示「增量生成」提示词见第5节】
**师:** 生成之后,先手动测几条——自己玩一玩,看看新功能有没有按需求做出来。特别注意:不是随便玩,是对照着你的验收标准一条条测。
**学生实践 (Practice): (10分钟)**
1. 提交增量需求文档 + 旧代码给 Kimi生成第一版
2. 打开游戏,手动测试:新功能触发了吗?原有消行/得分还正常吗?
3. 对照验收标准逐条检查,记录发现的问题
> 教师走动观察故意不干预让学生自己感受「手动测试的局限」。观察是否有学生只是「玩了一会儿」而没有逐条对照验收标准是否有学生根本没有测「新功能和消行同时触发」的边界情况是否有学生测了3条就觉得「差不多了」
**进度同步 (Checkpoint): (3分钟)**
**师:** 手动测了哪几条?对照你的验收标准,你测了几条?
**生:** 预期2条、3条……
**师:** 你的验收标准里一共有几条?
**生:** 5条……
**师:** 你测了3条还有2条没测。你确定那2条没有问题吗
**生:** 不确定……
**师:** 还有一个问题——就算今天你把所有5条都测了下次你改了代码你还要重新手动测一遍。如果你做了第二版、第三版每次都要从头手动测一遍。需求文档越来越长要测的条数越来越多手动测试需要的时间也越来越长。
**师:** 有没有办法,让电脑帮我们测,而且每次都自动把所有条件测完?
**【分支A】若有学生说「那肯定有程序能做这个」**
**师:** 你说对了!这个东西叫测试脚本。我们现在就去做一个。
**【分支B】若学生面面相觑不知道答案**
**师:** 答案就是——用 AI 帮我们写一个测试脚本。这个脚本会自动运行你的游戏逻辑,逐条对照需求文档,告诉你哪条通过了、哪条失败了。我来给你们看一下它长什么样。
---
**【分段三AI 生成测试脚本 → 跑测试 → 修复】(25分钟)**
*本段重点:生成并运行测试脚本,学会读懂 ✅❌ 结果并溯源,建立「测试才算完成」的质量意识*
**预设误概念:**
- 误概念 M2测试脚本是程序员才会写的
- 误概念 M5测试失败说明代码写错了不可能是需求问题
- 误概念:看不懂代码就没法用测试脚本
**讲解与演示 (Teach & Demo): (6分钟)**
**师:** 我来给你们看一个东西。
【投屏展示教师提前准备好的测试结果页面,显示 ✅❌ 列表】
**师:** 在看之前,先讲一个重要规则——**生成测试脚本必须开新窗口。** 这和上节课需求审核的原因一样AI 不会承认自己的问题。你在写游戏代码的同一个窗口里让 AI 生成测试,它会倾向于「测试通过」,因为它觉得自己写的代码没问题。换新窗口,让新的 AI 来测,它才会客观地发现问题。
**师:** 整个项目的窗口规则是这样的:写需求一个窗口、审核需求一个新窗口、生成代码一个新窗口、生成测试一个新窗口。**每个独立的任务,都在自己干净的窗口里做,避免上下文污染。**
**师:** 这是一个测试脚本跑出来的结果。你们来读一下——
- ✅ 消1行得分100分通过
- ✅ 消2行得分300分通过
- ✅ 消4行得分800分通过
- ❌ 炸弹爆炸得分期望50实际得到0
- ✅ 游戏结束条件:通过
- ❌ 炸弹+消行叠加得分期望150实际得到100
**师:** 它自动测了6条规则告诉我哪些通过了、哪些失败了还说明了失败的具体原因——期望是多少实际是多少。我没有玩游戏是代码自己测自己。
**师:** 你们说这个测试脚本是怎么工作的它是怎么知道「消1行应该得100分」的
**生:** (预期:从需求文档里知道的?)
**师:**测试脚本的工作方式就三步第一步构造一个具体的场景——比如「棋盘最底行全满了一行」第二步调用游戏里的消行函数第三步对比结果和需求文档里写的数字。需求文档说100分结果也是100分就是 ✅;结果是别的数字,就是 ❌。
**师:** 你不需要看懂每一行代码。你只需要:能读 ✅❌ 结果、能看懂失败原因、能判断是代码问题还是需求文档问题。
**师:** 怎么判断失败原因?方法是这样的——
**师:** 如果 ❌ 显示「期望50实际0」你先查需求文档文档里有没有写「炸弹爆炸得50分」
**师:** 如果需求文档写了但代码算出来是0那是代码问题——让 AI 修代码。
**师:** 如果需求文档漏写了这条规则,那是需求问题——先补需求文档,再重新生成代码。
【投屏展示「生成测试脚本」提示词见第5节】
**学生实践 (Practice): (15分钟)**
1. 把需求文档 + 游戏代码提交 Kimi生成 `test.html`
2. 双击打开 `test.html`,查看测试结果
3. 对每一个 ❌,按以下步骤处理:
- 读失败原因:「期望 X实际 Y」
- 查需求文档:这条规则文档里写清楚了吗?
- 如果是代码问题 → 告诉 Kimi「这个测试失败了[失败信息],帮我修复」
- 如果是需求问题 → 先补充需求文档,再让 AI 重新生成代码
4. 修复后重新跑测试,目标:全部 ✅
> 教师走动观察重点:
> - 学生是否看到 ❌ 就直接让 AI「修代码」而没有先判断是需求问题还是代码问题
> - 是否有学生测试结果全是 ✅,但游戏实际上还有问题?此时引导:「哪条 bug 对应的规则,在需求文档里没有写到?」
> - 是否有学生 `test.html` 打开是空白?提示使用「测试失败溯源」提示词
**进度同步 (Checkpoint): (4分钟)**
**师:** 你的测试脚本发现了几个 ❌?哪一个让你最意外?
【诊断点:学生是否发现了手动测试时没有发现的问题】【应用层】
**【分支A】若测试发现了手动没发现的 bug**
**师:** 这个 bug 你刚才手动玩的时候发现了吗?
**生:** 没有……
**师:** 这就是自动测试的价值——你没想到去测的地方,它帮你测了。想象一下,如果你发布了这个版本,别人玩到这个 bug 会怎么样?
**生:** 会觉得游戏有问题……
**师:** 但是因为你有测试脚本,你在发布之前就知道了。这就是「安全网」。
**【分支B】若测试全部通过**
**师:** 全部通过——说明你的需求文档写得很清楚AI 也执行得准确。但我再问一下:测试脚本测了几条?你的验收标准里有几条?
**生:** 测了5条文档里写了7条……
**师:** 有2条没有被测到。是不是那2条在生成测试脚本的时候AI 没找到对应的逻辑或者写得不够清楚没办法生成测试把那2条拎出来让 AI 单独补测。
**【分支C】若测试脚本打不开或全是错误**
**师:** 不要慌,这种情况很正常。我们用「最小化调试法」——不是让 AI 修整个测试脚本而是只请它「单独测一条最简单的规则比如消1行得100分」先让一条测试跑起来再逐步扩展。
---
**【分段四:有了安全网,放心魔改——第二版、第三版】(10分钟)**
*本段重点:建立「跑测试 → 确认安全 → 再改代码」的迭代习惯,鼓励学生大胆扩展*
**预设误概念:**
- 误概念:有了测试脚本,每次改完不用跑,「应该没问题」
- 误概念:第一版做到及格就行,没必要做第二版
**讲解与演示 (Teach & Demo): (3分钟)**
**师:** 你现在有了一个测试脚本。这个测试脚本是你的「安全网」。它的具体意义是什么?
**师:** 没有安全网的时候,你怕改出问题,所以不敢改太多。每次改完要自己手动测半天,万一改出新 bug 还得找半天。结果是——你不敢大胆加功能。
**师:** 有了安全网之后:每次改完代码,双击 `test.html`10秒钟看结果。全部 ✅,说明原有功能没被破坏,可以继续加。出现 ❌,马上知道哪里出问题,立刻修。这就是「安全网」的具体作用——让你敢放心改代码。
**师:** 所以现在,有了安全网,我们才敢做更大胆的第二版。去做——比第一版更酷、更复杂。每加完一个功能,跑一遍测试,确认 ✅ 再继续加下一个。
**学生实践 (Practice): (5分钟)**
1. 在第一版基础上继续扩展——加第二个功能,或者把第一个功能做得更完整
2. 每次生成新代码后,跑 `test.html`,确认没有破坏原有逻辑
3. 有余力的学生继续做第三版,目标:做出功能灵感清单里「⭐⭐⭐」的功能
> 教师走动鼓励学生跑测试发现有学生只靠手动玩来验证时提示「跑一遍测试脚本10秒就有答案比手动玩快多了。」
**进度同步 (Checkpoint): (2分钟)**
**师:** 做了第二版的举手。你加了什么?跑测试了吗?结果怎样?
【诊断点:学生是否形成「加功能 → 跑测试 → 检查结果」的迭代习惯】【应用层】
**【分支A】若学生说「跑了测试全部通过然后继续加」**
**师:** 这就是正确的迭代节奏。以后做任何项目都是这个节奏。
**【分支B】若学生说「加了功能但忘记跑测试」**
**师:** 现在跑一遍。——有没有 ❌?
**生:** (跑完结果)
**师:** 如果有 ❌,这就是「不跑测试的代价」——你可能在不知情的情况下,新加的功能破坏了原来的逻辑。
---
**第三幕:反思 (Contemplate) — 10分钟** 🤔
*本幕目标:通过路演和互评,让学生反思「测试覆盖」的本质,以及「需求文档质量 = 测试质量」的核心认知*
**【环节】成果路演 (7分钟)**
**师:** 每人1.5分钟路演6人小班。路演有引导卡大家拿一张。
【发放路演结构引导卡见第5节】
**师:** 路演不是「打开游戏给大家看」——那只是演示,不是路演。路演要讲三件事:你加了什么功能、测试脚本发现了什么、你怎么修的。开始。
(每人依次路演,教师计时。路演要点:)
- 展示最终版本游戏20秒演示
- 展示测试脚本的 ✅❌ 结果(告诉大家发现了几个 ❌)
- 重点讲:最意外的那个 ❌ 是代码问题还是需求问题?怎么修的?
- 一句话总结:「如果重来一次,需求文档哪里会写得不一样?」
> 教师观察:是否有学生的路演只说了「我加了炸弹方块」,但没有说测试相关的内容?提示:「测试脚本发现了什么?」
**【环节】互评与讨论 (3分钟)**
**师:** 刚才大家的路演里,哪一个测试脚本发现的问题最有价值?
**生:** (讨论)
**师:** 我们来做一个结构化互评——选一位刚才路演的同学,给他的作品说「一个你觉得做得好的地方」和「一个你觉得可以改进的地方」。
**生:** (互评,例如:「他的炸弹功能很酷,但是我觉得爆炸范围可以更大一点」)
**师:** 「更大一点」——具体怎么大?你能告诉他怎么写到需求文档里吗?
**生:** 尝试表达「爆炸范围从3×3改成5×5」
**师:** 这就是一条可测试的需求改进建议。他可以把这条加到需求文档里,然后让 AI 重新生成。
**师:** 有没有人的测试结果全是 ✅,但路演时我们发现游戏还是有问题?
**生:** (可能有学生举手)
**师:** 这说明什么?
**生:** (预期:测试没有覆盖到那个情况……)
**师:** 对。测试只能发现「你写进需求文档的问题」。你没写的,它不知道要测。这就是为什么需求文档要尽量详细——需求文档越完整,测试覆盖越全,漏掉的 bug 越少。
**师:** 给今天的工作做一个评分——需求文档 100 分、测试脚本 100 分、游戏功能 100 分,你自己的三项各给多少分?
(让学生自评,不需要出声,心里有数就好)
**师:** 哪位愿意说说自己的分?
**生:** 分享例如「需求文档70测试80功能90」
**师:** 三项里面,哪一项你觉得下次会做得更好?
**生:** (分享)
**师:** 记住这个答案。这就是你下一个项目要重点改进的地方。
---
**第四幕:延续 (Continue) — 5分钟** 🚀
**【环节】抽象总结 (3分钟)**
**师:** 两节课,我们走了一个完整的流程。谁来说说是哪几步?
**生:** Plan Mode → 需求文档 → 审核 → 生成 → 测试 → 修复……
**师:** 对。这个流程有个名字:需求 → 实现 → 测试 → 修复。真实的工程师每天都在走这个循环。
**师:** 今天最重要的一句话:**「测试通过才算完成,不是能玩就算完成。」**
**师:** 能玩是 60 分,测试通过是 90 分,需求文档里每一条都有测试覆盖是 100 分。
**师:** 还有一件事——今天你们体验了什么是「安全网」。这不只是测试脚本的概念,这是一种工作方式:在做危险操作之前,先建立一个保护机制。以后你们做任何项目,都可以问自己:「我有没有安全网?」
**师:** 最后,我想让你们想一个问题——今天学到的「需求文档 → 测试 → 修复」这个流程,除了做游戏,还能用在哪里?
**生:** (讨论:做网站?做 App……
**师:** 对。你们今天走的这个流程,和真实的工程师在公司里走的流程是一样的。只不过他们的项目更大、需求文档更长、测试脚本更复杂。但方法是一样的。
**【环节】下节预告 + 5分钟挑战 (2分钟)**
**师:** 接下来的课程你们会做更大的项目。今天学到的——Plan Mode、需求审核、自动测试——会一直用到只是项目变得更复杂。你们已经有了完整的工具箱。
**师:** 本周5分钟AI挑战找到你需求文档里一条没有被测试覆盖到的规则把它补进需求文档然后让 Kimi 给测试脚本加上这条测试,跑一下看是 ✅ 还是 ❌。下节课说出你补的是哪条规则,测试结果是什么。
---
### 5. AI 助教使用指南
**⚠️ 新窗口使用规则(必须遵守):**
```
本课四个阶段的窗口规则:
窗口 A写增量需求文档整理新功能需求用这个窗口
窗口 B需求审核全新窗口——AI 不会承认自己写的有问题)
窗口 C执行生成全新窗口——基于原代码增加新功能
窗口 D生成测试脚本全新窗口——让新 AI 客观测试)
核心原则:审核、测试必须换新窗口。在同一个窗口里又写又审
又测叫「上下文污染」——AI 会偏向为自己生成的内容辩护,
找不出真正的问题。
```
**增量需求文档模板(学生填写):**
```
# 新功能需求文档:[功能名称]
## 功能描述
[用一两句话说清楚这个功能是什么]
## 触发条件
[什么情况下这个功能会出现/触发?]
## 功能规则
[这个功能具体怎么运作?每条规则尽量具体]
## 与原有功能的交互规则
- 与消行的交互:[这个功能触发时,如果同时消了行,怎么处理?]
- 与得分的交互:[这个功能会影响得分吗?怎么影响?]
- 与游戏结束的交互:[游戏结束时,这个功能有没有特殊处理?]
## 验收标准(每条都要能自动测试)
1. [情况] → [预期结果]
2. [情况] → [预期结果]
3. [情况] → [预期结果]
```
**「增量生成」提示词:**
```
我已经有一个俄罗斯方块游戏(代码在下面)。
现在需要在这个基础上增加一个新功能,需求如下:
[粘贴增量需求文档]
要求:
1. 在已有代码基础上增加,不要改变原有功能的行为
2. 严格按照需求文档实现,不要添加文档里没有提到的内容
3. 核心逻辑函数collides、clearLines、rotate 等)必须保持独立,
不要把逻辑和渲染混在一起(方便后续生成测试脚本)
4. 输出完整的 HTML 文件(内联 CSS 和 JS
已有代码:
[粘贴游戏 HTML 代码]
```
**「生成测试脚本」提示词(本课核心):**
```
我有一个俄罗斯方块游戏,代码如下:
[粘贴游戏 HTML 代码]
我的需求文档如下:
[粘贴需求文档]
请帮我生成一个独立的 test.html 文件,要求:
1. 把游戏的核心逻辑函数(碰撞检测、消行、得分计算等)提取出来
2. 根据需求文档里的每一条规则,编写一个对应的测试
3. 每个测试构造一个具体的场景,调用函数,验证结果是否符合需求
4. 在页面上显示测试结果:✅ 通过 / ❌ 失败(失败时说明期望值和实际值)
5. 不需要任何外部依赖,双击 test.html 就能在浏览器里运行
重点测试的边界条件:
- 方块在左/右边界的碰撞
- 底部锁定
- 消1行/2行/4行的得分是否符合需求文档
- 消行后上方积木是否正确下移
- 游戏结束条件
- [新功能]的触发条件和结果
```
**测试脚本示例输出(教师用于课堂展示):**
```
俄罗斯方块测试结果
───────────────────────────────────
✅ 消1行得分 100 分 通过
✅ 消2行得分 300 分 通过
✅ 消4行Tetris得分 800 分 通过
✅ 游戏结束条件(积木超出顶行) 通过
❌ 炸弹方块落地得分 失败
期望50 实际0
→ 提示clearBomb() 函数未返回得分
✅ 消行后积木正确下移 通过
❌ 炸弹 + 消行叠加得分 失败
期望15050+100 实际100
→ 提示:爆炸触发消行时,炸弹得分未累加
───────────────────────────────────
6/8 通过 2/8 失败
```
**「测试失败溯源」提示词:**
```
我的测试脚本显示这个测试失败了:
测试名称:[测试名称]
期望结果:[期望值]
实际结果:[实际值]
我的需求文档里关于这条规则写的是:
[粘贴相关需求]
请帮我分析:
1. 是代码实现有问题?还是需求文档没有说清楚?
2. 如果是代码问题,具体是哪里需要修改?
3. 如果是需求问题,我需要在需求文档里补充什么?
```
**测试失败溯源示例对话:**
```
学生输入:
测试名称:炸弹方块落地得分
期望结果50
实际结果0
需求文档写的是:炸弹方块落地时,播放爆炸动画,得分+50
AI 回答:
这是代码问题。你的需求文档写得很清楚——「落地得分+50」。
问题出在 clearBomb() 函数:当前函数只处理了爆炸效果,
没有调用得分更新逻辑。建议修改:
在 clearBomb() 函数末尾加一行 score += 50 并调用 updateScore()。
```
**功能灵感参考清单:**
| 功能 | 复杂度 | 一句话说明 |
|------|--------|---------|
| 炸弹方块 | ⭐⭐ | 落地后炸掉周围3×3范围 |
| 速度加成方块 | ⭐⭐ | 碰到就让下落速度加快5秒 |
| 冻结方块 | ⭐⭐ | 碰到就让当前方块暂停3秒 |
| 彩虹消行 | ⭐ | 消行时屏幕闪彩虹色 |
| 方块预言家 | ⭐⭐ | 显示接下来3个方块 |
| Boss 行 | ⭐⭐⭐ | 每隔10行出现一行无法普通消除的行 |
| 重力反转 | ⭐⭐⭐ | 消4行触发5秒方块往上飞 |
**路演结构引导卡(路演前发给学生):**
```
我的路演每人约1.5分钟)
1. 我做了几个版本加了什么功能20秒
→ 演示给大家看
2. 需求文档改了几版20秒
→ AI 审核问了我什么让我意外的问题?
3. 测试脚本发现了什么30秒
→ 发现了几个 ❌?
→ 最意外的 ❌ 是什么?是代码问题还是需求问题?怎么修的?
4. 如果重来一次10秒
→ 需求文档哪里会写得不一样?
```
---
### 6. 教师指南
**本课技术备注:**
测试脚本的原理:游戏的核心逻辑函数(`collides``clearLines``rotate`)是纯函数——给定输入,返回确定的输出,不依赖渲染。测试脚本把这些函数复制出来,构造特定的棋盘状态作为输入,调用函数,然后检查输出是否符合需求文档的描述。整个过程在浏览器里运行,不需要任何安装。
需求文档与测试的关系测试脚本的质量取决于需求文档的质量。需求文档里写「消行得分」但没写具体数字AI 就无法生成有意义的测试。这是本课的核心教学点——需求越具体,测试越有效。
增量需求文档的代码实现原理教师需要了解增量功能是在已有代码的基础上「插入」新逻辑而非替换。Kimi 在生成增量代码时会查找合适的插入位置(比如 `clearLines()` 函数内部并添加新逻辑。如果原有代码结构混乱逻辑和渲染混在一起AI 会很难找到插入点导致生成失败。这就是为什么第6课的「增量生成」提示词里要求「核心逻辑函数保持独立」。
**常见问题 FAQ**
| 问题 | 应对 |
|------|------|
| 「测试脚本打开是空白页」 | 检查 HTML 文件是否完整;让 Kimi 检查生成的代码有没有语法错误 |
| 「测试全部 ❌」 | 很可能是函数提取时出了问题;让 Kimi「只提取 clearLines 函数写一个最简单的测试」,逐步调试 |
| 「测试全部 ✅ 但游戏还是有 bug」 | 引导学生找到:哪条 bug 对应的规则,在需求文档里没有写到?这就是测试覆盖不足 |
| 「Kimi 生成的测试脚本太复杂看不懂」 | 「你不需要看懂每一行代码,只需要看 ✅❌ 结果和失败原因」;理解结果比理解实现更重要 |
| 「测试失败但不知道是代码问题还是需求问题」 | 使用「测试失败溯源」提示词,让 AI 帮你判断 |
| 「AI 审核问了太多问题,不知道怎么回答」 | 让学生先回答最好回答的那一条,其他的暂时写「待定」,先生成第一版,跑测试的时候再补 |
| 「增量生成后原有消行功能不见了」 | 说明 Kimi 误修改了原有逻辑;把原版代码重新提交,强调「不要改变原有功能的行为」 |
| 「验收标准不知道怎么写」 | 用「情况 → 预期结果」句式套一遍:「[什么情况] → [应该看到什么]」;如果写不出来,说明还没想清楚这条规则 |
| 「第二版加完功能跑测试有 ❌,但不知道是新功能的问题还是旧功能出了问题」 | 先把新功能注释掉,跑测试,确认旧功能全部 ✅;再把新功能打开,重新跑,锁定是新代码引入的问题 |
| 「路演超时怎么办」 | 路演前说清楚:演示只需要展示「最酷的那一个功能」,不需要演示全部;测试部分只说「最意外的 ❌」 |
**课堂节奏控制:**
- 分段一15分钟最容易超时的环节是「AI 审核」——有学生会和 AI 进行很长的对话。给学生一个限制AI 审核最多回答3轮问题然后就进入执行。
- 分段二结束时,学生手动测完后,教师主动提问「你需求文档里有几条规则,你测了几条」——制造认知冲突,让学生自己感受手动测试的不完整性,再引出自动测试。
- 分段三的核心是「看懂 ✅❌ 结果并溯源」,不是「让每个学生都把测试写完」。进度慢的学生只要跑出测试结果、理解一个 ❌ 的原因即可。
- 分段四时间有限,对于进度慢的学生,「跑一遍测试确认第一版没问题」就算达标,不强求做第二版。
- 路演时间第三幕要严格控制在1.5分钟/人。如果有学生的功能特别复杂,教师可以帮助提炼:「你只需要说最意外的那个 ❌,其他的可以跳过。」
**课堂风险预案:**
- 如果 Kimi 生成的测试脚本无法运行:教师用提前准备好的演示版本讲解概念,让学生理解「测试脚本是什么、能发现什么问题」即可,不强求每人都跑成功。
- 如果学生进度差异大:进度快的学生尝试「给测试脚本加更多边界条件测试」;进度慢的学生重点完成「生成第一版 + 跑一次测试 + 理解结果」。
- 如果网络不稳定导致 Kimi 响应慢:提前在教师电脑上缓存一份完整的增量生成 + 测试脚本生成示例,用于离线演示。
---
### 7. 5分钟日常AI挑战
**本周挑战:** 给你的游戏补一条没有被测试覆盖的规则
**挑战说明:** 找到你需求文档里一条没有被测试脚本覆盖到的规则,把它补进需求文档,然后让 Kimi 给测试脚本加上这条测试,跑一下看是 ✅ 还是 ❌。
**下节课分享:** 说出你补的是哪条规则,测试结果是什么
---
### 8. 拓展任务
**拓展一(推荐):** 让你的测试脚本覆盖所有需求文档里的规则——对照需求文档逐条检查,补上缺少的测试。目标:测试脚本的条数 = 验收标准的条数。
**拓展二(挑战):** 在需求文档里加一个你自己想出来的「边界情况」,写清楚预期结果,让 AI 生成对应的测试看游戏能不能通过。参考方向「同时消4行 + 炸弹爆炸」这种极端叠加情况的得分是多少?
**拓展三(超级挑战):** 试着把你的增量需求文档写得让另一个同学能看懂——交换给同桌,让他/她用你的需求文档,在他/她的游戏上生成同样的功能。看看你的需求文档清不清楚。

View File

@@ -0,0 +1,602 @@
---
课时: 8
主题: 涂鸦PK— 画图工具 + 角色设计
核心能力: [拆解力, 审美力]
核心工具: [Trae IDE, Kimi]
时长: 90分钟
透明化层级: 过程层
适用路线: AICODE-06
---
### 1. 课程目标
**知识目标:**
- 理解「精准需求 > 冗余需求」:需求文档的价值在于可测试性,不在于篇幅长短
- 理解「新窗口审核」的本质:不同上下文的 AI 才能发现你看不见的漏洞
- 理解角色属性是「打法定位」的策略设计,不是随意填写的数字
**能力目标:**
- 能用 Plan Mode 三步窗口A整理→窗口B审核→窗口C执行完成画图工具的需求文档拆解力
- 能用自己生成的画图工具画出两帧角色帧1待机 + 帧2攻击差异度清晰可见审美力
- 能根据20分预算制选定自己的打法定位合理分配属性点拆解力
**情感目标:**
- 建立「工具是自己做的」的自豪感——不是用别人的软件,而是先造工具再用工具
- 发现「像素画」的审美乐趣小小的64×64格子里也能表达角色个性
- 体验「打法定位」带来的策略感:我的角色是有设计意图的,不是随便画的
---
### 2. 核心概念与误概念预设
**核心概念认知层级:**
| 概念 | 学生类比 | 认知层级 |
|------|---------|---------|
| Spritesheet精灵图集 | 动画书里的一页纸上排了多幅连续图快速翻动就成了动画Spritesheet 就是把所有帧拼成一张图,程序按顺序裁取每一帧 | 识别层 |
| 帧Frame | 动画里每一张静止的画面;两帧之间的差异越大,动画的动感越强 | 识别层 |
| 属性预算制 | 创建角色时只有20个技能点花在HP多了ATK就少——这就是策略不是加法题 | 理解层 |
| 需求可测试性 | 「字要好看」不可测试「字体大小16px、颜色白色」可以测试。只有写得出来测试步骤的需求才是写清楚了的需求 | 应用层 |
| 打法定位 | 篮球队里有中锋/控卫/得分手,分工不同;坦克型靠血厚扛伤害,刺客型靠高攻一击必杀——每种打法都有对应的属性分配 | 应用层 |
**典型误概念表:**
| 编号 | 误概念 | 正确认知 | 激发策略 |
|------|--------|---------|---------|
| M1 | 需求写得越多越好,越详细越安全 | 冗余需求会制造更多歧义和 bug精准的需求才是关键——每条需求都要能写出测试步骤 | 展示一份100行需求文档生成的混乱代码 vs 精准15行需求生成的干净代码问「这条需求你怎么测试它对不对」 |
| M2 | 让 AI 直接写代码更快,需求文档浪费时间 | 没有需求文档,验收时你不知道从哪里查;出了 bug 无法溯源,只能靠直觉瞎改 | 问:「如果 AI 做出来画布不是64×64你怎么知道是哪条需求没说清楚」 |
| M3 | 需求审核就是自己再读一遍 | 新窗口审核:上下文不同的 AI 能发现你看不见的漏洞;自己审等于让同一个人既出题又判卷 | 演示:把需求文档发给同一窗口的 AI它说「没问题」发给新窗口它提了3个你没想到的问题 |
| M4 | 两帧画一样就行,动画不重要 | 帧1待机姿势 + 帧2攻击姿势差异越大动画越有冲击力帧2完全复制帧1等于没有动画 | 打开 demo-3-animation展示两帧相同 vs 帧2明显前冲的动画对比让学生自己感受差距 |
| M5 | 属性随便填,反正都是数字 | 属性是角色的「打法定位」这是策略设计不是填表格20分预算是有限资源怎么分配决定了这个角色的战斗风格 | 类比:篮球队里有中锋/控卫/得分手,不是每个人都练同一个技能;问「你是想先手打爆对方,还是慢慢耗死对方?」 |
---
### 3. 教学准备
**工具与环境:**
- 每台电脑已登录 Kimi网页版网络正常
- 每台电脑装有 Trae IDE可以创建 HTML 文件并用浏览器预览
- 教师电脑准备好 demo-pk 目录下的两个 demo 文件:
- `demo-1-draw-tool.html`(完整画图工具,备用展示)
- `demo-3-animation.html`角色动画展示Connect 环节使用)
**教学资源:**
- 教师准备:打开 `demo-3-animation.html`,导入一个提前画好的临时涂鸦角色 Spritesheet用于 Connect 导入
- 教师准备「画图工具需求文档模板」文字版见第5节投屏展示
- 教师准备一份「属性分配表」展示四种打法定位见第5节
- 学生资源:上节课完成的俄罗斯方块作品(精神延续,不需要文件)
**教师备课体验任务:**
> 备课前,教师必须亲自完成以下操作:
>
> 1. 走完完整的 Plan Mode 三步用窗口A整理画图工具需求文档 → 窗口B审核记录 AI 问了哪些你没想到的问题)→ 窗口C生成画图工具
> 2. 用生成的画图工具画一个角色帧1待机 + 帧2攻击感受64×64像素格子的限制和乐趣
> 3. 故意画一个「两帧完全相同」的角色,导出预览,体验「没有动画感」的效果——备用作为 M4 反例
> 4. 验证 `demo-3-animation.html` 的导入功能是否正常,准备一张 128×64 的 Spritesheet PNG
---
### 4. 教学流程
---
**第一幕:联系 (Connect) — 10分钟** 🔗
*本幕目标:用上周挑战回顾激活工程思维记忆;用 demo-3-animation 的动画展示制造「哇!」的感受,建立今天的项目目标*
**【环节】上节课挑战回顾 (3分钟)**
**师:** 上节课我布置了一个5分钟挑战——给测试脚本补一条没有被覆盖的规则。谁补了补了什么
**生:** (预期 A我补了一条测试「方块碰到左边界不能出去」
**师:** 具体怎么补的?你是发现了哪条规则测试脚本没写?
**生:** (预期:测试脚本里没有检查边界,我就加了一个模拟按键到最左边的测试)
**师:** 很好——你不只是「加了一条」,你先发现了漏洞,再去补。这就是工程师的思维方式:不是随便加,而是找到没覆盖的地方补上去。
【诊断点:学生能否清晰描述「我发现了什么漏洞 → 我用什么方法补上去了」,而不只是「我加了一条测试」】【理解层】
**【分支A】若学生能说出具体的漏洞和补法**
**师:** 这条补得很到位。以后做任何项目,验收脚本里「没写到的地方」就是最容易漏 bug 的地方。
**【分支B】若学生说「我没有补不知道怎么补」**
**师:** 没关系,今天我们马上会用到同样的思路——先写需求,需求就是验收的标准。你今天会自己搞清楚这个流程。
**【分支C】若没有学生做了挑战**
**师:** 好,我来快速过一遍——测试脚本的核心作用是什么?
**生:** (预期:验证游戏规则有没有做对)
**师:** 对。「没被覆盖的规则」就是「测试脚本没想到的情况」。记住这个概念,今天会继续用到。
---
**【环节】情景导入 (7分钟)**
**师:** 上节课我们用 Plan Mode 完成了俄罗斯方块。今天我们开启一个全新项目——涂鸦PK。
**师:** 先不解释「PK」是什么我先让你们看一个东西。
【投屏打开 demo-3-animation.html】
**师:** 这是一个角色动画展示器。你们看——这两个角色在做什么?
**生:** (预期:在动!/ 一个在走路,一个在打架)
**师:**这是两帧动画——帧1是待机状态帧2是攻击状态快速交替播放就有了动感。但是这两个角色是我临时画的我花了大概5分钟。
**师:** 现在,我要把这个临时角色换成你们自己画的角色。
【教师演示导入提前准备的临时涂鸦 Spritesheet动画播放起来】
**师:** 这个动画里的角色,是用一个 HTML 画图工具做的。这个画图工具——是我们今天自己写出来的。
**师:** 今天的任务,两件事:第一,用 Plan Mode 做出一个画图工具第二用这个工具画你自己的战斗角色——帧1待机帧2攻击。
**师:** 上节课用的 Plan Mode 三步,还记得吗?
**生:** 预期记得窗口A整理需求窗口B审核窗口C执行
**师:** 对,一模一样的流程。只是这次做的不是游戏,而是工具。先造工具,再用工具。
【诊断点:学生是否能自己说出 Plan Mode 三步,而不是被教师提示】【识别层】
---
**第二幕:建构 (Construct) — 65分钟** 🛠️
*本幕目标:完整走完 Plan Mode 三步生成画图工具;逐条验收画图功能;用生成的工具画出两帧角色并填写属性 JSON*
---
**【分段一Plan Mode — 画图工具需求文档】(25分钟)**
*本段重点引导学生从「我要做什么」出发用窗口A整理需求文档再用窗口B新窗口审核工程师发现漏洞补充完整后准备提交*
**预设误概念:**
- 误概念 M1需求写得越多越好越长越安全
- 误概念 M2直接让 AI 写代码更快,不需要文档
- 误概念 M3需求审核就是自己再读一遍不用开新窗口
**讲解与演示 (Teach & Demo): (8分钟)**
**师:** 开始 Plan Mode 第一步——整理需求。我们要做的画图工具有哪些功能?先不急着写,我们来想一想,这个工具要能做什么。
**师:** 用这个画图工具,最后要交出一张图。这张图有什么要求?
**生:** (预期:要是方块组成的 / 要有两帧 / 颜色要能选)
**师:** 我来把这些需求点梳理一下一共6个部分。你们跟着我一起确认每个部分的内容然后我们用 AI 整理成规范文档——
**师:** 第一部分——画布大小。我们用 64×64 像素,但显示的时候放大 8 倍显示,这样格子就有 512×512 的大小,不会太小看不清。
**师:** 第二部分——工具。需要三个:画笔(点击涂色)、橡皮(把像素变透明或白色)、填充桶(把一片同色区域一起换色)。
**师:** 第三部分——调色板。至少 16 种颜色,支持自定义颜色选择器。
**师:** 第四部分——多帧编辑。这是最关键的——我们有两帧帧1是待机姿势帧2是攻击姿势可以切换编辑而且可以把帧1复制到帧2作为修改起点。
**师:** 第五部分——动画预览。在工具里就能看到两帧切换的动画效果,不用每次都导出去看。
**师:** 第六部分——导出。生成一张 128×64 的 PNG 图片,两帧横向拼在一起,就是 Spritesheet。
**师:** 这 6 个部分,每一个都有一个关键问题:「怎么测试它做对了?」这叫需求可测试性。比如「画布 64×64 像素」——怎么测试?
**生:** (预期:在画布上画一条线,看有没有 64 格 / 在画布最边上点看坐标是不是6363
**师:** 对,能说出测试步骤,说明需求写清楚了。如果是「颜色要好看」——怎么测试「好看」?
**生:** (停顿)……好像测不了……
**师:** 对,「好看」不可测试。所以需求文档里不能出现「好看」「流畅」「方便」这类词。要写具体的、能测试的描述。
【理解层:建立「需求可测试性」的直觉,澄清 M1 误概念】
**师:** 现在看一下今天用的三步流程——
**师:** 窗口A——打开 Kimi把这6个需求要点告诉 AI请它整理成规范的需求文档五部分格式功能描述/触发条件/功能规则/交互规则/验收标准)。
**师:** 窗口B——全新 Kimi 对话,把文档发给「审核工程师」,让它只问问题不给答案,找出所有描述不清楚的地方。
**师:** 窗口C——再新一个 Kimi 对话,把补充完整的需求文档发给它,生成画图工具的 HTML 代码。
**师:** 我来演示窗口A这一步。
【教师打开 Kimi用保底提示词一见第5节提交投屏展示 AI 整理出来的需求文档结构】
**师:** 你看 AI 给我整理出来的格式——每个功能都有「功能规则」和「验收标准」。你们对比一下AI 加的这些验收标准,我在口头说的时候有没有提到?
**生:** (预期:有几条我没说 / 填充桶的那条我没想到边界情况)
**师:**AI 帮我补了一些我没说清楚的细节。但注意——AI 整理的内容可能有你不想要的,也可能漏掉你想要的。整理完之后要自己过一遍,确认每条都是你真正想要的。
【应用层学生开始区分「AI 帮我补充的」和「我自己想要的」】
**学生实践 (Practice): (12分钟)**
学生操作:
1. 打开 Kimi新建对话窗口A
2. 根据教师给出的保底提示词一,补充自己想要的特殊需求,提交
3. 拿到 AI 整理的需求文档,自己读一遍,圈出「这条我没提到的」和「这条说法我不认可的」
4. 打开新的 Kimi 对话窗口B把文档和审核提示词一起提交
> 教师走动观察重点:
> - 学生在窗口A拿到文档后有没有真的读了一遍还是直接复制粘贴给窗口B「读一遍」是诊断这步的关键行为。
> - 窗口B的 AI 问了哪些问题?有没有学生对某个问题感到惊讶——「这个我真的没想过」是好信号。
> - 是否有学生在同一个对话窗口里同时做整理和审核?这是 M3 误概念的表现,需要介入。
**进度同步 (Checkpoint): (5分钟)**
**师:** 窗口B审核出来了AI 问了你哪个最意外的问题?谁来说一下?
**生:** (预期 A它问我「橡皮是把像素变透明还是变白色」我没想过这两种不一样
**师:** 好——这两种有什么区别?
**生:** (预期:透明就是真的没颜色,白色就是变成白色格子)
**师:** 对,区别很重要。如果背景是黑色的,你用橡皮想抹掉一块,变成「白色」就是留了个白点,变成「透明」才是真正删掉。你准备选哪个?
**生:** 透明!
**师:** 那就在你的需求文档里补上「橡皮擦除后像素变为透明alpha=0」。
【诊断点:学生能否说出一条「因为 AI 审核问了这个问题我补充了这条描述」而不是「AI 问了很多,我不知道填哪些」】【理解层】
**【分支A】若学生能说出具体补充了什么**
**师:** 很好。需求每补充一条,代码出问题的概率就降低一点点。这就是 Plan Mode 的价值。
**【分支B】若学生说「AI 问了很多但我觉得都不重要」:**
**师:** 好,拿出来看看。最意外的那条是哪个?我们一起判断——这条如果不补,代码生成出来会发生什么?
(引导学生预测后果,建立「需求漏洞→代码行为异常」的因果感)
**【分支C】若学生在同一窗口做了整理和审核**
**师:** 我注意到你在同一个对话框里做的审核。还记得上节课说的「上下文污染」是什么意思吗?现在再开一个全新对话,把文档重新发过去,对比一下这次 AI 问的问题和刚才有没有不同。
---
**【分段二窗口C生成画图工具 → 逐条验收 → 溯源修复】(20分钟)**
*本段重点:提交需求文档生成画图工具;建立「按需求逐条验收」的习惯;遇到问题先溯源需求再修改*
**预设误概念:**
- 误概念 M2直接改代码比溯源需求更快
- 误概念:看起来能用就算完成,不需要逐条验收
**讲解与演示 (Teach & Demo): (3分钟)**
**师:** 需求文档补完了进入窗口C——提交生成。把补充完整的需求文档粘贴进新的 Kimi 对话,加上保底提示词三,提交。
**师:** 生成完之后,你会拿到一段 HTML 代码。把这段代码复制出来,在 Trae 里新建一个 `draw-tool.html` 文件,粘贴进去,保存,用浏览器打开。
**师:** 打开之后——不要急着画。先做一件事:用你的需求文档,逐条验收。验收方式:你需求文档里写了什么,就测什么。
**师:** 我给你们一个最快的验收清单——五条最关键的:
【投屏展示验收清单】
```
1. 画笔工具:选颜色,点击画布能涂色
2. 橡皮工具:点击有颜色的格子,变为透明/白色
3. 填充桶:点击一片同色区域,整体换色
4. 多帧切换点「帧1」「帧2」Tab画的内容各自独立
5. 动画预览:点播放,两帧交替显示
```
**师:** 这五条,每条真的去操作一次,不能只「看起来好像对」。有一条不通过就标叉,写下「实际看到了什么现象」。
【应用层:建立「验收 = 逐条测试 + 记录现象」的习惯】
**学生实践 (Practice): (13分钟)**
学生操作:
1. 在 Trae 新建 `draw-tool.html`,粘贴 AI 生成的代码,浏览器打开
2. 按五条验收清单逐条测试,记录「通过✓」或「不通过✗ + 实际现象」
3. 遇到「不通过」先不改代码——找回需求文档,找到是哪条需求没说清楚,标注出来
4. 把找到的漏洞描述清楚(「步骤→预期→实际」格式),让 AI 针对性修复
> 教师走动观察重点:
> - 有没有学生遇到 bug 直接问 AI「帮我改这个 bug」而没有先溯源需求主动走过去问「你知道是哪条需求没说清楚吗
> - 有没有学生卡在代码生成失败(如 AI 输出不完整)?准备好备用的 demo-1-draw-tool.html但先引导学生尝试重新生成
> - 验收清单有没有学生不知道「如何测试填充桶」?示范:先用画笔画一个封闭区域,再用填充桶点内部
**进度同步 (Checkpoint): (4分钟)**
**师:** 五条验收,全部通过的举手。
**师:** 好。有没有人验到「不通过」的?说一下你发现了什么。
**生:** (预期 A帧1和帧2切换但画的内容是共用的切换没效果
**师:** 好,你溯源到需求文档了吗?哪条需求没写清楚?
**生:** (预期:我写了「可以切换帧」,但没写「两帧的数据是独立存储的」)
**师:** 对,这就是漏洞。「切换帧」和「数据独立」是两件事,你只说了一件。现在补上这条,用「步骤→预期→实际」格式描述给 AI让它修复。
【诊断点:学生能否从「出了 bug」出发溯源到「是哪条需求没写清楚」而不是直接「AI 帮我改一下」】【应用层】
**【分支A】若学生完成了溯源并修复了 bug**
**师:** 这是真正的工程师思维——不是改代码,是改需求,然后重新生成。
**【分支B】若学生直接把代码发给 AI 说「帮我改」:**
**师:** 先停一下。你现在让 AI 改的是代码,但 AI 不知道你要的是什么效果。你能不能先找到需求文档里「两帧切换」那一条,把它描述得更清楚再提交?
**【分支C】若 AI 生成的代码完全无法运行(语法错误):**
**师:** 遇到这种情况,先用「步骤→预期→实际」描述给 AI「我打开 HTML 文件,浏览器显示空白/报错,期望看到画图工具界面」。让 AI 自己排查哪里有问题。如果还不行,可以用老师电脑上的备用 demo 继续后面的步骤——画角色才是今天更重要的部分。
---
**【分段三用工具画角色帧1待机 + 帧2攻击+ 填属性JSON】(20分钟)**
*本段重点用画图工具完成角色设计理解两帧差异的审美原则用20分预算制选定打法定位并填写属性*
**预设误概念:**
- 误概念 M4两帧画一样就行动画不重要
- 误概念 M5属性随便填反正都是数字
**讲解与演示 (Teach & Demo): (5分钟)**
**师:** 画图工具做好了。现在正式开始画你的战斗角色。
**师:** 先确认一件事——帧1和帧2要画什么
**师:** 帧1是「待机」姿势——就是角色站在那里什么都没发生。这一帧决定了角色的整体外形和配色。
**师:** 帧2是「攻击」姿势——角色在出击的那一瞬间。这一帧决定了动画的冲击感。
**师:** 我让你们看一个对比。
【投屏打开 demo-3-animation.html分别导入两个角色一个帧1和帧2完全一样一个帧2明显前冲】
**师:** 哪个更有打击感?
**生:** 第二个!第一个完全没动……
**师:**帧1和帧2差异越大动画的冲击感越强。帧2复制帧1不修改等于没有动画。攻击帧要做什么肢体前冲、武器伸出去、颜色变化——让角色看起来「正在做某件事」。
【识别层建立「帧1待机 vs 帧2攻击」的视觉差异意识澄清 M4 误概念】
**师:** 画完之后我们要给角色填属性。属性系统用的是「20分预算制」——你有20分分配完就没了怎么分是你的策略。
**师:** 四个属性:
【投屏展示属性表】
```
HP生命值1分 = 10血 范围 3-10分
ATK攻击力1分 = 5伤害 范围 2-10分
DEF防御力1分 = 3减伤 范围 0-6分
SPD速度1分 = 1速度值 范围 1-6分
```
**师:** 我给你们四种打法定位参考,你选一种,或者自己设计——
```
坦克型HP 8 + ATK 4 + DEF 6 + SPD 2 = 20分耗死对方
刺客型HP 4 + ATK 10 + DEF 0 + SPD 6 = 20分一击毙命
平衡型HP 5 + ATK 5 + DEF 4 + SPD 6 = 20分没有弱点
速攻型HP 6 + ATK 7 + DEF 1 + SPD 6 = 20分先手压制
```
**师:** 注意——你的角色外观要和你的打法定位「匹配」。刺客型画个超大的圆圆的胖角色,感觉不对;坦克型画个细细的小人,也感觉不对。角色的视觉设计要传达出它的战斗风格。
**师:** 还有一个特技系统——每个角色可以选一个特技,不占预算:
```
🔥燃烧:每回合额外-5血持续3回合
🛡护甲:首次受击伤害归零
⚡连击25%概率攻击两次
💉吸血攻击回复伤害30%血量
❄️冰冻:每场一次,对方跳过下回合
🎯穿透:一次攻击无视防御
```
**师:** 选好了吗?现在开始画。先定打法定位,再动手画角色外形。
【应用层:学生开始从策略角度设计角色,而不是「随便画一个」】
**学生实践 (Practice): (12分钟)**
学生操作:
1. 在画图工具里,先选定自己的打法定位(坦克/刺客/平衡/速攻,或自定义)
2. 在帧1画待机姿势——确定角色外形、配色、整体风格
3. 点「复制帧1到帧2」在帧2的基础上修改攻击姿势肢体前冲、武器伸出、颜色变化等
4. 预览动画确认帧1和帧2有明显差异
5. 完成后,在 Trae 新建一个 `character.json`,填写属性数据:
```json
{
"name": "你的角色名",
"hp": 50,
"atk": 25,
"def": 12,
"spd": 4,
"special": "⚡连击",
"description": "一句话描述你的打法"
}
```
> 教师走动观察重点:
> - 有没有学生帧2完全没改只是复制了帧1走过去问「攻击的时候角色在做什么动作
> - 有没有学生分配完属性后发现「总分超了20分」引导他重新算一下
> - 关注速度慢的学生如果卡在画角色帧2可以暂时只改一两个像素保证完成基本的两帧结构细节可以之后优化
**进度同步 (Checkpoint): (3分钟)**
**师:** 谁的帧2跟帧1有明显不同的把动画预览给大家看一下。
**生:** (学生展示动画预览)
**师:** 帧2里角色做了什么动作
**生:** (预期:他的手臂伸出来了 / 整个身体往前倾了)
**师:** 这就是攻击感。你们的打法定位是什么型?
**生:** 刺客型!
**师:** 刺客型——高攻低血。碰到坦克型怎么办?
**生:** (预期:先手攻击!速度快,先打对方)
**师:** 好,这说明你的策略是清晰的。你知道你的角色能赢谁,也知道怕谁——这才是策略设计。
【诊断点:学生能否说出自己的打法定位,并能解释「这个定位的优势和弱点」,而不只是「我觉得这个好看」】【应用层】
---
**第三幕:反思 (Contemplate) — 10分钟** 🤔
*本幕目标2-3名学生展示角色并说出打法定位的策略思考让其他学生从「对抗」角度给出评价*
**【环节】成果展示 (6分钟)**
**师:** 现在我们来做一个角色路演。每个人只说三件事:第一,你的角色叫什么名字;第二,你的打法定位是什么型;第三,你的特技是什么,为什么选这个。
**师:** 谁先来?
【1-2名学生上来展示角色动画说出三件事】
**师:** (对展示的学生)你是刺客型,特技选了「穿透」。你的思路是什么?
**生:** (预期:因为刺客型攻击高,加上穿透直接无视防御,坦克型也扛不住)
**师:** 这个搭配有意思——高攻 + 穿透,专门克制高防御的坦克。但是你血量很少,碰到先手比你快的角色怎么办?
**生:** (预期:就……输了? / 要速度快才行)
**师:** 所以你的角色有一个明显弱点。这不是坏事——有弱点才有对抗的乐趣。
**【环节】互评与讨论 (4分钟)**
**师:** 大家来评价刚才展示的这个角色——一个优点 + 一句「如果我拿XX型去打他我的策略是」。
**生:** (预期:优点是攻击穿透很厉害;如果我用速攻型,速度比他快,先手打他,因为他血少可能一下就输了)
**师:** 很好——你在想「反制策略」了。这就是涂鸦PK的乐趣不只是画角色而是设计一个有自己思考的战斗方案。
**师:** 帧2动画有一个想问的——你们觉得帧2改变得最大、最有攻击感的是哪个角色
【学生投票或说出谁的】
**师:** 为什么你觉得那个最有攻击感?
**生:** 预期因为他帧2整个身体都往前扑了和帧1差别很大
**师:** 对——**差异越大,冲击感越强。** 这是今天审美设计的核心原则,记住它。
---
**第四幕:延续 (Continue) — 5分钟** 🚀
**【环节】抽象总结 (3分钟)**
**师:** 今天我们做了两件事:先用 Plan Mode 做出了画图工具然后用工具画了角色、填了属性。我来问你们——今天用的三步流程窗口A整理、窗口B审核、窗口C执行和上节课做俄罗斯方块时有什么一样的地方
**生:** (预期:流程完全一样 / 都是先写需求再生成)
**师:** 对——**流程是可以迁移的。** 这套流程不只用来做游戏,不只用来做画图工具。以后你们要做任何稍微复杂一点的项目,都可以用这三步。
**师:** 今天还有一个新东西——「先造工具,再用工具」。你们不是下载别人的画图软件来用,而是自己写了一个。这个思路以后还会用到:当你发现某个工具不好用,或者根本没有,你可以自己造。
**师:** 今天的「审美力」训练,核心是什么?
**生:** 预期帧1和帧2差异要大 / 攻击帧要有动感)
**师:**帧2差异越大动画越有冲击感。这条原则不只用在像素画——你以后做任何动画、做任何交互「差异对比产生冲击感」都是一条通用的审美原则。
【迁移层:将「先造工具」「差异产生冲击感」提炼为可迁移的思维模型】
**【环节】下节预告 + 5分钟挑战 (2分钟)**
**师:** 下节课——涂鸦PK。你们今天画好的角色和属性会被真正用来对战。我们要搭建一个战斗引擎让你的角色和同学的角色打起来看看谁的策略赢。
**师:** 想一想——你的打法定位,碰到哪种对手会输?下节课我们来验证。
**师:** 本周 5 分钟挑战:用你今天做的画图工具,帮一个同学画一个角色,然后对比和他自己画的有什么不同——外形、配色、攻击姿势,哪里不一样?拍照或截图,下节课分享。
---
### 5. AI助教使用指南
**教师演示用提示词需求文档整理窗口A**
```
你是一个需求分析师。我要做一个HTML5像素画图工具功能是64×64画布、8倍缩放显示、支持画笔/橡皮/填充桶、有调色板、两帧编辑帧1待机/帧2攻击、可复制帧1到帧2、有动画预览、能导出128×64 Spritesheet PNG。请把这些需求整理成规范的需求文档包含功能描述、触发条件、功能规则、交互规则、验收标准五个部分。
```
**审核用提示词窗口B——新窗口不要在整理窗口里使用**
```
你是一个挑刺工程师。下面是一份画图工具的需求文档,请只提出你觉得描述不清楚或有遗漏的问题(每个问题一行),不要给答案,不要写代码:
[粘贴需求文档]
```
**学生保底提示词生成画图工具窗口C**
```
请帮我用HTML5 Canvas做一个像素画图工具64×64像素画布、8倍缩放显示、支持画笔/橡皮/填充桶、有颜色调色板至少16色、有两帧帧1待机/帧2攻击可切换编辑、可复制帧1到帧2、有动画预览循环播放、能导出128×64 Spritesheet PNG。单文件HTML不用任何外部库。
```
**进阶提示词(属性 JSON 生成辅助):**
```
我的角色是刺客型打法是先手高爆发。20分预算制HP/ATK/DEF/SPD各有范围限制HP:3-10ATK:2-10DEF:0-6SPD:1-6。请帮我给出一个满足20分总预算、符合刺客型定位的属性分配方案并说明每个属性为什么这样分配。
```
**属性分配参考表(投屏展示用):**
| 打法定位 | HP | ATK | DEF | SPD | 合计 | 核心优势 |
|---------|----|----|-----|-----|-----|---------|
| 坦克型 | 8 | 4 | 6 | 2 | 20 | 血厚扛伤害,慢慢耗死对方 |
| 刺客型 | 4 | 10 | 0 | 6 | 20 | 先手高爆发,一击毙命 |
| 平衡型 | 5 | 5 | 4 | 6 | 20 | 无明显弱点,应对各种对手 |
| 速攻型 | 6 | 7 | 1 | 6 | 20 | 先手压制,中等爆发 |
---
### 6. 教师指南
**本课技术备注:**
- **Spritesheet 格式**128×64 PNG左半0-63像素宽是帧1右半64-127像素宽是帧2。对战引擎会按这个坐标裁取每帧。导出时必须确保两帧横向拼接否则下节课的战斗引擎无法正确读取。
- **Canvas 像素操作**:画图工具底层用 `getImageData/putImageData` 操作像素数组。每个像素有 RGBA 四个值各0-255。橡皮设为「透明」等于把该像素的 alpha 通道设为 0。这部分不需要传达给学生但教师需要了解以便解释「橡皮变透明 vs 变白色」的区别。
- **填充桶(洪水填充)**:用 BFS 或 DFS 算法扩展同色区域。如果学生报告「填充桶没有反应」,可能是起点像素和周边颜色完全相同导致;让学生先用画笔画一个封闭区域再测试。
- **帧数据独立存储**:每帧用一个独立的 `Uint8ClampedArray` 存储像素数据。切换帧时先保存当前帧数据,再加载目标帧数据。如果生成的代码两帧共用数据,需要在需求里补充「每帧像素数据独立存储,互不影响」。
- **导出 PNG**:用 `canvas.toDataURL('image/png')` 获取 base64 编码后触发下载。导出前需要把两帧数据拼接到一个 128×64 的临时 canvas 上再导出。
**常见问题 FAQ**
| 问题 | 应对 |
|------|------|
| 「AI 生成的代码报错,打开是空白页」 | 让学生把报错信息(浏览器 F12 控制台)截图给 AI用「步骤→预期→实际」描述问题让 AI 修复 |
| 「橡皮擦了但颜色还在」 | 先确认是否切换到了橡皮工具;如果工具选对但无效,可能是代码把橡皮当成了「白色画笔」,需要溯源需求文档里橡皮的功能描述 |
| 「填充桶整个画面都变色了」 | 这是填充桶「溢出」问题——起点像素周围没有封闭边界,颜色扩散到整个画布。让学生先用画笔封闭区域再用填充桶 |
| 「帧1和帧2切换后内容一样」 | 需求文档里需要补充「两帧像素数据独立存储」;溯源后让 AI 修复数据存储逻辑 |
| 「导出的 PNG 只有一帧」 | 需求里「128×64 Spritesheet」的描述可能没说清楚是「两帧横向拼接」补充描述后重新生成导出功能 |
| 「20分分配不够不知道怎么算」 | 给学生纸笔,列出四个属性的分值,加减法算一遍;主要目的是让学生思考优先级,不是数学练习 |
| 「帧2不知道怎么画才有攻击感」 | 给三个具体操作方向1身体/手臂前倾2把武器或特效从帧1的位置移动到「伸出去」的位置3帧2加一些颜色特效爆炸光晕、冲击线条 |
**课堂风险预案:**
- **AI 服务不可用**:使用 `demo-1-draw-tool.html` 作为备用画图工具,跳过 Plan Mode 生成步骤,直接让学生用 demo 画角色。Plan Mode 三步可以在白板上演示讲解,不需要真的生成。
- **学生进度差异过大**:画图工具生成完成的学生直接开始画角色;还在 Plan Mode 阶段的学生可以在分段三时使用教师备用的 demo-1-draw-tool.html 画角色,保证每位学生都能产出两帧角色。
- **属性分配争议(学生觉得某个定位「必胜」)**:预先告知下节课会有实际对战,不同对手克制关系不同,没有「必胜型」。这个问题留到实战后讨论,今天重点是把角色画完。
---
### 7. 5分钟日常AI挑战
**本周挑战:** 用画图工具帮同学画一个角色,然后对比差异
**挑战说明:**
用你今天做好的画图工具帮一个同学家人或朋友也行画一个角色帧1待机 + 帧2攻击然后让他自己也画一个版本对比两个版本有什么不同外形、配色、攻击姿势哪里不一样想一想为什么会不一样是因为你们对「好看」的标准不同还是因为对「攻击感」的理解不同
**下节课分享:** 下节课选2-3位同学展示「你画的版本」和「同学画的版本」说出最大的一处差异是什么。
---
### 8. 拓展任务
**拓展一(推荐):** 设计一个「克制链」——你的角色克制哪种打法定位的对手被哪种打法定位克制用文字写出来「我是XX型我克制XX型因为……我怕XX型因为……。」然后想想下节课对战时要不要调整属性。
**拓展二(挑战):** 给你的角色再画一帧「受伤帧」——当角色被攻击时显示的状态帧3。试着修改画图工具的代码让它支持三帧编辑并在动画预览里加入「受伤」动画。

View File

@@ -0,0 +1,605 @@
---
课时: 9
主题: 涂鸦PK— 基础对战系统
核心能力: [拆解力, 韧性力]
核心工具: [Trae IDE, Kimi]
时长: 90分钟
透明化层级: 过程层
适用路线: AICODE-06
---
### 1. 课程目标
**知识目标:**
- 理解「边界情况」的概念:任何系统都有正常路径以外的边缘场景,这些场景必须在需求里明确定义,否则 AI 的行为不可预测
- 理解「独立窗口审核」原则:需求文档、边界审核、代码生成、测试验证各用独立会话,防止 AI 自我偏袒
- 理解「测试脚本」的价值:用代码来验证代码,比人工点击效率高十倍,且可重复执行
**能力目标:**
- 能用 Plan Mode 整理战斗系统需求文档,并用窗口 B 发现边界情况(拆解力)
- 能把自己的 Spritesheet 导入对战系统,完成逐条验收,遇到 bug 能描述「步骤→预期→实际」(韧性力)
- 能让 AI 生成测试脚本,读懂「✅通过/❌失败」的输出,并定位到需求文档中对应的条款
**情感目标:**
- 建立「测试不是挑刺,是保护」的意识——有 bug 的游戏比没有游戏更让人沮丧,测试帮你在发布前修好它
- 体验「让 AI 测试 AI 写的代码」的效率感,而不是自己一条条人工验证
- 对「今天的角色真的打起来了」产生真实的成就感为第10课加动画做好期待
---
### 2. 核心概念与误概念预设
**核心概念认知层级:**
| 概念 | 学生类比 | 认知层级 |
|------|---------|---------|
| 边界情况 | 游戏规则书上写了「普通攻击」怎么算,但没写「两个人同时出手」怎么判——比赛现场这种情况真出现了,裁判不知道怎么判,整场比赛乱掉 | 理解层 |
| 独立窗口审核(新窗口原则) | 作文让同学评改:写作文的同学自己改,永远改不出大问题;换一个没看过你作文的同学来改,他会发现真正的漏洞 | 应用层 |
| 测试脚本 | 乐高玩具的「质检机器人」——每次按一个按钮,它自动把所有零件逐一检查一遍,告诉你哪个零件不对;比你自己一个个用眼睛看快一百倍,而且不会看漏 | 应用层 |
| 伤害公式 | 攻击力是出拳力气,防御力是护甲厚度,伤害 = 出拳力气 护甲厚度,最少也会有 1 点伤害(护甲再厚也会有点疼) | 识别层 |
| 先手机制 | 速度值高的人先出手,就像运动会百米跑步,反应快的人先起跑——如果两人反应时间一样,就抛硬币决定 | 识别层 |
**典型误概念表:**
| 编号 | 误概念 | 正确认知 | 激发策略 |
|------|--------|---------|---------|
| M1 | 游戏能跑就行,不用测试 | 有 bug 的游戏比没有游戏更难受;测试发现的 bug 才是可修复的 bug | 问:「你有没有玩过一个游戏,有个 bug 特别烦但一直不修?你是什么感受?」 |
| M2 | 测试脚本要自己写,太难了 | 测试脚本也可以让 AI 写,你只需要描述「我要验证什么结果」 | 演示:把「验证 ATK=15, DEF=5 时伤害=10」告诉 AI它直接给出测试代码 |
| M3 | 同速的情况 AI 会自动处理好 | 边界情况必须在需求里明确,否则 AI 行为不可预测(可能每次随机,也可能直接报错) | 问:「如果两个角色速度一样,谁先出手?你需求里写了吗?」 |
| M4 | 伤害公式在脑子里,不用写进需求 | 代码里的公式必须和需求文档一致,否则测试脚本无法验证;口头约定不算 | 演示需求里写「ATK 减 DEF」代码里却写成「ATK 乘以 DEF」逻辑完全不同 |
| M5 | 格挡和攻击同帧不会有问题 | 行动顺序是战斗公平性的核心;「同时出手」必须在需求里明确谁的优先级高 | 类比:石头剪刀布如果两人同时出,规则说谁赢?——必须有一个规则,不能靠 AI 猜 |
---
### 3. 教学准备
**工具与环境:**
- 每台电脑已安装 Trae IDEKimi 已登录,网络正常
- 每台电脑浏览器可以正常打开本地 HTML 文件
- 每台电脑上已有上节课完成的 Spritesheet PNG 文件128×64帧1待机+帧2攻击
- 每台电脑上已有上节课完成的角色属性 JSONhp/atk/def/spd/skill 字段20分预算制
- 投影可切换至任意学生屏幕
**教学资源:**
- 教师准备demo-2-pk-battle.html可直接运行的战斗演示开课前准备好用于 Connect 展示和 Contemplate 演示)
- 教师准备战斗需求文档范例见第5节完整版供学生卡壳时参考
- 教师准备测试脚本的预期输出截图4条全部 ✅ 通过的版本,用于对比)
- 学生资源:上节课完成的 Spritesheet PNG 和角色属性 JSON若有学生未完成教师提供默认模板
**教师备课体验任务:**
> 备课前,教师必须亲自完成以下操作:
>
> 1. 打开 demo-2-pk-battle.html完整打一局记录下「格挡+重击同回合」时伤害是怎么算的,以及「特技用完后再点特技按钮」的实际行为
> 2. 在窗口 A 整理一份战斗需求文档,然后在全新窗口 B 用审核提示词跑一遍,记录 AI 问出来的边界问题里哪几条最让你意外
> 3. 在窗口 D 生成测试脚本,验证伤害公式和先手机制,确认本课所有测试用例都能通过
> 4. 故意把伤害公式改成「ATK × DEF」重跑测试脚本截图「❌失败」的输出备课时展示用
---
### 4. 教学流程
---
**第一幕:联系 (Connect) — 10分钟** 🔗
*本幕目标:让学生展示上节课做的角色,说出打法定位和取胜策略,激活策略意识;教师演示可以运行的对战系统,建立「今天目标」的画面感*
**【环节】角色展示 + 打法定位 (7分钟)**
**师:** 上节课结束前,每个人都交了两样东西:一个 Spritesheet PNG一份角色属性 JSON。今天我们用这两样东西做什么
**生:** (预期:做游戏?让它打起来?)
**师:** 对。上节课大家做了什么,记得吗?
**生:** (预期:画了自己的角色 / 设计了角色属性)
**师:** 对。你们每个人手里有一个属于自己的角色:有自己画的图,有自己定的属性,还有自己选的特技。今天不是来欣赏它的——今天要让它真的打起来。
**师:** 在打之前,我先问每个人一个问题。谁来第一个回答——你的角色是什么打法定位?为什么你的角色应该赢?
【点一个学生,把他的 Spritesheet 投屏展示】
**生:** (预期 A「我是高攻击型我的 ATK 是 16我可以直接打穿对面」
**生:** (预期 B「我是高防御型我血多耗死对手」
**生:** (预期 C「我速度最快我先手每次都比对面早打」
**师:** 很好。你说的「先手」——如果两个角色速度值一样怎么办?
**生:** (预期:不知道 / 随机?/ 谁编号小谁先?)
**师:** 这就是我们今天第一件事要想清楚的——你脑子里的规则必须写成文字AI 才能按你想的来做。
【诊断点:学生能否意识到「打法逻辑」和「代码规则」之间有一层需要翻译的过程】【识别层】
**【分支A】若学生对打法定位有清晰的想法**
**师:** 很好,待会儿写需求文档的时候,你的打法定位就是你的出发点——你的角色是高攻击,那你的伤害公式、重击倍率、特技效果就都要围绕「攻击压制」来设计。
**【分支B】若学生说「我随便什么定位都行」**
**师:** 没关系,我们来帮你定位。你的 ATK 最高,还是 DEF 最高,还是 SPD 最高?
(引导学生从属性数值反推定位,而不是空想)
**【环节】展示今天目标 (3分钟)**
**师:** 我先给大家看一个东西。
【投屏打开 demo-2-pk-battle.html运行一局对战】
**师:** 你们看——左边是玩家角色,右边是 AI 对手,有血条,有行动按钮,有战斗日志。这就是今天下课前你们要做到的东西。
**师:** 但注意——你们做出来的版本,角色是你们自己画的,属性是你们自己定的。你的角色放在左边,跟 AI 对手打。今天不做动画,角色还是用矩形占位,但战斗逻辑是真实的。下节课我们把图换上去、加动画。
**师:** 今天的核心是:战斗规则要真的是你想的规则,而不是 AI 随机猜的规则。怎么保证?用 Plan Mode。
**师:** 今天我们会开 4 个窗口——窗口 A 写需求、窗口 B 审核边界、窗口 C 生成代码、窗口 D 做测试。这 4 个窗口,每个只做一件事,互相不干扰。记住这个结构,以后你做任何大一点的项目都可以用。
---
**第二幕:建构 (Construct) — 65分钟** 🛠️
*本幕目标:走完「需求文档→边界审核→代码生成→导入 Spritesheet→验收→测试脚本验证」完整闭环强化多窗口协作和边界意识*
---
**【分段一Plan Mode — 战斗系统需求文档】(20分钟)**
*本段重点:学生在窗口 A 整理战斗系统需求,在窗口 B 用「边界审核」提示词发现漏洞,补充进文档*
**预设误概念:**
- 误概念 M3同速情况 AI 会自动处理好,不用写
- 误概念 M4伤害公式在脑子里不用写进需求文档
- 误概念 M5格挡和攻击同帧不会有问题
**讲解与演示 (Teach & Demo): (7分钟)**
**师:** 我们先开窗口 A——这个窗口只做一件事整理需求文档。
**师:** 今天的需求文档比俄罗斯方块简单一点,但有一个新的挑战——这个系统里有很多「边界情况」。什么叫边界情况?就是不在「正常流程」里,但真实对战时会出现的场景。
**师:** 我给你们举几个例子。正常流程是什么玩家点攻击AI 点攻击,互相扣血,谁血量归零谁输。这是主路径,好想,好写。
**师:** 但是——如果两个角色速度值完全相同,谁先出手?这条你有没有想过?
**生:** (预期:没想过 / 随机?)
**师:** 还有——特技每场只能用一次。如果玩家特技已经用完了,再点「特技」按钮,游戏会怎样?按钮灰掉不能点?还是提示「已用完」?还是直接变成普通攻击?
**生:** (预期:好像要变灰 / 要提示吧)
**师:** 对。还有——格挡时受到的伤害减少 50%那对方用的是重击伤害×1.8),格挡减伤是基于重击后的伤害算,还是基于原始伤害算?
**生:** (预期:有区别吗……?哦,区别很大!)
**师:** 这些都是边界情况。这些情况如果你没有在需求里写清楚AI 会自己猜一个——可能猜得跟你想的不一样,可能每次结果还不同,可能直接崩掉。
**师:** 所以今天需求文档要写两轮。第一轮是窗口 A把主路径写出来。第二轮是窗口 B让 AI 扮演审核工程师,专门找你没考虑到的边界情况。
【投屏展示战斗需求文档的五个核心模块】
**师:** 战斗系统需求文档有五个必填模块。我过一遍每个的关键要点——
**师:** 模块一先手机制。你必须写清楚两条第一SPD 高的先出手——这好理解;第二,**同速时怎么处理**——你必须选一个方案,写进文档。你可以写「同速时随机决定」,也可以写「同速时玩家先手」,但必须选一个,不能空着。
**师:** 模块二伤害公式。写「ATK 减 DEF最低造成 1 点伤害」。注意「最低 1 点」这个细节必须写——如果不写当防御值大于攻击值时AI 可能算出负数伤害,变成治疗对手了。
**师:** 模块三行动类型。四种普通攻击、重击伤害×1.8)、格挡(本回合受伤减少 50%)、特技(每场只能用一次,效果来自角色 JSON 的 skill 字段)。
**师:** 模块四胜负判定。HP≤0 立即判输,游戏结束。
**师:** 模块五AI 对手决策逻辑。AI 对手不是乱打的——写清楚它的决策规则HP 低于 30% 时用特技25% 概率重击;否则普通攻击。
**师:** 我来快速演示一下这五个模块写出来是什么样的。
【投屏展示教师自己的需求文档草稿,只展示模块一和模块二,让学生对格式有直观感受,不逐字念,只指出关键字段】
**师:** 你们看模块一的「同速处理」我写了「同速时随机决定Math.random() < 0.5 为玩家先手」。你们不需要写代码,但要写清楚「同速时谁先」——随机、或者固定玩家先手,选一个写进去。
**师:** 模块二里,我特别标了「最低 1 点」并且加了验收标准:「当 ATK=3, DEF=8 时,伤害=1 而不是负数」。这就是一条可测试的需求——能写出具体数字的需求,才是真正写清楚了的需求。
**师:** 这五个模块写完,是你的「主路径」。然后我们开窗口 B让 AI 来挑刺。
**学生实践 (Practice): (10分钟)**
**师:** 现在打开 Kimi开一个新对话这是窗口 A。用我刚才说的五个模块把你的战斗系统需求文档写出来。不确定的地方先写一个版本待会儿窗口 B 会帮你发现问题。
> 教师走动观察重点:
> - 是否有学生跳过「同速处理」这条?走过去问:「你这里写了 SPD 高的先手,那两人 SPD 一样怎么办?」
> - 是否有学生漏写「最低 1 点伤害」?问:「如果对手 DEF 比你 ATK 还高,伤害是负数还是零?你想要哪个?」
> - 是否有学生把 AI 对手的决策逻辑写得很复杂(超过 3 条提醒「AI 对手逻辑先简单,战斗规则才是重点」
**进度同步 (Checkpoint): (3分钟)**
**师:** 五个模块都填了的举手。
**师:** 好,现在开窗口 B——新开一个 Kimi 对话,把你写好的需求文档整个复制过去,然后加上这句话:「你是一个游戏测试工程师,请列出这份需求文档里所有的边界情况和异常情况,只列问题,不需要解答。」提交。
【诊断点:学生是否能把「用窗口 B 挑需求漏洞」理解为「需求还没写完」,而不是「可以不管这些问题」】【理解层】
**【分支A】若学生窗口 B 得到了边界问题列表:**
**师:** 好,先把这些问题读一遍。你觉得哪条最重要——就是如果不处理,对战时一定会出问题的那条?
(让学生自己判断优先级,而不是逐条补充所有问题)
**【分支B】若学生说「AI 没问出什么有用的问题」:**
**师:** 我来帮你加一条——「同速时谁先出手」这条你文档里怎么写的?
(从教师预设的边界情况里选一条,帮学生发现漏洞)
**【分支C】若学生觉得「这些边界情况不重要先做再说」**
**师:** 好,我们来做个实验——你先做,遇到边界情况的时候我们再说。
(让学生先继续,在分段二验收时用真实 bug 来说明边界问题的后果)
**师:** 窗口 B 出来的问题,不需要全部解决——先挑最重要的 3 条,补充到你的需求文档里。什么叫「最重要」?就是如果不写清楚,对战时一定会出现的场景。「同速先手」、「特技用完后再点」、「格挡+重击叠加」——这三条必须在文档里有明确答案。
**师:**5分钟把窗口 B 给你的问题里最关键的几条回答了,补到需求文档里。
> 教师走动:重点帮学生判断哪条边界问题「必须解决」。判断标准——对战时能触发的频率高不高?触发了但没处理会崩游戏还是只是显示不对?优先处理会崩游戏的。
---
**【分段二:生成 PK 系统 → 导入 Spritesheet → 逐条验收】(25分钟)**
*本段重点:在窗口 C 生成战斗系统骨架,替换为自己的角色数据,验收核心功能*
**预设误概念:**
- 误概念 M1能跑就行不用验收每一条
- 误概念 M4遇到 bug 直接让 AI 改代码,不回需求文档溯源
**讲解与演示 (Teach & Demo): (5分钟)**
**师:** 需求文档确认好了,进入执行阶段。打开 Trae IDE新开一个文件命名为 pk-battle.html。
**师:** 这次生成分两步走。**第一步**:先生成「带占位图的骨架」——角色先用彩色矩形代替,重点让战斗逻辑跑通。**第二步**:骨架验收通过后,再把你的 Spritesheet 换进去。
**师:** 为什么要分两步?如果你一开始就把 Spritesheet 传进去,战斗逻辑有 bug 的话,你不知道是逻辑问题还是图片问题——两个问题混在一起,很难调试。先把逻辑跑通,再换图,问题清晰。
**师:** 现在开窗口 C——这个窗口专门做代码生成。把你的需求文档复制进去加上保底提示词提交。
【投屏展示学生保底提示词见第5节教师演示提交过程展示骨架生成结果】
**师:** 生成出来之后,把代码复制到 Trae IDE 里的 pk-battle.html保存用浏览器打开开始验收。
**师:** 验收怎么做?还是我们之前说的三步——「步骤→预期→实际」。你做了一个操作,你期望看到什么,实际看到了什么。如果期望和实际不一样,记下来,这就是一个 bug。
**师:** 验收清单,我们一起来定:
```
验收清单(至少验这 5 条):
1. 普通攻击 → 对手 HP 减少,减少量 = ATK - DEF最低 1
2. 格挡 → 下一次受到伤害减少 50%
3. 重击 → 伤害比普通攻击×1.8
4. 特技按钮 → 用完一次后变灰或显示「已用」
5. HP≤0 → 游戏结束,显示胜负
```
**学生实践 (Practice): (17分钟)**
第一步8分钟用保底提示词在窗口 C 生成战斗骨架,复制到 Trae IDE浏览器打开
第二步4分钟按验收清单逐条点击验收记录「通过/不通过」
第三步5分钟把验收通过的骨架里的角色数据替换为自己的角色属性 JSON把矩形占位符的颜色改成自己角色的代表色
> 教师走动观察重点:
> - 学生是否在逐条验收,还是点一下「感觉可以」就算通过?走过去问:「第 3 条重击倍率你验了吗?怎么确认它是 1.8 倍?」
> - 学生遇到 bug 时,是直接跟 AI 说「帮我修复」,还是先回需求文档确认这条需求怎么写的?
> - 有学生进展很快的:引导他多做一步——把 Spritesheet 也导入,用 `drawImage` 替换矩形,不需要动画,只是让图片显示出来
**进度同步 (Checkpoint): (3分钟)**
**师:** 谁来说一条验收结果——哪条通过了,哪条没通过?
**生:** (预期 A「普通攻击通过了格挡没通过格挡好像没有减伤」
**生:** (预期 B「特技用完之后按钮没变灰还能继续按」
**师:** 格挡没有减伤——这个 bug 我们用「步骤→预期→实际」来描述。步骤是什么?
**生:** 点格挡,然后对面攻击我。
**师:** 预期是什么?
**生:** 受到的伤害应该是正常伤害的 50%。
**师:** 实际是什么?
**生:** 受到的伤害跟没格挡一样。
**师:** 对。这个描述发给 AI加上一句「请检查需求文档里的格挡逻辑修复这个 bug」。注意——不是说「帮我改代码」是「检查需求文档对应的逻辑」让 AI 溯源。
【诊断点:学生是否会用「步骤→预期→实际」三要素描述 bug而不是只说「格挡不对」】【应用层】
**【分支A】若学生能准确描述 bug 三要素:**
**师:** 这就是专业的 bug 报告。真正的软件工程师报 bug 就是这个格式,你今天已经在用了。
**【分支B】若学生描述模糊「格挡感觉不太对」**
**师:** 「感觉不对」AI 不知道从哪里改。你告诉我——你点了格挡之后,对面攻击你,你的 HP 掉了多少?你预期应该掉多少?
(帮学生把感受翻译成具体数字)
---
**【分段三:窗口 D 生成测试脚本 → 验证核心逻辑】(15分钟)**
*本段重点:在独立窗口 D 生成测试脚本,运行 4 条测试用例,读懂「✅通过/❌失败」输出,体验「让 AI 测试 AI」的效率*
**预设误概念:**
- 误概念 M2测试脚本要自己写太难了我不会代码
- 误概念 M1能跑就行不需要写测试
**讲解与演示 (Teach & Demo): (5分钟)**
**师:** 你们刚才用手工逐条验收——点按钮,看结果。这样做是对的,但有一个问题:下次你改了一段代码,你要把所有条目再点一遍吗?
**生:** (预期:要的 / 好累 / 不想点)
**师:** 有一个更高效的方法——**测试脚本**。你告诉 AI「我要验证哪些规则预期结果是什么」AI 写一个 HTML 文件,你一打开,它自动跑完所有验证,每条显示「✅通过」或者「❌失败+原因」。
**师:** 这叫「用代码测试代码」。以后你每次改完代码打开测试脚本跑一遍5 秒看完所有结果,知道改了哪里有没有破坏原来的逻辑。
**师:** 注意——测试脚本必须在全新窗口 D 生成。为什么?因为如果你在窗口 C 生成测试脚本AI 知道自己刚刚写了什么代码,它的测试会偏向「帮自己的代码辩护」——故意写成能通过的测试,而不是真正验证逻辑是否正确。
**师:** 独立窗口 D让一个「什么都不知道」的 AI 来写测试,它才会真的按需求文档来验证。
【投屏展示窗口 D 测试脚本提示词见第5节演示把需求文档粘贴进去提交】
【投屏展示测试脚本的运行结果4条全部✅效果直观】
**师:** 这 4 条测试验证了什么——
```
测试 1ATK=15, DEF=5 → 伤害=10 ✅
测试 2ATK=5, DEF=10 → 伤害=1最低
测试 3SPD=8 vs SPD=5 → SPD=8 先手 ✅
测试 4ATK=10, DEF=2, 重击 → 伤害=14 ✅
```
**师:** 现在我把伤害公式故意改错——把「ATK - DEF」改成「ATK × DEF」再跑一遍。
【演示:故意修改代码,重跑测试脚本,出现 ❌ 失败】
**师:** 你们看——测试脚本立刻告诉我「测试1失败期望10实际75」。这就是测试脚本的价值改完代码立刻能发现问题不用自己重新点一遍。
**学生实践 (Practice): (8分钟)**
**师:** 现在你们来做。打开全新窗口 D用测试脚本提示词把你的需求文档粘贴进去生成测试脚本保存为 pk-test.html浏览器打开看结果。
> 教师走动观察重点:
> - 是否有学生在窗口 C 生成测试脚本(违反独立窗口原则)?立刻叫停,强调「必须是窗口 D全新对话」
> - 学生的测试结果有 ❌ 吗?引导他们找对应的需求条款,而不是直接改代码
> - 进度快的学生:引导拓展任务——「让 AI 帮你写一条需求里没有的边界测试,看是否通过」
**进度同步 (Checkpoint): (2分钟)**
**师:** 谁的 4 条测试全部通过的举手。
**师:** 有没有哪条测试失败了的?能说一下是哪条,失败的原因是什么?
**生:** 预期「测试3失败了说期望8先手但实际是5先手」
**师:** 这就是测试脚本的价值——它告诉你具体哪里不对。现在打开你的需求文档,找「先手机制」那条,看看写的是什么,再对照你生成的代码,找差异。这才是正确的调试方式。
【诊断点:学生是否能从「❌失败」的输出里,找到对应的需求文档条款进行溯源】【应用层】
**【分支A】若学生能自己完成溯源**
**师:** 你刚才做的就是「测试驱动调试」——测试报告告诉你哪里不对,你去需求里找根因,然后修代码。这是专业开发者的工作方式。
**【分支B】若学生看到 ❌ 直接去改代码而不溯源:**
**师:** 等等——你要改什么?测试说失败,但测试是按需求文档验证的。先问:你的需求文档里这条是怎么写的?再问:代码里是不是按这条写的?最后才改代码。
**【分支C】若学生的测试脚本全部通过但游戏实际有 bug**
**师:** 这很有趣——测试通过但游戏有 bug说明两件事之一第一测试脚本用的是独立实现和游戏代码逻辑不一样第二你的 bug 不在测试覆盖的范围里——是一个「需求没写到的边界情况」。你知道是哪种吗?
(引导学生区分「需求定义的 bug」和「需求未覆盖的 bug」
---
**第三幕:反思 (Contemplate) — 10分钟** 🤔
*本幕目标:两学生角色现场对战演示(投屏全班围观),展示「同一规则、不同角色」下的策略差异*
**【环节】角色对战演示 (6分钟)**
**师:** 好,现在我们来见证今天最重要的时刻——让两个同学的角色真的打一场。
【选两个进度最快的学生,把他们的 pk-battle.html 分别投屏,或者其中一个学生的对战画面投屏全班观看】
**师:** 我们先看左边——这个角色是什么打法?
**生(观察者):** (预期:高攻击型 / 速度型 / 防御型)
**师:** AI 对手用的是默认属性。先看第一回合谁先出手——
【运行对战,全班实时观看,教师边打边念战斗日志】
**师:** 看到没先手机制起作用了——SPD 高的先出手。现在玩家用重击——
【展示重击效果,强调伤害计算是按需求文档的公式来的】
**师:** 这场对战里有哪条规则你们觉得体现得最清楚?
**生:** (预期:先手 / 格挡减伤 / 特技限制次数)
**【环节】互评与讨论 (4分钟)**
**师:** 现在回顾今天的工作。今天你们打开了几个窗口,每个窗口做什么?
**生:** (预期:窗口 A 写需求,窗口 B 找边界,窗口 C 生成代码,窗口 D 做测试)
**师:** 对。这四个窗口,缺一个会出什么问题?比如如果跳过窗口 B——
**生:** (预期:边界情况没想到,游戏对战时可能出 bug
**师:** 如果窗口 D 不开新窗口,在窗口 C 里让 AI 测自己的代码——
**生:** 预期AI 会偏袒自己,测试结果不可信)
**师:** 今天遇到最大的挑战是什么?
**生:** (预期 A「边界情况想不到」/ 预期 B「看懂测试失败的原因」/ 预期 C「导入 Spritesheet」
**师:** 你们今天写需求、审核、生成、验收、测试——5 个步骤。这 5 步,哪一步对你来说最有收获?
**生:** (开放回应,教师倾听,不评判)
**师:** 我来说说我的观察——今天做得最好的事,是大家在遇到 bug 时,第一步不是「让 AI 帮我改」,而是先问「哪条需求没说清楚」。这个习惯,今天有人做到了,有人还在培养。没关系——这个习惯养成了,你做任何项目都会比别人少踩很多坑。
---
**第四幕:延续 (Continue) — 5分钟** 🚀
**【环节】抽象总结 (3分钟)**
**师:** 今天我们做了一件很重要的事——让「脑子里的规则」变成「代码里真正执行的规则」,中间经过了几个步骤?
**生:** (预期:写需求、找边界、生成代码、验收、测试脚本)
**师:** 对。这个流程不只是做游戏用的——以后你做任何项目,只要涉及「规则」,都要走这个流程。规则越复杂,「找边界」这步就越重要。
**师:** 今天你们掌握的能力有一个名字,叫**「测试驱动验证」**——先定规则,再写代码,再用测试检查代码是不是真的按规则跑。这是真实开发团队的工作方式,不是只有高手才能用的,你们今天已经用上了。
**师:** 还有一个能力——「边界意识」。以后遇到任何系统你的第一反应不是「正常情况怎么用」而是「边界情况、异常情况怎么处理」。这个意识让你的系统更稳定bug 更少。
**师:** 边界意识不只是写代码时用的。你们有没有玩过一个游戏,有个 bug 特别烦——比如某个角色在特定情况下无敌,或者某个技能叠加 20 层秒杀对手——这些 bug 从哪里来?就是设计师在写规则的时候,没有考虑到「如果玩家这样做怎么处理」。你们今天练习的,就是这个能力。
**【环节】下节预告 + 5分钟挑战 (2分钟)**
**师:** 现在你们的战斗系统能跑,逻辑是对的,但角色还是矩形占位符。下节课我们做什么?
**生:** (预期:换成自己的图?/ 加动画?)
**师:** 对——下节课我们把你的 Spritesheet 真正加进战斗系统,受击有闪烁效果,攻击有前摇后摇,死亡有倒下动画。你的角色会真的「活起来」。
**师:** 本周 5 分钟 AI 挑战——让 AI 帮你写一条需求文档里没有的边界情况测试,提交到测试脚本里,看是否通过。如果通过了,说明你的代码比需求文档还严格;如果没通过,说明你发现了一个新 bug。下节课分享。
**师:** 最后一件事——在 pk-battle.html 的顶部注释里,写上你的角色名、打法定位一句话总结,还有今天你发现的最有意思的一条边界情况。格式随意,只要下节课你打开文件,还能想起今天做了什么。
---
### 5. AI助教使用指南
**教师演示用提示词(窗口 A需求文档整理**
```
你是一个需求分析师。我要做一个基于 HTML/CSS/JS 的回合制战斗游戏,规则如下:
- 先手机制SPD 高的角色先出手;同速时随机决定(或编号小的先手,明确规定一种)
- 伤害公式ATK - DEF最低造成 1 点伤害
- 行动类型:普通攻击(按公式)/ 重击伤害×1.8/ 格挡本回合受伤减少50%/ 特技每场只能用一次效果来自角色JSON的skill字段
- 胜负判定HP≤0 判输,立即结束
- AI对手决策HP低于30%用特技25%概率重击;否则普通攻击
请把这些需求整理成规范需求文档,包含:功能描述、触发条件、功能规则、边界情况说明、验收标准。
```
**边界审核用提示词(窗口 B独立新对话**
```
你是一个游戏测试工程师。以下是战斗系统需求文档请列出所有你能想到的边界情况和异常情况例如同速时谁先出手特技已用完再点会怎样格挡叠加重击怎么算双方同时HP≤0怎么判只列问题不需要解答
[粘贴需求文档]
```
**测试脚本生成(窗口 D独立新对话**
```
以下是我的战斗系统需求文档:
[粘贴需求文档]
请帮我写一个 JavaScript 测试脚本(单文件 HTML验证以下4条规则
1. 伤害公式ATK=15, DEF=5 → 伤害=10ATK=5, DEF=10 → 伤害=1最低1点
2. 先手判定SPD=8 vs SPD=5 → SPD=8 的先出手
3. HP≤0 → 游戏结束标志为 true
4. 重击倍率ATK=10, DEF=2, 重击 → 伤害=(10-2)×1.8=14取整
每条测试显示"✅通过"或"❌失败期望XXX实际XXX"。用纯 HTML/JS不需要 Phaser.js。
```
**学生保底提示词(窗口 C生成战斗系统骨架**
```
请帮我用纯 HTML/CSS/JS单文件做一个回合制战斗游戏
角色数据从 JS 对象读取字段name/hp/maxHp/atk/def/spd/skill
- 玩家角色在左边AI 对手在右边
- 先用彩色矩形代替角色图片(稍后替换)
战斗规则:
- 先手SPD 高的先出手;同速时随机
- 伤害公式ATK - DEF最低 1 点
- 四种行动按钮:普通攻击 / 重击×1.8/ 格挡(本回合受伤-50%/ 特技(每场限用一次)
- AI对手逻辑HP<30%用特技25%概率重击;否则普通攻击
- HP≤0 判输,显示胜负结果,提供「再来一局」按钮
界面要求:
- 双方头顶有 HP 血条(数值显示)
- 底部有战斗日志最近3条
- 特技用完后按钮变灰不可点击
只给骨架,角色图片先用矩形占位,单文件 HTML。
```
**进阶提示词(替换 Spritesheet**
```
以下是我的 pk-battle.html 中绘制角色的代码片段:[粘贴相关代码]
请修改角色绘制逻辑,把左边玩家角色的矩形替换为 Spritesheet 图片显示:
- Spritesheet 文件名player-sprite.png128×64 像素
- 帧1x=0, y=0, w=64, h=64待机状态
- 帧2x=64, y=0, w=64, h=64攻击状态
- 平时显示帧1玩家攻击时显示帧2 持续 300ms 后恢复帧1
- 图片要等比缩放,显示在原来矩形的位置
```
---
### 6. 教师指南
**本课技术备注:**
- **Spritesheet 导入**`new Image()``onload``ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh)`。sx/sy 是帧在图片中的起始坐标sw/sh 是帧的宽高dx/dy 是画布上的目标位置dw/dh 是目标大小。教师需要理解这 8 个参数的含义学生不需要记只需知道「帧1=x0帧2=x64」。
- **回合制 vs 实时战斗**:本课做的是回合制(玩家点一次按钮 = 一个回合)。教师不要混淆,不要和「帧循环/requestAnimationFrame」扯在一起那是下节课动画的内容。
- **先手机制的实现**:在回合开始时比较 SPD高的先执行攻击逻辑低的后执行。同速时 `Math.random() < 0.5` 决定。这个逻辑放在「玩家点击行动按钮」的事件处理函数里。
- **格挡状态**:格挡是一个「状态标记」(如 `isBlocking = true`),在本回合受到伤害时检查这个标记,伤害减半后重置标记。注意:格挡对「下一次受到的攻击」生效,还是「本回合」生效,需在需求里明确(本课设计为「格挡后敌方先手攻击时生效」)。
- **测试脚本独立窗口**:这是本课最重要的原则。测试脚本里的战斗逻辑函数是「从需求文档重新实现」的独立版本,不引用 pk-battle.html 的代码——所以必须在独立窗口生成,让 AI 按需求文档写,而不是看着自己的代码写测试。
**常见问题 FAQ**
| 问题 | 应对 |
|------|------|
| 「特技按钮用了还能继续按」 | 引导学生检查需求文档里「特技每场限用一次」的验收标准写了什么,再在代码里找特技的触发逻辑,看有没有加 `skillUsed` 标记 |
| 「测试脚本生成出来全部通过,但游戏实际打起来伤害不对」 | 说明测试脚本和游戏代码用的是两套独立实现。先确认测试脚本按需求文档写,再对照游戏代码里的实现,找两者差异 |
| 「Spritesheet 图片不显示,一片黑」 | 99% 是路径问题——图片文件和 HTML 文件要放在同一个目录,路径写相对路径。让学生把 HTML 和 PNG 放到同一个文件夹再刷新 |
| 「重击的伤害计算和我预期不一样」 | 让学生打开战斗日志,找重击那行,用计算器手算一遍:(ATK - DEF) × 1.8,取整,对比日志里的数值 |
| 「AI 对手一直用特技,根本不普通攻击」 | AI 对手的决策逻辑里「HP<30%用特技」的判断可能有 bug——HP 初始值设置不对,或者条件写反了。引导学生检查 AI 对手的 maxHp 和当前 hp 的比较逻辑 |
| 「窗口 B 没问出什么问题」 | 提示学生在提示词末尾加一句:「请特别关注:同速先手、特技用完再点、格挡叠加重击、双方同时归零 这四个场景」 |
| 「测试脚本里 ✅ 全通过,但我不知道这说明什么」 | 说明你的战斗逻辑函数在这 4 种情况下是正确的。测试脚本是「护栏」——以后你改代码,再跑一次,如果还是全通过,说明你没有破坏原来的逻辑 |
| 「怎么知道 AI 对手「25%概率重击」是不是真的 25%?」 | 这是个好问题。严格测试需要跑 100 次统计分布,但我们的课堂不做这个。你可以告诉 AI「请解释这段 AI 决策代码里重击概率是如何实现的」,让 AI 用中文解释给你看 |
**课堂风险预案:**
- **如果 AI 服务不可用Kimi 宕机)**:切换到 Trae IDE 的内置 AI 对话,或使用教师备用的 demo-2-pk-battle.html 直接展示;学生完成需求文档部分,代码生成推迟
- **如果学生进度差异过大**:进度快的学生做拓展(导入 Spritesheet + 写边界测试);进度慢的学生保证完成「战斗骨架能跑通 + 普通攻击验收通过」即可,其余留作课后
- **如果有学生上节课的 Spritesheet 未完成**:提供默认占位图(单色 128×64 PNG确保不因图片问题卡住当天进度
---
### 7. 5分钟日常AI挑战
**本周挑战:** 让 AI 帮你写一条「需求文档里没有的」边界测试,看看能不能通过
**挑战说明:**
打开窗口 D全新对话告诉 AI「我的战斗系统需求文档里有这些规则[粘贴你的需求文档]。请帮我写一条我没有明确写进需求的边界测试——就是那种「正常情况不会想到,但真实对战时可能出现」的场景。」把这条新测试加入你的 pk-test.html运行看是通过还是失败。把结果截图或描述带到下节课分享。
**下节课分享:** 下节课选 2-3 位同学展示——「我写的新边界是什么,测试结果如何,有没有发现 bug」
---
### 8. 拓展任务
**拓展一(推荐):** 把你的 Spritesheet 真正导入 pk-battle.html`drawImage` 替换矩形占位符;不需要动画,只要图片能显示在正确位置。验收标准:打开游戏,左边角色是你自己画的图,不是矩形。
**拓展二(挑战):** 给你的战斗系统加一条新的行动类型:「反击」——格挡时如果对方用了重击,本回合额外反弹 30% 伤害给对方。先把这条规则写进需求文档,在窗口 B 审核边界情况(格挡+反击叠加怎么算?反击触发条件是什么?),然后在窗口 C 生成新代码,在窗口 D 补充测试用例,确保测试通过。
**拓展三(终极挑战):** 做一个双人对战模式——两个玩家分别控制左边和右边的角色,轮流点行动按钮。需求文档里要写清楚:轮次如何切换、一方操作完后如何提示另一方、胜负后如何重新开始。先写需求,再生成,再测试。
---
> 本教案遵循穹狼科创 SDDT + 4C 教学方法论编写,配合 `教学方法论规则.md` 和 `标准教案模板.md` 使用。

View File

@@ -0,0 +1,621 @@
---
课时: 10
主题: 涂鸦PK— 动画 + 音效 + 特技
核心能力: [审美力, 提问力]
核心工具: [Trae IDE, Kimi]
时长: 90分钟
透明化层级: 过程层
适用路线: AICODE-06
---
### 1. 课程目标
**知识目标:**
- 理解「游戏感Game Feel」的概念每次操作必须有视觉反馈玩家才知道自己的行动生效了
- 理解「增量需求」的写法:在已有系统上叠加新功能,只描述新增部分,避免 AI 误改已验收的代码
- 理解 Web Audio API 的核心思想:用纯代码合成音效,不需要任何音频文件
**能力目标:**
- 能用「感觉语言」描述动画需求(「快速冲过去然后弹回来」),让 AI 把感觉翻译成代码参数(审美力)
- 能写出增量需求文档,只描述新增的动画/音效,不重写已有战斗逻辑(提问力)
- 能在 AI 生成代码后,通过视觉对比和试玩,判断动画「感觉对不对」并给出精准修改意见(审美力)
**情感目标:**
- 对「同样的游戏,有没有动画,体验天差地别」产生真实感受——建立「视觉反馈是游戏的灵魂」的审美直觉
- 体验到「我说出一种感觉AI 把它变成了代码」的成就感,增强自然语言驱动开发的自信
- 对下节课的班级对战产生期待感
---
### 2. 核心概念与误概念预设
**核心概念认知层级:**
| 概念 | 学生类比 | 认知层级 |
|------|---------|---------|
| 游戏感Game Feel | 打篮球投篮时「嗖」一声进了网——同样进了,但有没有这个声音感觉完全不一样 | 理解层 |
| 增量需求 | 你去装修只告诉工人「加一个书架」,不需要重新描述整个房子的结构 | 理解层 |
| 感觉语言→参数翻译 | 给厨师说「要辣一点」而不是「加5克辣椒粉」——厨师懂怎么翻译成具体操作 | 应用层 |
| Web Audio 合成音效 | 用嘴巴「嗖——」发出声音,不需要找一个录好的「嗖」的音频文件 | 理解层 |
| 特技视觉标记 | 超市商品的标签——没有标签你看不出这是打折商品;特技没有特效,玩家感知不到它生效了 | 应用层 |
**典型误概念表:**
| 编号 | 误概念 | 正确认知 | 激发策略 |
|------|--------|---------|---------|
| M1 | 动画只是装饰,不影响游戏质量 | 动画是「游戏感」的核心:每次操作必须有视觉反馈,玩家才知道自己的行动生效了 | 展示对比无动画只有数字变化vs 有动画(角色冲刺),问学生哪个更爽 |
| M2 | 音效需要找音频文件,太麻烦了 | Web Audio API 用纯代码合成音效,零素材依赖,浏览器内置支持 | 演示:三行代码合成一个攻击音效,不需要任何音频文件 |
| M3 | 增量需求要把整个需求文档重写一遍 | 增量需求只写新增的部分;已验收的功能不重写,避免 AI 误改 | 类比:装修只说「加书架」,不需要重新描述整个房子 |
| M4 | 动画描述必须用技术参数duration: 200ms | 先用感觉描述「快速冲过去然后弹回来」AI 把感觉翻译成参数;感觉描述比参数描述更准确 | 让学生先说出感觉,再看 AI 翻译的参数,对比哪种方式更自然 |
| M5 | 特技效果只要触发就行,不需要视觉区分 | 特技必须有独特的视觉效果,玩家才能感知到特技生效了(如燃烧 = 橙色粒子,冰冻 = 蓝色闪烁) | 问:「如果燃烧特技触发了但什么都没发生,你怎么知道它触发了?」 |
---
### 3. 教学准备
**工具与环境:**
- 每台电脑已安装 Trae IDE可正常打开上节课第9课的战斗游戏文件
- 每台电脑浏览器可正常播放 Web AudioChrome/Edge 均支持,确认未被系统静音)
- 投影可切换至任意学生屏幕
- 准备两个演示文件:`demo-2-pk-battle.html`(无动画版)和 `demo-3-animation.html`(有动画版)
**教学资源:**
- 教师准备:`demo-3-animation.html` 在本机调试好,各按钮动画流畅可用
- 教师准备「动画增量需求」保底提示词见第5节、「音效合成」保底提示词见第5节
- 教师准备Web Audio 三行代码片段(在 Kimi 里提前验证过能正常合成音效)
- 学生资源上节课第9课完成的战斗游戏 `index.html`,血条/行动/胜负判定已可用
**教师备课体验任务:**
> 备课前,教师必须亲自完成以下操作:
>
> 1. 打开 `demo-2-pk-battle.html`,实际点一遍「普通攻击、重击、格挡、特技」四个按钮,感受没有动画时的体验——记录「哪里感觉很空洞」
> 2. 打开 `demo-3-animation.html`,同样点一遍四种行动,对比感受差距——这是课堂对比演示的素材
> 3. 用「动画增量需求」保底提示词向 Kimi 提交一次,拿到动画代码片段,在自己的测试文件里跑通——记录可能遇到的报错
> 4. 写一段 Web Audio 代码合成「攻击音效」,在浏览器里验证能正常播放(注意 Chrome 需要用户交互后才能触发音频)
> 5. 确认学生电脑音量未被静音Web Audio 可正常发声
---
### 4. 教学流程
---
**第一幕:联系 (Connect) — 10分钟** 🔗
*本幕目标:通过无动画 vs 有动画的真实对比,让学生切身感受「游戏感」的差距,建立「视觉反馈是游戏的灵魂」的直觉*
**【环节】上节课回顾 (2分钟)**
**师:** 上节课我们做了什么?谁来说一下我们的战斗系统现在能做什么?
**生:**(预期:有血条、可以攻击、有胜负、有特技)
**师:** 对,上节课我们还跑了测试脚本,伤害公式和胜负判定都验证通过了。今天我问你们——你们觉得现在这个游戏,好玩吗?好看吗?
**生:**(预期:还行 / 感觉有点无聊 / 就是点按钮数字变化)
**师:** 嗯。现在先别回答,我让你们看两个版本——看完再说。
**【环节】对比演示——无动画 vs 有动画 (8分钟)**
【投屏展示 demo-2-pk-battle.html教师操作】
**师:** 这是我们现在的版本。我点一下「普通攻击」——
【点击普通攻击,血条数字变化,但角色没有任何动作】
**师:** 发生了什么?
**生:**(预期:血量少了 / 没什么感觉 / 就是数字变了)
**师:** 对,血条从 100 变成了 85。游戏逻辑是对的。那感觉呢
**生:**(预期:感觉很平淡 / 不知道攻击有没有打出去 / 没什么反应)
**师:** 好,这个感受记住。现在看第二个版本。
【切换投屏到 demo-3-animation.html】
**师:** 同样的游戏,我点「普通攻击」——
【点击普通攻击,角色冲刺向对方,对方受击抖动,音效响起】
**师:** 现在感觉怎么样?
**生:**(预期:爽多了!/ 有打击感 / 感觉真的在打架)
【继续演示重击:角色蓄力放大→猛冲,屏幕震动】
【演示格挡:角色后退,护盾闪烁】
【演示特技:粒子效果爆发】
**师:** 这两个游戏,逻辑是完全一样的。血条、伤害、胜负——一模一样。唯一的区别是什么?
【诊断点:学生是否能说出「动画」「视觉反馈」「音效」等关键词】【识别层】
**【分支A】若学生说出「动画」「特效」「声音」**
**师:**动画、音效、视觉反馈。这些加在一起有一个专门的词——叫做「游戏感Game Feel」。什么叫游戏感就是每次你操作都有看得见、听得到的反馈——你的动作「生效了」。没有游戏感的游戏就算逻辑完美玩起来也像在操作表格。
**【分支B】若学生说「好看多了但说不出为什么」**
**师:** 你说好看多了,我问你——当你点普通攻击的时候,第一个版本你看到了什么?
**生:**(预期:数字变了)
**师:** 第二个版本呢?
**生:**(预期:角色冲过去了、对方抖了)
**师:** 对。第一个版本你的操作「发生了」但你看不到。第二个版本你的操作「生效了」,你看得到、感受得到。这种「每次操作都有反馈」的体验,叫「游戏感」。今天我们的任务就是把游戏感加到你们的战斗系统里。
**师:** 今天三件事:第一,给每种行动加动画;第二,加上音效;第三,让特技有独特的视觉标记。加完之后,你的战斗游戏就「活了」——可以参加下节课的班级对战了。
---
**第二幕:建构 (Construct) — 65分钟** 🛠️
---
**【分段一:动画系统——用感觉描述需求】(20分钟)**
*本段重点:学会用「感觉语言」而不是技术参数来描述动画;学会写增量需求(只写新增的动画部分,不重写已有战斗逻辑)*
**预设误概念:**
- 误概念 M3增量需求要把整个需求文档重写一遍
- 误概念 M4动画描述必须用技术参数duration: 200ms
**讲解与演示 (Teach & Demo): (5分钟)**
**师:** 我们要给战斗系统加动画。但加动画不是从头重写游戏——我们用「增量需求」的方式,只告诉 AI 「新加什么」,已有的代码不动。
**师:** 有人知道为什么不能重写吗?
**生:**(预期:不知道 / 会改坏 / 太麻烦)
**师:** 你上节课的战斗逻辑,血条、伤害、胜负——都测试通过了,是吧?如果我现在把整个需求文档重写一遍提交给 AIAI 很可能会「重新理解」你的战斗逻辑,把你已经调好的伤害公式改掉,或者把你的特技系统换一种方式实现。这叫「误改」。增量需求就是告诉 AI「已有的代码已经验收通过你只需要在这个基础上加动画不要动其他东西。」
**师:** 就像你家装修完了,现在只想加一个书架——你不会让装修工人重新来一遍,你只说「加一个书架,其他不动」。
**师:** 然后——动画要怎么描述?我问你:你觉得「普通攻击」的感觉应该是什么样子的?
**生:**(预期:冲过去 / 快速冲向对方 / 用力打一下)
**师:** 好,「快速冲向对方」——再具体一点,冲过去之后呢?
**生:**(预期:然后回来 / 弹回来)
**师:**「快速冲向对方然后弹回来」——这就是动画描述。不需要说「x偏移100像素duration 200msease Power2」——你只需要说出感觉AI 会把感觉翻译成参数。
【投屏展示对比】
```
❌ 技术参数描述:
普通攻击x += 100, duration: 200, ease: 'Power2', yoyo: true
✅ 感觉描述:
普通攻击:攻击方快速冲向对方,然后弹回原位,感觉轻快有力
```
**师:** 我们来做个小练习——每个人说出四种行动的感觉,不要用技术词汇,就说你脑子里的画面。
【投屏展示四种行动每个留5秒空白让学生思考】
| 行动 | 你脑子里的画面是什么? |
|------|---------------------|
| 普通攻击 | |
| 重击 | |
| 格挡 | |
| 受击 | |
**师:** 说一下,「重击」你觉得是什么感觉?
**生:**(预期:很用力冲过去 / 先蓄力然后猛冲 / 会震动)
**师:** 「先蓄力然后猛冲」——太好了这就是完美的感觉描述。AI 看到这个会知道要做两段动画:第一段蓄力(角色变大),第二段冲刺(快速位移)。
**学生实践 (Practice): (10分钟)**
在窗口 A 里,学生用感觉语言写出增量需求,提交给 Kimi获取动画代码。
> 教师走动观察谁停下来不动超过2分钟立刻过去观察他们写的感觉描述是否太技术化「角色向右移动100px」还是真的在用感觉语言
**学生写增量需求的结构提示(投屏展示):**
```
我已有一个用 HTML/JavaScript 写的战斗游戏,战斗逻辑已经验收通过。
现在我要新增动画系统,只描述新增部分,请不要修改已有的战斗逻辑。
普通攻击动画:[你的感觉描述]
重击动画:[你的感觉描述]
格挡动画:[你的感觉描述]
受击动画:[你的感觉描述]
死亡动画:[你的感觉描述]
```
**进度同步 (Checkpoint): (5分钟)**
**师:** 谁来分享一下你写的感觉描述?用感觉语言写的举手。
【诊断点:检验学生是否真的用了感觉语言,而不是技术参数】【理解层】
**【分支A】若学生写出了好的感觉描述「像被闪电劈中一样抖动」**
**师:** 这个描述太棒了!我们来看 AI 怎么翻译——
【投屏展示 Kimi 返回的参数,对应学生的感觉描述】
**师:** 你说「像被闪电劈中一样抖动」AI 翻译成了「x 方向快速来回震荡5次每次10像素总时长150毫秒」。感觉对吗
**【分支B】若学生的描述还是偏技术「角色向右移动」**
**师:** 我来问你——你玩游戏里打人,那个「被打」的感觉是什么?能用一个词或一句话描述吗?
**生:**(预期:抖了一下 / 像撞墙一样 / 闪了一下)
**师:**把这个感觉写进去比说「向右移动」更准确——AI 更能理解你真正想要的效果。
**【分支C】若学生 AI 返回的动画代码与感觉不符:**
**师:** 你看一下动画效果,跟你想的感觉一样吗?哪里不对?
**生:**(预期:太慢了 / 感觉不够有力 / 角色跑的方向不对)
**师:** 好,现在你就直接告诉 AI「重击感觉不够震撼蓄力时间太短了冲刺速度也不够快能不能把蓄力时间加长一倍冲刺速度加快」——这就是用感觉来迭代。
---
**【分段二:音效系统——描述声音感觉】(20分钟)**
*本段重点:用三行代码演示 Web Audio 合成原理;学会用「声音感觉」而不是技术参数描述音效需求;实现五种基础战斗音效*
**预设误概念:**
- 误概念 M2音效需要找音频文件太麻烦了
- 误概念 M4延伸描述音效也要用技术参数
**讲解与演示 (Teach & Demo): (5分钟)**
**师:** 现在我们加音效。有人觉得加音效很麻烦的举手——要找音频文件、下载、导入……
**生:**(预期:举手 / 点头)
**师:** 告诉你们一个秘密——我们不需要任何音频文件。我们用代码「生成」声音。
【投屏展示,教师打开浏览器控制台】
**师:** 这是 Web Audio API——浏览器内置的音频引擎。我现在在控制台里写三行代码让浏览器发出一个「攻击音效」。
【现场演示,逐行输入并解释】
```javascript
const ctx = new AudioContext();
const osc = ctx.createOscillator();
osc.frequency.value = 300; // 音调——300 是中等音高
osc.type = 'sawtooth'; // 波形——锯齿波比较「粗糙有力」
const gain = ctx.createGain();
gain.gain.value = 0.3; // 音量
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
setTimeout(() => osc.stop(), 80); // 只响 80 毫秒
```
**师:** 听到了吗?这是攻击音效——短促有力,像轻击。全部代码就这几行,不需要任何文件。
**师:** 和动画一样,音效也用感觉来描述。我来问你——「重击」的声音是什么感觉?
**生:**(预期:低沉 / 爆炸感 / 震动 / 轰的一声)
**师:** 「低沉震撼,像爆炸」——这就是感觉描述。告诉 AI 这个感觉AI 会帮你调频率、波形、时长。你不需要知道「锯齿波还是方波」——你只需要知道「你要什么感觉」。
【投屏展示感觉→参数对应表】
| 行动 | 声音感觉(你来填) | AI 可能翻译成 |
|------|----------------|-------------|
| 普通攻击 | 短促有力,像轻拍 | 频率300Hz锯齿波80ms |
| 重击 | 低沉震撼,像爆炸 | 频率150Hz锯齿波150ms |
| 格挡 | 硬邦邦,像金属碰撞 | 频率800Hz方波50ms |
| 受击 | 痛感,短促的「嗷」 | 频率400→150Hz扫频120ms |
| 胜利 | 欢快,像叮当 | 三音符523→659→784Hz正弦波 |
**学生实践 (Practice): (10分钟)**
学生打开窗口 B独立于战斗游戏用感觉描述让 Kimi 生成五种音效代码。验证每种音效能正常播放后,再把音效函数添加进战斗游戏的对应行动里。
> 教师走动观察重点:
> 1. 学生在描述音效时是否用了感觉语言还是乱写参数
> 2. 音效代码添加进战斗游戏后,有没有整合进「行动触发」时机(不能只是独立的函数,要在攻击时自动调用)
**进度同步 (Checkpoint): (5分钟)**
**师:** 谁来演示一下?点一个行动,我们听听音效。
【让1-2位学生上来演示战斗播放音效】
【诊断点:音效是否在正确的时机触发(点攻击时响,而不是游戏开始时就响)】【应用层】
**【分支A】若音效在正确时机触发且效果合理**
**师:** 注意听——每次点攻击有没有声音?每种行动的声音有没有区别?
【让全班听对比,引导学生注意重击和普通攻击的音效差异】
**师:** 好,把这个感觉记住——有音效和没音效,游戏感差距很大。
**【分支B】若音效能响但时机不对比如每隔一秒自动响**
**师:** 音效响了,但是时机有点问题——应该是点攻击时响,而不是自动响。你的音效函数是在哪里被调用的?
**生:**(查代码)……好像放到外面了?
**师:** 对,你需要把 `playSound_attack()` 这个函数的调用,放进你处理「普通攻击」按钮点击的那个函数里——告诉 AI「请把音效调用放在行动触发时不要自动播放」。
**【分支C】若音效完全没有声音Chrome 浏览器 AudioContext 未激活):**
**师:** 先检查电脑音量,再检查——在 Chrome 里,必须是用户点击页面之后,音频才能播放。你有没有先点一下游戏页面,再点攻击按钮?
【引导学生先点一下页面,激活 AudioContext然后再试】
---
**【分段三:特技视觉效果完善 + 平衡性调整】(15分钟)**
*本段重点:给每个特技加上独特的视觉标记(粒子/颜色/光效),让玩家能感知特技「生效了」;同时微调特技触发概率让对战更平衡*
**预设误概念:**
- 误概念 M5特技效果只要触发就行不需要视觉区分
**讲解与演示 (Teach & Demo): (4分钟)**
**师:** 最后一段——特技。你们的特技现在已经有效果了,对吧?但我问你——燃烧特技触发了,你怎么知道它触发了?
**生:**(预期:有文字提示 / 看日志 / 对方血量变化快)
**师:** 靠文字提示来告诉玩家「特技触发了」——这叫什么?这叫「说给玩家听」。而好的游戏是「让玩家感受到」。
**师:** 每个特技必须有专属的视觉标记——就像每种英雄技能有自己的颜色。我来给你们看几个方向:
【投屏展示特技视觉效果参考】
```
🔥 燃烧:目标角色持续冒出橙红色小粒子,向上飘散
🛡 护甲:角色周围出现蓝色光圈,护盾吸收伤害时闪烁
⚡ 连击:第二次攻击时黄色闪光从角色身上爆发
💉 吸血:攻击时绿色光线从对方身上流向自己
❄️ 冰冻对方角色整体变蓝tint 0x4444ff出现碎冰粒子
🎯 穿透:攻击线变红色,穿过对方护盾
```
**师:** 你的游戏里有哪种特技?现在打开需求文档,给你的特技加一条「视觉效果描述」——用感觉语言,告诉 AI 特技触发时玩家应该「看到什么」。
**学生实践 (Practice): (8分钟)**
学生在窗口 A增量需求里补充特技视觉效果描述提交给 Kimi 生成代码;同时,根据上节课测试结果,检查特技触发概率是否合理(可调整概率,让对战更有趣),把最终版战斗游戏跑通。
> 教师走动重点:检查学生的特技视觉效果是否真的有视觉差异,而不是所有特技都做成「同一种颜色的粒子」
**进度同步 (Checkpoint): (3分钟)**
**师:** 好,现在所有人试玩一下自己的战斗游戏——从开始到有人胜利,完整走一遍。有动画、有音效、有特技视觉效果的请举手。
【诊断点:完整游戏循环(动画+音效+特技视觉)是否全部正常运行】【应用层】
**【分支A】若学生完整游戏循环正常**
**师:** 很好你的涂鸦PK已经有游戏感了。下节课的班级对战就用这个版本。给自己鼓个掌。
**【分支B】若学生动画有但音效没有或相反**
**师:** 没关系,少一项我们继续加。现在缺的是哪一个?告诉我——动画、音效、特技视觉,哪个没做完?
【针对性引导完成缺失的部分,优先保证动画完整,音效可以只有三种基础音效也算通过】
**【分支C】若学生整个游戏运行出错**
**师:** 现在打开浏览器的控制台F12看一下有没有红色的报错信息把报错信息截图或者复制下来告诉 AI「我在运行时遇到了这个报错请帮我定位问题」——这是你们最后一次用 AI 调试,我们在课堂上解决。
---
**第三幕:反思 (Contemplate) — 10分钟** 🤔
**【环节】成果展示 (6分钟)**
**师:** 好,时间到。谁愿意来展示一下今天的战斗游戏?要展示三件事:第一,你的角色是谁;第二,你加了什么样的动画;第三,你设计了什么特技的视觉效果。
【邀请 2-3 名学生展示,每人 1.5 分钟】
**师:** 展示的时候,先说你「用感觉描述了什么」,再演示效果——让大家看你的感觉描述和 AI 翻译出来的代码,差距有多大。
【第一位学生展示】
**师:** 你说「重击感觉像被大锤砸到」AI 做出来的效果,符合你的感觉吗?
**生:**(预期:基本符合 / 比我想的还要好 / 有一点不对但我改了)
**师:** 好——改了几次才满意?
**生:**(预期:两三次)
**师:** 两三次迭代就做到满意——这就是「用感觉描述 + 迭代优化」的效率。
**【环节】互评与讨论 (4分钟)**
**师:** 谁来给刚才展示的游戏说「一个优点 + 一个改进建议」?要具体说,不能只说「很好看」。
**生:**(预期:动画很流畅,但是音效重击的声音感觉不够震撼)
**师:** 好,「不够震撼」——怎么改?
**生:**(预期:声音低一点 / 时间长一点 / 加个爆炸效果)
**师:** 对,这就是具体的改进建议。今天我们训练的核心能力是什么?
**生:**(预期:动画 / 音效 / 特效)
**师:** 这些都是工具。背后的能力是——**审美力**:用感觉描述「好不好看、好不好听」,让 AI 把你的感觉变成代码。审美力不是说「画画好看」,是说「你能不能清楚地描述一个体验,并且判断 AI 做出来的是不是你要的」。
---
**第四幕:延续 (Continue) — 5分钟** 🚀
**【环节】抽象总结 (3分钟)**
**师:** 今天三件事,我们来总结一下。第一件是什么?
**生:**(预期:动画 / 加了动画)
**师:** 对,我们加了动画。背后的方法是什么?
**生:**(预期:用感觉描述 / 感觉语言)
**师:** 对——「用感觉描述需求」。不用技术参数直接说你脑子里的画面AI 来翻译。这个能力不只能用在游戏开发里——你想做一个网页、一个动效、一个演示文稿都可以这样描述「我想要一种……的感觉」AI 来实现。
**师:** 第二件事,音效。背后的发现是什么?
**生:**(预期:不需要音频文件 / 代码可以直接合成声音)
**师:** 对——Web Audio API 用代码合成声音。零文件,浏览器内置。这说明一件事:很多你以为「需要素材」的东西,代码都可以直接生成。
**师:** 第三件事,特技视觉效果。核心原则是什么?
**生:**(预期:要让玩家看得见 / 每种特技要有独特的效果)
**师:** 对——「让玩家感受到,而不是告诉玩家」。这是所有好游戏的共同原则。
**【环节】下节预告 + 5分钟挑战 (2分钟)**
**师:** 下节课——班级大乱斗。你们的战斗游戏会互相对战,不同角色、不同特技。在对战之前,你有机会再调整一次你的特技参数——如果你觉得你的角色太弱,可以偷偷增强一点……
**生:**(预期:笑声 / 期待)
**师:** ……但是太强了也不行,因为我会检查公平性。
**师:** 本周的5分钟挑战给你的一个特技加一个你自己设计的视觉特效——不用上课说的那些完全自己想。描述感觉让 AI 实现,下节课展示。
---
### 5. AI助教使用指南
**教师演示用提示词(动画增量需求):**
```
我已有一个用 HTML + JavaScript 写的战斗游戏,代码在下面。
战斗逻辑(血条、行动、伤害、胜负判定)已经验收通过,请不要修改。
现在我要新增动画系统,只描述新增的动画部分:
- 普通攻击攻击方快速冲向对方100像素然后弹回来感觉轻快有力
- 重击攻击方先膨胀变大1.2倍蓄力感然后猛冲向对方150像素对方水平抖动3次
- 格挡格挡方向后退50像素同时闪烁两次半透明/不透明切换,护盾感)
- 受击被击方左右快速抖动±10像素3次同时变红后恢复原色
- 死亡旋转360度同时缩小到0并淡出600毫秒内完成
- 胜利上下弹跳3次-30像素/次200毫秒/次)
请只输出新增的动画相关代码,以及在哪里插入。不要改动现有代码结构。
```
**教师演示用提示词(音效合成,窗口 B**
```
我要用 Web Audio API 合成战斗游戏音效,全部在 HTML 里实现,不需要任何音频文件。
请帮我写以下音效函数(每个独立的 function音效感觉描述如下
1. playSound_attack()短促有力像轻击金属80毫秒
2. playSound_heavy()低沉震撼像远处爆炸有一点回响感150毫秒
3. playSound_block()清脆硬邦邦像剑挡住了另一把剑50毫秒
4. playSound_hit()短促的痛感像「嗷」一声120毫秒
5. playSound_win():欢快的三音符上升,像「叮叮叮」,音调一个比一个高
每个函数调用时直接播放,不需要预加载。请输出可以直接放进 HTML 的完整代码片段。
```
**学生保底提示词(动画增量需求,窗口 A**
```
我已有一个用 HTML/JavaScript 写的战斗游戏(代码如下)。
战斗逻辑已验收,请不要修改已有代码。
在此基础上新增动画:
- 普通攻击:角色冲出去然后弹回
- 重击:先蓄力放大再猛冲,对方抖动
- 格挡:角色后退并闪烁
- 受击:角色抖动并变红
- 死亡:旋转消失
请只添加动画相关代码,告诉我在哪里插入。
```
**学生保底提示词(音效合成,窗口 B**
```
请帮我用 Web Audio API 合成以下游戏音效,写成 HTML 代码片段,每个音效一个函数:
1. 普通攻击:短促有力的金属碰撞声
2. 重击:低沉震撼的爆炸声
3. 格挡:清脆的金属阻挡声
4. 受击:短促的痛苦音
5. 胜利:三音符上升的欢快音
函数名分别为 playSound_attack、playSound_heavy、playSound_block、playSound_hit、playSound_win。
调用时直接播放,不需要任何音频文件。
```
**进阶提示词(特技粒子效果):**
```
我的战斗游戏有一个「燃烧」特技,触发时目标会持续受到灼烧伤害。
请帮我用 Canvas 2D API不用任何外部库实现
- 燃烧状态下,目标角色周围持续出现橙红色小粒子
- 粒子从角色身体向上飘散透明度随时间降低到0后消失
- 燃烧持续3秒期间粒子效果一直在
请只给我新增的粒子系统代码,以及调用时机说明。
```
---
### 6. 教师指南
**本课技术备注:**
**关于 Canvas 动画实现方式(不用 Phaser 的情况下):**
本课的战斗游戏基于 HTML Canvas 2D 直接绘制(无 Phaser动画通过 `requestAnimationFrame` 循环和时间差deltaTime驱动。动画「缓动感」来自 lerp线性插值每帧把当前位置向目标位置靠近一小步产生「减速停下」的感觉。AI 生成的代码通常会用 `let t = 0; t += deltaTime / duration` 的方式控制进度0到1之间变化再套上缓动函数`1 - (1-t)^3`)产生弹性效果。
**关于 Web Audio API 的自动播放限制:**
Chrome/Edge 要求页面必须有用户交互(点击/键盘输入)后,才允许 `AudioContext` 播放声音。如果游戏一打开就尝试播放,会被浏览器拦截(报错:`AudioContext was not allowed to start`)。解决方案:把第一个 `AudioContext` 的创建放在用户点击按钮之后,或者用 `ctx.resume()` 在点击时激活。AI 生成的代码不一定会处理这个问题,需要教师在课堂提醒。
**关于增量需求的上下文传递:**
让学生在「增量需求提示词」里把整个现有代码粘贴进去Kimi 才能根据现有代码结构插入新代码。如果不提供现有代码Kimi 会自己猜代码结构,生成的插入位置可能不对。提醒学生:「把你的战斗游戏代码全选复制,粘贴到提示词末尾」。
**关于特技粒子效果的性能:**
粒子效果如果每帧创建大量 DOM 元素,会导致游戏卡顿。告诉学生用 Canvas 绘制粒子(在 `requestAnimationFrame` 里画圆点),不要让 AI 创建 `<div>` 元素来做粒子。如果学生的游戏明显卡顿,检查是否有粒子数组无限增长的问题(每帧加粒子但不清除),引导学生告诉 AI「粒子消失后从数组里删除」。
**常见问题 FAQ**
| 问题 | 应对 |
|------|------|
| 「动画代码加进去之后,血条不显示了」 | 很可能是 Canvas `clearRect` 的调用顺序被改了,告诉 AI「加了动画代码后血条不显示请检查 clearRect 和绘制顺序是否被修改」 |
| 「音效能响但是每次攻击响很多次」 | `playSound_attack` 函数被错误地放在了 `requestAnimationFrame` 里,每帧都在调用;告诉 AI「音效每次行动只应该播放一次」 |
| 「死亡动画结束后角色还留在屏幕上」 | 死亡动画只改了透明度,没有在动画结束后将角色标记为「已死亡」并停止绘制;引导学生告诉 AI「动画结束后停止绘制该角色」 |
| 「特技粒子效果触发一次后就一直在了」 | 粒子系统的「存活时间」逻辑没有实现,粒子永远不消失;告诉 AI「每个粒子有3秒存活时间时间到了就从数组里移除」 |
| 「我的重击蓄力动画太慢了游戏变拖沓」 | 这是审美判断问题,直接让学生告诉 AI「蓄力时间改成 150ms冲刺时间改成 80ms感觉更爽快」 |
| 「Chrome 没有声音」 | 先确认系统音量;再确认是否先点击了游戏页面;最后检查 `AudioContext` 是否在按钮点击后创建 |
**课堂风险预案:**
- **如果 Kimi 返回的代码加进去运行报错:** 让学生把报错信息复制给 Kimi提示词格式「我按你说的把代码加进去了运行时出现了这个报错[报错信息],原来的代码是 [原代码],请帮我找到原因并给出修正后的代码」
- **如果学生进度差异过大(有人全部做完有人还没做动画):** 做完的学生进入拓展任务(给特技加粒子效果 + 调平衡性);做到一半的学生优先完成动画,音效用保底提示词一步到位
- **如果整体时间不够25分钟建构还剩两段没做完** 合并分段二和分段三,音效只做三种基础音效(攻击/受击/胜利),特技视觉效果作为课后挑战
---
### 7. 5分钟日常AI挑战
**本周挑战:** 给你的一个特技,设计一个全新的视觉特效
**挑战说明:**
选你游戏里你最喜欢的一个特技,想象它触发时应该「看起来像什么」——可以是粒子爆炸、颜色变化、屏幕震动、时间静止特效……只要用感觉语言描述出来,告诉 AI 实现。要求:这个特效必须和课上做的不一样,完全是你自己的设计。
**下节课分享:** 下周课上选 2-3 位同学展示特技特效挑战成果说明自己是怎么描述感觉的AI 实现了几轮才满意。
---
### 8. 拓展任务
**拓展一(推荐):屏幕震动特效**
重击时不只是角色抖动——整个游戏画布轻微震动一下Canvas 偏移 ±5 像素,持续 200ms。这种效果叫「屏幕震动Screen Shake是格斗游戏的经典手法。用感觉描述让 AI 实现:「重击时整个画面抖一下,像相机被猛地推了一把」。
**拓展二(挑战):行动预判音效**
在角色攻击「蓄力」阶段播放一个低频「嗡——」的充能音效,攻击命中时再播放冲击音效——两段音效组合,制造出「蓄力→爆发」的完整听觉体验。提示:需要两个 Web Audio 函数,第一个在蓄力开始时调用,第二个在命中时调用,注意时机控制。
---
### 附:本课核心教学框架速查
**四幕时间分配(合计 90 分钟):**
| 幕 | 内容 | 时长 |
|----|------|------|
| 联系 Connect | 对比演示无动画 vs 有动画,引出「游戏感」 | 10 分钟 |
| 建构 · 分段一 | 感觉语言描述动画,写增量需求 | 20 分钟 |
| 建构 · 分段二 | 用代码合成音效,整合进战斗行动 | 20 分钟 |
| 建构 · 分段三 | 特技视觉标记完善 + 平衡性微调 | 15 分钟 |
| 反思 Contemplate | 成果展示 + 感觉描述回顾 + 互评 | 10 分钟 |
| 延续 Continue | 抽象总结三件事 + 预告班级对战 + 挑战发布 | 5 分钟 |
**本课核心能力训练路径:**
```
审美力路径:
无动画体验(识别层)
→ 用感觉描述动画/音效需求(理解层)
→ 对比 AI 输出和自己预期,定位差距(应用层)
→ 精准迭代修改,达到满意(迁移层)
提问力路径:
理解增量需求的必要性(识别层)
→ 只写新增部分的提示词,不重写全局(理解层)
→ 在已有代码基础上定向追加功能(应用层)
```
**关键词速查(下课前可口头检查学生是否能解释):**
- 游戏感Game Feel每次操作都有视觉/听觉反馈,让玩家感受到行动生效
- 增量需求:在已验收代码基础上只描述新增部分,避免 AI 误改
- Web Audio API浏览器内置音频引擎用代码合成声音无需音频文件
- 感觉语言:用描述体验和画面感的词汇提需求,而非技术参数

View File

@@ -0,0 +1,599 @@
---
课时: 11
主题: 涂鸦PK— 班级锦标赛
核心能力: [表达力, 共创力]
核心工具: [Trae IDE, Kimi]
时长: 90分钟
透明化层级: 结果层
适用路线: AICODE-06
---
### 1. 课程目标
**知识目标:**
- 理解「数据驱动设计」的核心思想:角色数据与游戏代码分离,加新角色只需加文件,代码不用改
- 理解角色选择界面是「数据读取 + UI 展示」的组合,而非把所有角色写死在代码里
- 理解路演的核心是「设计决策」而非「功能列表」:说清楚「为什么这样做」比「做了什么」更有价值
**能力目标:**
- 能用增量需求文档描述 roles 系统并经过窗口B审核、窗口C执行共创力
- 能在班级锦标赛中用语言分析胜负原因,做出有依据的复盘(表达力)
- 能按「展示角色→设计意图→精彩瞬间→设计复盘」结构完成3分钟路演表达力
**情感目标:**
- 感受到「自己的角色和全班的角色一起对战」的真实社交激励
- 建立「输了是迭代的起点」而非「输了说明设计失败」的成长心态
- 对「数据和代码分离」产生直觉性认同——这是真实工程师的设计方式
---
### 2. 核心概念与误概念预设
**核心概念认知层级:**
| 概念 | 学生类比 | 认知层级 |
|------|---------|---------|
| 数据驱动设计 | 游戏皮肤系统——你换皮肤不需要重新下载整个游戏,皮肤是「数据」,游戏逻辑是「代码」,两者分离 | 理解层 |
| 硬编码 | 把菜单直接印在餐厅墙上——想加一道菜要重新刷墙 | 识别层 |
| roles 系统 | 班级花名册——班里来了新同学,只需要在花名册上加一行,不需要改班级的所有规则 | 理解层 |
| 增量需求 | 在已有房子里加一个房间——不是推倒重建,而是在现有结构上扩展 | 应用层 |
| 路演设计决策 | 苹果发布会不说「这个手机有摄像头」,而是说「为什么这个摄像头改变了拍照方式」 | 应用层 |
**典型误概念表:**
| 编号 | 误概念 | 正确认知 | 激发策略 |
|------|--------|---------|---------|
| M1 | 加新角色需要改代码 | 数据驱动设计:加一个 json 文件 = 加一个角色,代码不用改 | 演示:不改一行代码,只在 roles/ 文件夹里加一个新 json新角色就出现在选择界面 |
| M2 | 路演就是说「我做了什么功能」 | 路演要说「我为什么这样设计」——设计决策比功能列表更有价值 | 类比苹果发布会:发布会不说「手机有摄像头」,而是说「为什么这颗摄像头改变了拍照方式」 |
| M3 | 属性随便分的角色不可能赢认真分的 | 「奇怪」的属性分配有时会产生意外的克制效果;游戏平衡比绝对强度更重要 | 展示一个「全部堆 HP」的坦克型角色用消耗战赢了「全部堆 ATK」的爆发型角色 |
| M4 | 输了说明自己设计失败了 | 输了是迭代的起点;「如果重来属性怎么分」才是最有价值的问题 | 强调:世界上最厉害的游戏设计师也在持续迭代,没有「一次设计就完美」的 |
---
### 3. 教学准备
**工具与环境:**
- 每台电脑已安装 Trae IDE第10课的战斗游戏项目可以正常运行
- 每台电脑有学生自己的角色图片(.png和角色数据.json
- 投影可切换至任意学生屏幕,用于锦标赛投屏
- 教师电脑预建好 `roles/` 文件夹结构(用于演示数据驱动)
**教师备课必做(课前):**
> 1. 提前向每位学生收集角色文件(角色名.png + 角色名.json放入教师电脑 `roles/` 文件夹
> 2. 在教师电脑上跑一遍 roles 系统生成流程,确保角色选择界面正常读取所有角色
> 3. 准备好「花名册投屏」:一个展示全班角色名字 + 特技的页面或截图
> 4. 准备好锦标赛对阵表(单淘汰制,根据班级人数提前画好括号)
> 5. 备用方案:如果 roles 系统生成失败手动在代码里加角色选择下拉菜单15分钟内可完成
**教学资源:**
- 教师准备:全班 roles/ 文件夹(含所有 png + json
- 教师准备:锦标赛对阵表(投屏用)
- 教师准备保底提示词见第5节
- 学生资源第10课的战斗游戏项目文件index.html
---
### 4. 教学流程
---
**第一幕:联系 (Connect) — 10分钟** 🔗
*本幕目标:用「角色花名册」制造全班兴奋感,引出数据驱动概念,建立「今天是全班大乱斗」的期待*
**【环节】角色花名册 + 今日导入 (10分钟)**
**师:** 课前我收集了大家的角色文件。我现在打开,给大家看看今天的「选手花名册」。
【投屏展示花名册——所有角色的名字、HP/ATK/DEF/SPD 属性、特技名称】
**师:** 你们来数一数,今天一共有多少个角色参赛?
**生:** 数人数……XX 个!
**师:** XX 个角色,今天全部都要上场对战。谁是最强的?我们今天锦标赛见真章。
【停顿 3 秒,让兴奋感发酵】
**师:** 但我现在遇到一个问题。大家的游戏现在只能用「硬编码」的两个角色。什么叫硬编码?就是角色数据直接写死在代码里,像这样——
【投屏展示代码片段】
```javascript
// 角色数据写死在代码里
const player = { name: '章鱼怪', hp: 80, atk: 15, def: 5, spd: 8 };
```
**师:** 这种写法,想换一个角色,要去代码里改数字。想加一个新角色,要再写一段代码。如果班里有 8 个角色要互相打,要改多少次代码?
【识别层:让学生感受到硬编码的局限性】
**生:** (预期:很多次……要改来改去)
**师:** 对,非常麻烦。今天我们要做一件事——用「数据驱动」的方式重新设计这个系统。做完之后,加新角色只需要加一个 json 文件,代码一行都不用改。我们先做好这个系统,然后——锦标赛开始。
【诊断点:学生是否感受到「数据驱动」和「硬编码」的差别,还是觉得无所谓】【识别层】
**【分支A】若学生问「数据驱动是什么意思」**
**师:** 你知道游戏皮肤吗?比如王者荣耀,你换一个皮肤,不需要重新下载整个游戏对吧?皮肤就是「数据」,游戏逻辑是「代码」,两个东西分开存放。我们今天就是要把角色数据从代码里分离出来,放进独立的 json 文件。
**【分支B】若学生觉得「改代码也没什么反正能跑」**
**师:** 如果你做的游戏将来要卖给别人,需要经常更新角色,每次更新都要改代码,那维护成本是很高的。数据驱动就是为了让「扩展」变得简单——这是真实游戏公司的标准做法。
---
**第二幕:建构 (Construct) — 65分钟** 🛠️
*本幕目标:完成 roles 系统(数据驱动角色选择);举办班级锦标赛*
---
**【分段一roles 系统——数据驱动设计实战】(20分钟)**
**预设误概念:**
- 误概念 M1加新角色需要改代码核心要破除的误概念
- 学生可能认为「从文件夹读取 json」很难实现需要很复杂的代码
**讲解与演示 (Teach & Demo): (5分钟)**
**师:** 现在我们用三个窗口来完成这个系统。还记得三窗口原则吗?
**生:** 预期窗口A写需求窗口B审核窗口C执行
**师:** 对。今天的需求是「增量需求」——不是从头做,而是在现有战斗游戏上加一个新模块。我先给大家看增量需求文档长什么样。
【投屏展示增量需求文档内容,逐条讲解】
**师:** 这份需求文档有四条核心要求。我来念一遍:
第一,从 roles/ 文件夹读取所有 .json 文件,每个文件包含 name、hp、atk、def、spd、skill 字段。
第二,显示角色选择界面:左边选玩家角色,右边选 AI 角色,每个角色显示名字和属性。
第三,有「开始战斗」按钮,点击后进入战斗,加载选中角色对应的同名 .png 作为 Spritesheet。
第四,不需要重写整个游戏——只增加角色选择这一层,原来的战斗逻辑不动。
**师:** 注意最后一条——「不重写整个游戏」。这就是增量需求的核心。我们不推倒重来,我们在现有基础上扩展。
现在我演示一下最重要的一步——数据驱动设计的关键代码思路。
【投屏展示两种写法对比】
```
硬编码方式(现在):
代码里直接写 { name: '角色A', hp: 80, atk: 15 ... }
→ 想加角色:改代码
数据驱动方式(今天要做):
读取 roles/角色A.json → 代码只负责读取,不存储数据
→ 想加角色:在 roles/ 文件夹里加一个新 json 文件,代码不变
```
**师:** 看到区别了吗现在窗口A的需求文档大家已经看到了我们直接去窗口B审核。
**学生实践 (Practice): (12分钟)**
【教师打开窗口B新的 Kimi 对话),投屏展示审核提示词】
**师:** 窗口B的提示词是——
```
你是一个严格的需求审核工程师。我有一份增量需求文档,请找出其中可能的漏洞和没说清楚的地方。每个问题单独列出来,只问问题,不给解决方案。
需求文档:
[粘贴增量需求内容]
```
**师:** 大家现在把窗口B打开把这个提示词加上你自己的增量需求提交审核。我来演示一遍你们同步做。
> 教师同步演示窗口B审核过程投屏展示 AI 找出的问题roles/ 文件夹里没有 png 文件只有 json 怎么处理?同一个角色能选两次吗?)
**师:** AI 找到了这几个问题。大家对照一下你的窗口B找到了什么问题
**生:** (预期:找到类似问题,或发现自己的需求文档有遗漏)
**师:** 好,把 AI 问的问题答案补充进需求文档。然后开窗口C执行。窗口C的提示词见屏幕。
【投屏展示保底提示词,学生复制使用】
> 教师巡视:谁的屏幕 3 分钟没变化就主动过去。重点关注窗口C是否用了「只输出新增/修改的代码」指令,避免 AI 重写整个游戏。
**进度同步 (Checkpoint): (3分钟)**
**师:** 谁的角色选择界面已经出来了?截图或者展示屏幕。
【让 1-2 位学生展示屏幕】
**师:** 你试试——现在不改一行代码,把一个新的 json 文件放进 roles/ 文件夹,刷新页面,新角色出现了吗?
【诊断点:验证学生是否真正实现了数据驱动,而不只是在选择界面里把角色名字硬编码进去】【应用层】
**【分支A】若角色自动出现了**
**师:** 这就是数据驱动。你的代码现在可以无限扩展角色,不需要碰代码本身。这是真实游戏公司的标准做法。
**【分支B】若角色没有自动出现角色名写死在选择界面里**
**师:** 看一下你的选择界面代码,角色名是直接写在 HTML 里还是从文件里读的?如果是写在 HTML 里,说明还是硬编码,需要让 AI 改成读取 json 文件的方式。提示词这样说:「我的角色选择界面现在是把角色名硬编码在 HTML 里的,请改成从 roles/*.json 文件里读取,动态生成选择列表。」
**【分支C】若 AI 把整个游戏重写了:**
**师:** 这是常见的情况——AI 有时候会把整个代码重写一遍。发现这个情况后,告诉 AI「请不要重写整个游戏只输出 roles 系统的新增部分,我自己复制进去。」然后把新增部分手动粘贴到原来的代码里。
---
**【分段二:班级锦标赛——全班大乱斗】(30分钟)**
**预设误概念:**
- 误概念 M3属性随便分的角色不可能赢认真分的
- 误概念 M4输了说明自己设计失败了
**讲解与演示 (Teach & Demo): (3分钟)**
**师:** roles 系统已经完成了。现在游戏可以选任意角色对战了。我们正式开始今天的班级锦标赛!
【投屏展示锦标赛对阵表】
**师:** 规则说明。单淘汰赛制。每场对战一方选自己的角色另一方选自己的角色战斗5回合。5回合后谁 HP 多谁赢;如果有一方 HP 归零,提前结束。每场大约 2-3 分钟。赢的人晋级,输的人直接进入路演准备。
**师:** 在每场开始之前,我会给大家介绍两个角色的属性和打法,然后问你们——你们觉得谁会赢?
**师:** 有没有问题?
> 如果学生有问题,简短回答;如果没有,直接开始第一场
**学生实践 (Practice): (25分钟)**
【锦标赛主持逐字稿,每场 2-3 分钟】
**第一场开始前:**
**师:** 第一场对阵:[角色A] vs [角色B]。我来介绍一下这两位选手——
[角色A] 是 [学生名] 设计的,[角色名],属性是 HP[数字]、ATK[数字]、DEF[数字]、SPD[数字],特技是[特技名]。从属性看,这是一个[分析高ATK爆发型 / 高HP坦克型 / 高SPD速攻型]的角色。
[角色B] 是 [学生名] 设计的,[角色名],属性是 HP[数字]、ATK[数字]、DEF[数字]、SPD[数字],特技是[特技名]。这是一个[分析]的角色。
你们觉得谁会赢?举手投票——投 [角色A] 的举手……投 [角色B] 的举手……
好,开始!
【由第一个出战的学生上台操作,或者由教师代操,全班围观投屏】
**战斗进行中(教师解说):**
**师:** 第 1 回合,[角色A] 先手——攻击了 [数字] 点伤害。[角色B] 现在剩 HP [数字]。
**师:** [角色B] 反击……特技触发了![特技效果]。现在形势[分析]。
**师:** 大家注意这一回合——[角色A] 的 DEF 是 [数字],所以对方每次攻击被减了 [数字] 点。这就是 DEF 的价值。
**战斗结束后:**
**师:** [赢方角色] 赢了![学生名] 晋级。
**师:** 分析一下——[赢方角色] 赢的原因是什么?是属性设计好,还是特技时机对,还是单纯运气?
**生:** (预期:因为 HP 多扛住了 / 因为特技打了两次 / 因为 ATK 高秒杀了)
**师:** 对。[具体分析赢的原因]。[输方角色] 输了——如果重来,你觉得属性怎么分会更好?
**生(输方学生):** (预期:我应该把 DEF 加高一点 / 我应该多加 SPD 先手)
**师:** 这个分析很好。记住这个想法,等会儿路演的时候说出来。
【继续下一场,同样流程】
**半决赛/决赛前:**
**师:** 现在进入[半决赛/决赛][角色A] 和 [角色B],这两位已经赢过了[X]场。来介绍一下他们的战绩——
**师:** [角色A] 在上一场用[什么策略]赢了[谁]。[角色B] 在上一场用[什么方式]赢了[谁]。这场对阵,你们觉得——
**决赛结束后:**
**师:** 恭喜[冠军角色][学生名] 拿到今天锦标赛冠军!
**师:** 但我要说一件事——今天冠军不是「最聪明的人」,而是「今天这个对阵顺序下最幸运的人」。如果换一个对阵顺序,冠军可能完全不同。游戏平衡性比绝对强度更重要——这是游戏设计师最核心的命题。
**进度同步 (Checkpoint): (2分钟)**
**师:** 锦标赛结束了。在进入路演之前,我问大家一个问题:你们觉得,今天的冠军角色「设计得最好」吗?
【诊断点:学生是否理解「赢得比赛」和「设计得好」是两件事,随机因素(对阵顺序)影响结果】【理解层】
**【分支A】若学生说「冠军设计最好」**
**师:** 如果把今天的对阵顺序换一下,让冠军第一场就对上另一个强角色,他还会赢吗?不一定。所以赢比赛不等于设计最好——游戏平衡设计是一门科学,不是谁 ATK 最高谁就赢。
**【分支B】若学生说「运气成分很大」**
**师:** 对,你说到一个重点。同样属性的两个角色打十场,赢的次数可能各五场。所以好的游戏设计要让不同风格的角色都有赢的可能,而不是让一种打法通吃所有。这叫「策略多样性」。
---
**第三幕:反思 (Contemplate) — 15分钟** 🤔
*本幕目标每人完成3分钟路演训练「说设计决策」而非「说功能列表」*
**【环节】班级路演 (15分钟)**
**师:** 现在进入路演环节。每个人 3 分钟,包括展示和提问。路演有四段固定内容:
【投屏展示路演结构】
```
第1段30秒展示角色
说:「我的角色叫 XX是一个 XX 型,属性分配是……」
第2段30秒设计意图
说:「我选这个打法是因为……我觉得用 XX 特技可以克制……」
第3段60秒精彩瞬间
说:「最让我印象深刻的是和 XX 的对战,那一场……」
第4段60秒设计复盘
说:「如果重来,我会把 XX 调低一点,把 XX 调高……因为我发现……」
```
**师:** 注意——路演不是说「我的角色有什么功能」。路演是说「我为什么这样设计」。这两件事差很多。我来举个例子:
错误示范:「我的角色叫章鱼怪,它有 HP 80、ATK 15、DEF 5、SPD 8特技是连击。」
正确示范:「我叫它章鱼怪,我把 HP 设高是因为我想让它能扛住对方的连续攻击,用消耗战的方式赢。特技选连击是因为连击在对方 HP 低的时候效果最好,可以一次清掉。」
看到区别了吗?第一个说了什么,第二个说了为什么。
**师:** 好,谁先来?
【6-8人小班建议全员路演学生逐一路演教师计时到 3 分钟给提示】
**路演示例教师可以第一个先做一遍示范30秒内**
**师:** 我先给大家示范一遍格式。如果我设计了一个角色,我会这样路演——
「我的角色叫石甲龟是一个防御型角色。HP 设了 100DEF 设了 20ATK 只有 5——我知道这样攻击力很弱但我的想法是用超高的防御力把对方的输出全部挡掉靠持久战赢。特技我选了「反弹」——把对方一部分伤害反还给它。最让我印象深刻的是和高 ATK 角色对战,那一场打了整整 5 回合,对方 ATK 30 打在我身上每次只剩 10 点,我靠反弹慢慢把它磨死了。如果重来,我会把 SPD 调高一点,因为我发现先手权在某些情况下比防御更重要——有几场因为后手被秒杀了。」
看到了吗?每一个数字背后都有理由,每一个设计决策都可以说出来。好,现在轮到大家了。谁第一个?
【学生逐一路演,教师计时,到 3 分钟给提示】
**每位学生路演后,教师引导互评:**
**师:** [学生名] 路演完了。谁来给一个「一个优点 + 一个改进建议」?
**生:** (预期:优点是……建议是……)
**师:** 好。[学生名],你对这个建议怎么看?如果你的下一个版本按照这个建议改,你觉得会怎样?
**生:** (预期:可能会更好 / 但是我担心……/ 我觉得还有另一个问题是……)
**师:** 好,这就是设计思维——不是「改了一定变好」,而是「改了会带来什么新的权衡」。
**学生路演引导词(当学生卡壳时使用):**
若学生在「设计意图」卡壳:
**师:** 你当时为什么把 HP 设成这个数字?是随机的还是有理由的?
若学生在「精彩瞬间」卡壳:
**师:** 锦标赛里你最紧张的是哪一场?或者最意外的是哪一场?就说那一场。
若学生在「设计复盘」卡壳:
**师:** 你输的那场,对方哪个属性让你最难受?如果把你的属性分配改一下,能不能针对性地克制它?
若学生整体准备不足:
**师:** 好,我给你 30 秒想一下。有一个问题你一定能回答:你和谁打的那一场,最让你印象深刻?说那一场就行。
**路演结束后的整体反思问题:**
**师:** 好,大家都路演完了。我有一个问题——你们今天路演,最难说的是哪个部分?是「展示角色」还是「设计意图」还是「设计复盘」?
【诊断点:学生是否认为「说为什么」比「说是什么」更难,反映其对设计思维的认知层级】【理解层】
**【分支A】若学生说「最难的是设计复盘」**
**师:** 对,复盘最难。因为复盘要求你承认「我的设计有问题」,然后还要找到具体是什么问题,还要想出怎么改。这个能力叫「反事实推理」——世界上所有的工程师和设计师都在练这个能力。你们今天已经开始练了。
**【分支B】若学生说「最难的是设计意图因为我当时随便分的」**
**师:** 没关系,随便分也有设计意图——「我不知道怎么分,所以平均分」本身就是一个决策。下一次你设计角色的时候,在分属性之前先写一句话:「我要做一个什么风格的角色,要克制什么打法」,有了这句话,属性分配就有了方向。
---
**第四幕:延续 (Continue) — 5分钟** 🚀
**【环节】四课主线总结 (3分钟)**
**师:** 最后一个问题——锦标赛结束后,你们觉得游戏平不平衡?有没有某种打法「必赢」?
**生:** (预期:有一点不平衡 / 感觉高 ATK 的更容易赢 / 感觉差不多)
**师:** 游戏平衡是永远做不到「完美」的——哪怕是王者荣耀这种级别的游戏,每个版本也要出「平衡性调整」。你们今天做的游戏也一样,会有某些打法偏强。这是正常的。重要的是你们发现了这个问题,发现了就可以迭代。
好,进入总结。
**师:** 今天是涂鸦 PK 系列的最后一课。我们来回顾一下这四课做了什么——
【投屏展示四课主线总结】
```
第8课需求文档 → 画图工具
你用工程流程做了一个工具
第9课需求文档 → 战斗系统 + 测试验证
你用数据验证了你设计的规则
第10课增量需求 → 动画 + 音效
你用「感觉描述」扩展了系统
第11课数据驱动 → 角色系统 + 锦标赛
你让系统可以无限扩展
```
**师:** 但这四课里,你们学到的不是游戏。
你们学到的是——如何把一个想法,变成可以被验证、可以被扩展、可以被迭代的系统。
需求文档让你在动手之前想清楚。
测试验证让你知道自己做的对不对。
增量需求让你在已有基础上扩展,而不是推倒重来。
数据驱动让你的系统可以无限成长,不被代码本身限制。
**师:** 这四件事,就是工程师做事的方式。你们今天用这个方式,做出了一个可以全班对战的游戏。
**【环节】下节预告 + 5分钟挑战 (2分钟)**
**师:** 下节课,我们开始新的项目。你们在前面学到的这些能力——需求文档、增量迭代、数据驱动——会在接下来的项目里继续用到。
**师:** 本周 5 分钟 AI 挑战——给你的角色设计一个「2.0 版本」:改变属性分配,写下这次你要克制哪种打法,以及你的设计理由。不需要改代码,只需要改 json 文件里的属性数值,然后写一段话说清楚「我为什么这样改」。下节课分享。
---
### 5. AI助教使用指南
**窗口A——增量需求文档模板教师投屏用**
```
增量需求文档 v1.0
项目涂鸦PK战斗游戏
新增功能roles 角色选择系统
1. 从 roles/ 文件夹读取所有 *.json 文件
- 每个 json 包含字段name, hp, atk, def, spd, skill
- 如果文件夹为空或读取失败,显示提示信息「暂无角色,请检查 roles/ 文件夹」
2. 战斗开始前显示角色选择界面
- 左侧:玩家角色列表(从 json 文件读取,动态生成)
- 右侧AI 对手角色列表(同上)
- 每个角色显示名字 + HP/ATK/DEF/SPD 数值
- 两侧可选同一个角色(允许镜像对战)
3. 选择确认
- 点击「开始战斗」按钮进入战斗
- 战斗时加载对应的同名 .png 文件作为角色图片
- 如果 .png 不存在,使用默认占位图
4. 不修改原战斗逻辑
- 只新增角色选择这一层
- 原来的战斗循环、伤害计算、特技逻辑保持不动
```
**窗口C——保底执行提示词学生直接使用**
```
我已有一个 Phaser.js 战斗游戏,两个角色数据现在是硬编码的。
现在我要加角色选择系统:
1. 从 roles/ 文件夹读取所有 *.json 文件
(每个文件包含 name/hp/atk/def/spd/skill 字段)
2. 显示角色选择界面:左边选玩家角色(列表),右边选 AI 角色(列表)
3. 每个角色显示名字和属性数值
4. 选好后点「开始战斗」进入游戏,加载对应的同名 .png 作为角色图片
5. 如果 .png 不存在,显示默认占位图
只输出新增/修改的代码部分,不要重写整个游戏。
在代码里加注释说明每段新增代码的作用。
```
**路演辅助提示词(给路演准备不足的学生):**
```
我的游戏角色数据是:
名字XX
HP=[数字], ATK=[数字], DEF=[数字], SPD=[数字]
特技:[特技描述]
在班级锦标赛中,我赢了[X]场,输了[X]场。
帮我用 3 个问题引导我做「设计复盘」——只问问题,不给答案。
```
**进阶提示词(给完成基础任务、想进一步扩展的学生):**
```
我的角色选择系统已经完成了。现在我想加一个「角色详情」功能:
点击角色卡片时,弹出一个详情面板,显示:
1. 角色大图(放大版的 .png
2. 属性条形图HP/ATK/DEF/SPD 各用一条进度条表示满分100
3. 特技说明(从 json 里读取 skill 字段,格式化显示)
4. 「选择此角色」按钮
只输出新增代码,不改变现有的选择界面逻辑。
```
---
### 6. 教师指南
**本课技术备注:**
**关于浏览器无法直接读取本地文件夹的问题:**
浏览器出于安全原因,无法用 `fetch('roles/角色.json')` 直接读取本地文件夹(会报 CORS 错误)。解决方案:
- 方案A推荐用 Trae IDE 的本地服务器功能启动项目Trae 内置 Live Server自动解决 CORS 问题)
- 方案B让 AI 生成一个简单的 Node.js 本地服务器(`node server.js`),在本地 8080 端口提供文件服务
- 方案C备用改为手动列出所有角色文件名`Promise.all` 批量 fetch避免了「自动扫描文件夹」的需求但需要每次加新角色时在列表里加一个文件名
**备课时务必先在 Trae 里跑通,确认本地服务器正常。**
**关于 Phaser.js 动态加载 Spritesheet**
Phaser 的 `this.load.spritesheet()` 需要在 `preload()` 阶段调用,不能在游戏运行时动态加载。如果 AI 生成的代码在 `create()` 里加载图片,会报错。解决方案:提示词加上「角色图片需要在 preload 阶段加载,请用 scene.restart() 重新启动场景来实现动态切换角色」。
**锦标赛组织 FAQ**
| 问题 | 应对 |
|------|------|
| roles 系统没做好,无法进行锦标赛 | 退回到手动改代码换角色,每场由教师提前改好对应角色的数据,坚持锦标赛进行 |
| 某位学生的角色文件损坏/无法加载 | 临时用教师提前准备的备用角色文件替换 |
| 6人以下小班如只有4人| 改为双败赛制(输两场才淘汰),增加对战场次 |
| 学生对结果强烈不满认为Bug导致输了| 认可情绪说「你可以在5分钟挑战里设计2.0版本,下次来复仇」 |
| 路演时学生只说功能不说设计意图 | 用引导词打断:「等等,你刚才说了做了什么。现在告诉我,你为什么这样做?」 |
**路演控时技巧:**
- 用手机计时3分钟倒计时给学生看
- 到2分30秒时轻声提醒「还有30秒」
- 如果学生还没到「设计复盘」环节,可以跳过「精彩瞬间」直接问:「如果重来你会怎么改?」
- 互评限时 30 秒:「一个优点,一个建议,简短说」
- 路演顺序建议:冠军最后一个路演,制造节奏感;输了第一场的同学第一个路演,减少等待焦虑
**关于「数据驱动设计」的深度追问(给理解快的学生):**
如果有学生问「为什么要把数据和代码分开,直接写在代码里不是更方便吗?」,这是一个很好的问题,可以这样回答:
「当你只有 2 个角色的时候,写死在代码里确实更方便。但当你有 100 个角色的时候呢?当别人也要给你的游戏加角色的时候呢——你要让他改你的代码吗?数据和代码分离,是为了让扩展和维护变得容易。这个原则不只在游戏里,几乎所有的软件系统都在用——数据库、配置文件、皮肤系统,都是同一个思路。」
**锦标赛后的班级文化建立:**
锦标赛不是为了分出高下,是为了制造「真实战斗测试」的场景。建议在锦标赛结束后强调:
- 不设「最强角色」称号,只设「冠军」(今日锦标赛冠军,不代表绝对最强)
- 鼓励「最有创意设计」:给属性分配最特别、最有设计理由的角色点名表扬
- 鼓励「最佳复盘」:在路演中,给复盘最到位、最有具体改进方案的同学点名表扬
**课堂风险预案:**
- 如果 Trae 本地服务器启动失败:切换到 Kimi 直接生成 HTML不依赖本地服务器
- 如果锦标赛时间不够roles 系统做得慢):缩减锦标赛场次,只打半决赛 + 决赛(两场)
- 如果进度差异过大有学生roles系统5分钟做完有学生20分钟还没做好先完成的学生做进阶任务角色详情弹窗帮进度慢的学生看报错
---
### 7. 5分钟日常AI挑战
**本周挑战:** 给你的角色设计「2.0 版本」
**挑战说明:**
打开你角色的 json 文件修改属性数值设计一个新版本。要求写一段话3-5句说清楚三件事
1. 你改了哪些属性,改成了什么数字
2. 这次你想克制哪种打法(比如:克制高 ATK 爆发型 / 克制高 SPD 速攻型)
3. 你觉得这样改会带来什么新的弱点
不需要跑代码验证,只需要写出「设计意图」。
**下节课分享:** 下周课上选 2-3 位同学分享「我的 2.0 设计方案」,其他同学判断:这个改动真的能克制目标打法吗?
---
### 8. 拓展任务
**拓展一(推荐):角色详情卡**
在角色选择界面里,给每个角色加一个「详情」功能:点击角色名字时,弹出一个小面板,用条形图显示 HP/ATK/DEF/SPD 的比例关系满分100的进度条让玩家在选角色前能直观对比属性强弱。
提示:条形图可以用 HTML 的 `<progress>` 标签,或者用 CSS 的 `width` 百分比实现。
**拓展二(挑战):战斗回放摘要**
每场战斗结束后自动生成一段「战斗日志」记录关键回合如特技触发、HP 归零)的文字摘要。格式参考:
```
第3回合章鱼怪特技「连击」触发对火焰鸟造成双倍伤害 28 点
第5回合火焰鸟 HP 归零,章鱼怪以 HP 34 获胜
```
这个功能需要在战斗循环里加「事件记录」逻辑,是真正的功能扩展挑战。

View File

@@ -0,0 +1,422 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>涂鸦角色画图工具</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #1a1a2e;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
font-family: Arial, sans-serif;
color: #fff;
padding: 20px;
gap: 16px;
}
h1 { font-size: 22px; color: #ffd700; letter-spacing: 2px; }
.app { display: flex; gap: 24px; align-items: flex-start; }
/* 左侧画板 */
.left { display: flex; flex-direction: column; gap: 10px; }
.frame-tabs { display: flex; gap: 0; }
.tab {
padding: 8px 20px;
background: #2a2a4a;
border: 2px solid #444;
cursor: pointer;
font-size: 13px;
color: #aaa;
border-bottom: none;
}
.tab:first-child { border-radius: 8px 0 0 0; }
.tab:last-child { border-radius: 0 8px 0 0; }
.tab.active { background: #3a3a6a; border-color: #ffd700; color: #ffd700; }
#display-canvas {
display: block;
cursor: crosshair;
border: 2px solid #ffd700;
image-rendering: pixelated;
}
.tools { display: flex; gap: 8px; flex-wrap: wrap; }
.tool-btn {
padding: 7px 14px;
background: #2a2a4a;
border: 2px solid #555;
border-radius: 6px;
cursor: pointer;
color: #fff;
font-size: 13px;
transition: all 0.1s;
}
.tool-btn.active { border-color: #ffd700; background: #3a3a6a; color: #ffd700; }
.tool-btn:hover:not(.active) { border-color: #888; }
.palette { display: flex; flex-wrap: wrap; gap: 4px; max-width: 520px; }
.swatch {
width: 26px; height: 26px;
border-radius: 4px;
cursor: pointer;
border: 2px solid transparent;
transition: transform 0.1s;
}
.swatch:hover { transform: scale(1.2); }
.swatch.active { border-color: #fff; transform: scale(1.2); }
/* 右侧面板 */
.right { display: flex; flex-direction: column; gap: 12px; min-width: 160px; }
.panel {
background: #16213e;
border: 1px solid #0f3460;
border-radius: 10px;
padding: 14px;
display: flex;
flex-direction: column;
gap: 8px;
}
.panel h3 { font-size: 12px; color: #ffd700; text-transform: uppercase; letter-spacing: 1px; }
#preview-canvas {
image-rendering: pixelated;
align-self: center;
border: 1px solid #333;
background: #0a0a1a;
}
.btn {
padding: 9px 14px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: bold;
width: 100%;
transition: opacity 0.1s;
}
.btn:hover { opacity: 0.85; }
.btn-gold { background: #ffd700; color: #1a1a2e; }
.btn-blue { background: #0f3460; color: #fff; border: 1px solid #1a5276; }
.btn-red { background: #c0392b; color: #fff; }
.hint {
font-size: 11px;
color: #556;
line-height: 1.7;
}
.size-tag { font-size: 11px; color: #556; text-align: center; }
</style>
</head>
<body>
<h1>🎨 涂鸦角色画图工具</h1>
<div class="app">
<div class="left">
<div class="frame-tabs">
<div class="tab active" id="tab0" onclick="switchFrame(0)">帧1 · 待机</div>
<div class="tab" id="tab1" onclick="switchFrame(1)">帧2 · 攻击</div>
</div>
<canvas id="display-canvas"></canvas>
<div class="tools">
<button class="tool-btn active" id="btn-pen" onclick="setTool('pen')">🖊 画笔</button>
<button class="tool-btn" id="btn-eraser" onclick="setTool('eraser')">⬜ 橡皮</button>
<button class="tool-btn" id="btn-fill" onclick="setTool('fill')">🪣 填充</button>
<input type="color" id="custom-color" value="#ffffff"
onchange="setColor(this.value)"
title="自定义颜色"
style="width:36px;height:34px;border:2px solid #555;border-radius:6px;cursor:pointer;background:none;padding:1px;">
</div>
<div class="palette" id="palette"></div>
<div class="size-tag">画布 64×64 像素 · 8倍放大显示</div>
</div>
<div class="right">
<div class="panel">
<h3>动画预览</h3>
<canvas id="preview-canvas" width="128" height="128"></canvas>
<button class="btn btn-blue" onclick="togglePreview()" id="preview-btn">▶ 播放预览</button>
</div>
<div class="panel">
<h3>操作</h3>
<button class="btn btn-blue" onclick="copyFrame1to2()">📋 复制帧1→帧2</button>
<button class="btn btn-red" onclick="clearCurrentFrame()">🗑 清空当前帧</button>
</div>
<div class="panel">
<h3>导出</h3>
<button class="btn btn-gold" onclick="exportSpritesheet()">💾 导出 Spritesheet</button>
<button class="btn btn-blue" onclick="exportCurrentFrame()">💾 导出单帧 PNG</button>
</div>
<div class="panel">
<h3>使用说明</h3>
<div class="hint">
1. 在<b>帧1</b>画待机姿势<br>
2. 点「复制帧1→帧2」<br>
3. 切到<b>帧2</b>修改成攻击姿势<br>
4. 点▶预览动画效果<br>
5. 导出 Spritesheet 用于游戏
</div>
</div>
</div>
</div>
<script>
// ============================================================
// 配置
// ============================================================
const PX = 64; // 画布逻辑尺寸
const ZOOM = 8; // 显示倍率
const W = PX * ZOOM; // 显示尺寸 = 512
// ============================================================
// 数据
// ============================================================
const frameCanvases = [makeOffscreen(), makeOffscreen()];
let currentFrame = 0;
let currentTool = 'pen';
let currentColor = '#ffffff';
let isDrawing = false;
let previewTimer = null;
let previewIdx = 0;
function makeOffscreen() {
const c = document.createElement('canvas');
c.width = PX; c.height = PX;
return c;
}
// ============================================================
// 颜色调色板
// ============================================================
const COLORS = [
'#ffffff','#cccccc','#888888','#444444','#111111',
'#ff4444','#ff8800','#ffcc00','#aaff00','#00ff88',
'#00ffff','#0088ff','#4400ff','#aa00ff','#ff00aa',
'#ffaaaa','#ffddaa','#ffffaa','#aaffcc','#aaddff',
'#8B4513','#d2691e','#f4a460','#228B22','#006400',
'#2e86ab','#e94560','#f5a623','#7ed321','#417505',
];
function buildPalette() {
const el = document.getElementById('palette');
COLORS.forEach(c => {
const s = document.createElement('div');
s.className = 'swatch' + (c === currentColor ? ' active' : '');
s.style.background = c;
s.title = c;
s.onclick = () => {
currentColor = c;
document.getElementById('custom-color').value = c;
document.querySelectorAll('.swatch').forEach(x => x.classList.remove('active'));
s.classList.add('active');
};
el.appendChild(s);
});
}
function setColor(hex) {
currentColor = hex;
document.querySelectorAll('.swatch').forEach(x => x.classList.remove('active'));
}
// ============================================================
// 工具切换
// ============================================================
function setTool(t) {
currentTool = t;
['pen','eraser','fill'].forEach(n => {
document.getElementById('btn-' + n)?.classList.toggle('active', n === t);
});
}
// ============================================================
// 显示画布
// ============================================================
const display = document.getElementById('display-canvas');
display.width = W;
display.height = W;
const dCtx = display.getContext('2d');
dCtx.imageSmoothingEnabled = false;
function render() {
dCtx.clearRect(0, 0, W, W);
// 棋盘格背景(表示透明区域)
for (let y = 0; y < PX; y++) {
for (let x = 0; x < PX; x++) {
dCtx.fillStyle = (x + y) % 2 === 0 ? '#222' : '#1a1a1a';
dCtx.fillRect(x * ZOOM, y * ZOOM, ZOOM, ZOOM);
}
}
// 当前帧像素
dCtx.imageSmoothingEnabled = false;
dCtx.drawImage(frameCanvases[currentFrame], 0, 0, PX, PX, 0, 0, W, W);
// 网格线
dCtx.strokeStyle = 'rgba(255,255,255,0.04)';
dCtx.lineWidth = 0.5;
for (let i = 0; i <= PX; i++) {
dCtx.beginPath(); dCtx.moveTo(i * ZOOM, 0); dCtx.lineTo(i * ZOOM, W); dCtx.stroke();
dCtx.beginPath(); dCtx.moveTo(0, i * ZOOM); dCtx.lineTo(W, i * ZOOM); dCtx.stroke();
}
}
// ============================================================
// 画像素
// ============================================================
function getPos(e) {
const r = display.getBoundingClientRect();
return {
x: Math.max(0, Math.min(PX - 1, Math.floor((e.clientX - r.left) / ZOOM))),
y: Math.max(0, Math.min(PX - 1, Math.floor((e.clientY - r.top) / ZOOM))),
};
}
function putPixel(x, y) {
const fc = frameCanvases[currentFrame].getContext('2d');
if (currentTool === 'eraser') {
fc.clearRect(x, y, 1, 1);
} else {
fc.fillStyle = currentColor;
fc.fillRect(x, y, 1, 1);
}
render();
}
function floodFill(sx, sy) {
const fc = frameCanvases[currentFrame].getContext('2d');
const img = fc.getImageData(0, 0, PX, PX);
const d = img.data;
const ti = (sx + sy * PX) * 4;
const tR = d[ti], tG = d[ti+1], tB = d[ti+2], tA = d[ti+3];
const hex = currentColor.replace('#','');
const fR = parseInt(hex.slice(0,2),16);
const fG = parseInt(hex.slice(2,4),16);
const fB = parseInt(hex.slice(4,6),16);
if (tR===fR && tG===fG && tB===fB && tA===255) return;
const stack = [[sx, sy]];
const seen = new Set();
while (stack.length) {
const [x, y] = stack.pop();
if (x<0||x>=PX||y<0||y>=PX) continue;
const k = x + ',' + y;
if (seen.has(k)) continue;
seen.add(k);
const i = (x + y * PX) * 4;
if (d[i]!==tR||d[i+1]!==tG||d[i+2]!==tB||d[i+3]!==tA) continue;
d[i]=fR; d[i+1]=fG; d[i+2]=fB; d[i+3]=255;
stack.push([x+1,y],[x-1,y],[x,y+1],[x,y-1]);
}
fc.putImageData(img, 0, 0);
render();
}
display.addEventListener('mousedown', e => {
isDrawing = true;
const { x, y } = getPos(e);
currentTool === 'fill' ? floodFill(x, y) : putPixel(x, y);
});
display.addEventListener('mousemove', e => {
if (!isDrawing || currentTool === 'fill') return;
putPixel(getPos(e).x, getPos(e).y);
});
display.addEventListener('mouseup', () => isDrawing = false);
display.addEventListener('mouseleave', () => isDrawing = false);
// ============================================================
// 帧切换
// ============================================================
function switchFrame(idx) {
currentFrame = idx;
document.getElementById('tab0').classList.toggle('active', idx === 0);
document.getElementById('tab1').classList.toggle('active', idx === 1);
render();
}
// ============================================================
// 动画预览
// ============================================================
const previewCanvas = document.getElementById('preview-canvas');
const pCtx = previewCanvas.getContext('2d');
pCtx.imageSmoothingEnabled = false;
function togglePreview() {
const btn = document.getElementById('preview-btn');
if (previewTimer) {
clearInterval(previewTimer);
previewTimer = null;
btn.textContent = '▶ 播放预览';
pCtx.clearRect(0,0,128,128);
return;
}
btn.textContent = '⏹ 停止预览';
previewTimer = setInterval(() => {
pCtx.clearRect(0,0,128,128);
// 棋盘格
for (let y=0;y<4;y++) for (let x=0;x<4;x++) {
pCtx.fillStyle=(x+y)%2===0?'#222':'#1a1a1a';
pCtx.fillRect(x*32,y*32,32,32);
}
pCtx.imageSmoothingEnabled = false;
pCtx.drawImage(frameCanvases[previewIdx], 0, 0, PX, PX, 0, 0, 128, 128);
previewIdx = (previewIdx + 1) % 2;
}, 350);
}
// ============================================================
// 操作
// ============================================================
function copyFrame1to2() {
const src = frameCanvases[0].getContext('2d').getImageData(0,0,PX,PX);
frameCanvases[1].getContext('2d').putImageData(src, 0, 0);
if (currentFrame === 1) render();
alert('✅ 已将帧1复制到帧2\n现在切到帧2修改出攻击姿势吧');
}
function clearCurrentFrame() {
if (!confirm('确定清空当前帧吗?')) return;
frameCanvases[currentFrame].getContext('2d').clearRect(0,0,PX,PX);
render();
}
// ============================================================
// 导出
// ============================================================
function exportSpritesheet() {
const out = document.createElement('canvas');
out.width = PX * 2;
out.height = PX;
const oc = out.getContext('2d');
oc.imageSmoothingEnabled = false;
oc.drawImage(frameCanvases[0], 0, 0);
oc.drawImage(frameCanvases[1], PX, 0);
downloadCanvas(out, 'character-sheet.png');
alert('✅ Spritesheet 已导出!\n128×64 像素包含帧1和帧2');
}
function exportCurrentFrame() {
const name = currentFrame === 0 ? 'character-idle.png' : 'character-attack.png';
downloadCanvas(frameCanvases[currentFrame], name);
}
function downloadCanvas(c, name) {
const a = document.createElement('a');
a.download = name;
a.href = c.toDataURL('image/png');
a.click();
}
// ============================================================
// 初始化
// ============================================================
buildPalette();
render();
</script>
</body>
</html>

View File

@@ -0,0 +1,583 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>涂鸦PK · 对战演示</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #1a1a2e;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
font-family: Arial, sans-serif;
color: #fff;
overflow: hidden;
}
canvas#game { display: block; }
.ui {
display: flex;
gap: 12px;
margin-top: 14px;
flex-wrap: wrap;
justify-content: center;
}
.action-btn {
padding: 12px 22px;
border: 2px solid;
border-radius: 8px;
cursor: pointer;
font-size: 15px;
font-weight: bold;
transition: all 0.12s;
min-width: 110px;
}
.action-btn:hover:not(:disabled) { transform: translateY(-2px); }
.action-btn:disabled { opacity: 0.35; cursor: not-allowed; transform: none; }
.btn-attack { background: #e94560; border-color: #ff6b81; color: #fff; }
.btn-heavy { background: #c0392b; border-color: #e74c3c; color: #fff; }
.btn-block { background: #0f3460; border-color: #1a5276; color: #adf; }
.btn-skill { background: #6c3483; border-color: #9b59b6; color: #fff; }
.log-box {
width: 700px;
max-height: 80px;
overflow-y: auto;
background: rgba(0,0,0,0.5);
border: 1px solid #333;
border-radius: 8px;
padding: 8px 12px;
margin-top: 10px;
font-size: 12px;
color: #ccc;
line-height: 1.8;
}
</style>
</head>
<body>
<canvas id="game" width="700" height="380"></canvas>
<div class="ui" id="action-ui">
<button class="action-btn btn-attack" onclick="playerAction('attack')">⚔️ 普通攻击</button>
<button class="action-btn btn-heavy" onclick="playerAction('heavy')">💥 重击</button>
<button class="action-btn btn-block" onclick="playerAction('block')">🛡 格挡</button>
<button class="action-btn btn-skill" onclick="playerAction('skill')">✨ 特技</button>
</div>
<div class="log-box" id="log"></div>
<script>
// ============================================================
// Web Audio 音效系统
// ============================================================
const AC = new (window.AudioContext || window.webkitAudioContext)();
function playSound(type) {
const recipes = {
hit: { freqs: [300, 60], dur: 0.08, wave: 'sawtooth', vol: 0.35 },
heavyHit: { freqs: [160, 25], dur: 0.20, wave: 'sawtooth', vol: 0.60 },
block: { freqs: [600, 400], dur: 0.10, wave: 'square', vol: 0.25 },
skill: { freqs: [200, 900], dur: 0.30, wave: 'triangle', vol: 0.40 },
death: { freqs: [350, 40], dur: 0.55, wave: 'sine', vol: 0.30 },
victory: { notes: [523,659,784,1047], dur: 0.12, wave: 'sine', vol: 0.35 },
};
if (type === 'victory') {
const r = recipes.victory;
r.notes.forEach((freq, i) => {
setTimeout(() => {
const o = AC.createOscillator();
const g = AC.createGain();
o.connect(g); g.connect(AC.destination);
o.type = r.wave;
o.frequency.setValueAtTime(freq, AC.currentTime);
g.gain.setValueAtTime(r.vol, AC.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, AC.currentTime + r.dur);
o.start(); o.stop(AC.currentTime + r.dur);
}, i * 140);
});
return;
}
const r = recipes[type];
if (!r) return;
const o = AC.createOscillator();
const g = AC.createGain();
o.connect(g); g.connect(AC.destination);
o.type = r.wave;
o.frequency.setValueAtTime(r.freqs[0], AC.currentTime);
o.frequency.exponentialRampToValueAtTime(r.freqs[1], AC.currentTime + r.dur);
g.gain.setValueAtTime(r.vol, AC.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, AC.currentTime + r.dur);
o.start(); o.stop(AC.currentTime + r.dur);
}
// ============================================================
// 角色配置(学生可替换这里的属性)
// ============================================================
const PLAYER_CONFIG = {
name: '章鱼怪',
emoji: '🐙',
color: '#39ff14',
hp: 50, maxHp: 50,
atk: 15, def: 5, spd: 8,
skill: { name: '电击', type: 'burn', desc: '燃烧:每回合额外-5血持续3回合' },
skillUsed: false,
};
const ENEMY_CONFIG = {
name: '机器人',
emoji: '🤖',
color: '#e94560',
hp: 60, maxHp: 60,
atk: 12, def: 8, spd: 6,
skill: { name: '护甲', type: 'shield', desc: '护甲:下回合格挡所有伤害' },
skillUsed: false,
};
// ============================================================
// 游戏状态
// ============================================================
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const P_BASE_X = 160, E_BASE_X = 540, CHAR_Y = 200;
const state = {
phase: 'player-turn', // player-turn | animating | enemy-turn | gameover
player: { ...PLAYER_CONFIG, x: P_BASE_X, y: CHAR_Y, shakeX: 0, flash: 0, alpha: 1, burn: 0, shielded: false },
enemy: { ...ENEMY_CONFIG, x: E_BASE_X, y: CHAR_Y, shakeX: 0, flash: 0, alpha: 1, burn: 0, shielded: false },
winner: null,
screenShake: 0,
log: [],
};
// ============================================================
// 动画队列
// ============================================================
const animations = [];
let animating = false;
function queueAnim(fn) {
animations.push(fn);
}
async function runAnimations() {
if (animating) return;
animating = true;
setButtonsDisabled(true);
while (animations.length > 0) {
const fn = animations.shift();
await fn();
}
animating = false;
if (state.phase !== 'gameover') {
setButtonsDisabled(false);
}
}
// ============================================================
// 渲染
// ============================================================
function lerp(a, b, t) { return a + (b - a) * t; }
function drawChar(c, facingRight) {
const { x, y, shakeX, flash, alpha, burn, shielded } = c;
const size = 72;
const sx = x + shakeX;
ctx.save();
ctx.globalAlpha = alpha;
// 护盾光环
if (shielded) {
ctx.beginPath();
ctx.arc(sx, y, size / 2 + 14, 0, Math.PI * 2);
ctx.strokeStyle = `rgba(100,180,255,${0.5 + 0.3 * Math.sin(Date.now() / 200)})`;
ctx.lineWidth = 4;
ctx.stroke();
}
// 燃烧光圈
if (burn > 0) {
ctx.beginPath();
ctx.arc(sx, y, size / 2 + 10, 0, Math.PI * 2);
ctx.strokeStyle = `rgba(255,100,0,${0.4 + 0.2 * Math.sin(Date.now() / 150)})`;
ctx.lineWidth = 3;
ctx.stroke();
}
// 主体(圆形)
ctx.beginPath();
ctx.arc(sx, y, size / 2, 0, Math.PI * 2);
if (flash > 0) {
ctx.fillStyle = `rgba(255,255,255,${flash})`;
} else {
ctx.fillStyle = c.color;
}
ctx.fill();
ctx.strokeStyle = 'rgba(0,0,0,0.4)';
ctx.lineWidth = 2;
ctx.stroke();
// Emoji 角色符号
ctx.font = '36px serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(c.emoji, sx, y + 2);
ctx.restore();
}
function drawHPBar(c, bx, by) {
const W = 180, H = 16;
const ratio = Math.max(0, c.hp / c.maxHp);
// 背景
ctx.fillStyle = '#1a1a2e';
ctx.beginPath();
ctx.roundRect(bx, by, W, H, 4);
ctx.fill();
// 血量
const barColor = ratio > 0.5 ? '#39ff14' : ratio > 0.25 ? '#ffd700' : '#e94560';
ctx.fillStyle = barColor;
ctx.beginPath();
ctx.roundRect(bx, by, W * ratio, H, 4);
ctx.fill();
// 边框
ctx.strokeStyle = '#555';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.roundRect(bx, by, W, H, 4);
ctx.stroke();
// 文字
ctx.fillStyle = '#fff';
ctx.font = 'bold 11px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(`${Math.max(0, c.hp)} / ${c.maxHp}`, bx + W / 2, by + H / 2);
}
function drawNamePlate(c, x, y, align) {
ctx.font = 'bold 16px Arial';
ctx.textAlign = align;
ctx.textBaseline = 'top';
ctx.fillStyle = c.color;
ctx.fillText(c.name, x, y);
ctx.font = '11px Arial';
ctx.fillStyle = '#aaa';
ctx.fillText(`ATK:${c.atk} DEF:${c.def} SPD:${c.spd}`, x, y + 20);
if (c.burn > 0) {
ctx.fillStyle = '#ff8800';
ctx.fillText(`🔥 燃烧 ×${c.burn}`, x, y + 36);
}
if (c.shielded) {
ctx.fillStyle = '#64b4ff';
ctx.fillText('🛡 护甲中', x, y + 36);
}
}
function render() {
const shake = state.screenShake;
ctx.save();
if (shake > 0) ctx.translate((Math.random() - 0.5) * shake * 8, (Math.random() - 0.5) * shake * 4);
// 背景渐变
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, '#1a1a2e');
grad.addColorStop(1, '#16213e');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 地面线
ctx.strokeStyle = 'rgba(255,255,255,0.08)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, 260); ctx.lineTo(canvas.width, 260);
ctx.stroke();
// VS 文字
ctx.font = 'bold 28px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'rgba(255,255,255,0.15)';
ctx.fillText('VS', 350, CHAR_Y);
// 角色
drawChar(state.player, true);
drawChar(state.enemy, false);
// 血条 + 名字
drawHPBar(state.player, 30, 20);
drawHPBar(state.enemy, 490, 20);
drawNamePlate(state.player, 30, 42, 'left');
drawNamePlate(state.enemy, 880, 42, 'right');
// 回合提示
if (state.phase === 'player-turn') {
ctx.font = 'bold 13px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#ffd700';
ctx.fillText('你的回合 — 选择行动', 350, 295);
} else if (state.phase === 'enemy-turn') {
ctx.font = 'bold 13px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#e94560';
ctx.fillText('敌人回合...', 350, 295);
}
// 特技状态
ctx.font = '11px Arial';
ctx.textAlign = 'left';
ctx.fillStyle = state.player.skillUsed ? '#555' : '#9b59b6';
ctx.fillText(`${state.player.skill.name}${state.player.skillUsed ? '已使用' : '可使用'}`, 30, 330);
// 游戏结束
if (state.phase === 'gameover') {
ctx.fillStyle = 'rgba(0,0,0,0.7)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold 48px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = state.winner === 'player' ? '#ffd700' : '#e94560';
ctx.fillText(state.winner === 'player' ? '🏆 胜利!' : '💀 失败...', 350, 160);
ctx.font = '18px Arial';
ctx.fillStyle = '#aaa';
ctx.fillText('点击页面重新开始', 350, 220);
}
ctx.restore();
requestAnimationFrame(render);
}
// ============================================================
// 战斗函数
// ============================================================
function calcDamage(atk, def, type) {
const base = Math.max(1, atk - def);
if (type === 'heavy') return Math.floor(base * 1.8);
return base;
}
function addLog(msg) {
state.log.unshift(msg);
const el = document.getElementById('log');
el.innerHTML = state.log.slice(0, 8).map(m => `<span>${m}</span>`).join('<br>');
}
function setButtonsDisabled(d) {
document.querySelectorAll('.action-btn').forEach(b => b.disabled = d);
}
// ============================================================
// 动画工厂
// ============================================================
function wait(ms) { return new Promise(r => setTimeout(r, ms)); }
async function animAttack(attacker, defender, heavy) {
const isPlayer = attacker === state.player;
const targetX = isPlayer ? E_BASE_X - 80 : P_BASE_X + 80;
const startX = attacker.x;
const steps = 12;
// 冲向对手
for (let i = 0; i <= steps; i++) {
attacker.x = lerp(startX, targetX, i / steps);
await wait(12);
}
// 受击效果
playSound(heavy ? 'heavyHit' : 'hit');
defender.flash = 1.0;
if (heavy) state.screenShake = 1;
for (let i = 0; i < 6; i++) {
defender.shakeX = (i % 2 === 0 ? 1 : -1) * (heavy ? 14 : 8);
await wait(40);
}
defender.shakeX = 0;
state.screenShake = 0;
// 弹回
for (let i = 0; i <= steps; i++) {
attacker.x = lerp(targetX, startX, i / steps);
defender.flash = Math.max(0, 1 - i / steps);
await wait(10);
}
attacker.x = startX;
defender.flash = 0;
}
async function animDeath(char) {
playSound('death');
for (let i = 0; i <= 20; i++) {
char.alpha = 1 - i / 20;
char.y = CHAR_Y + i * 3;
await wait(30);
}
}
async function animSkill(char) {
playSound('skill');
const startX = char.x;
for (let i = 0; i < 8; i++) {
char.shakeX = (i % 2 === 0 ? 1 : -1) * 6;
char.flash = 0.5;
await wait(50);
}
char.shakeX = 0;
char.flash = 0;
char.x = startX;
}
// ============================================================
// 玩家行动
// ============================================================
async function playerAction(type) {
if (state.phase !== 'player-turn' || animating) return;
if (type === 'skill' && state.player.skillUsed) {
addLog('⚠️ 特技已经使用过了');
return;
}
state.phase = 'animating';
const p = state.player, e = state.enemy;
// 玩家蓄力+出招
if (type === 'block') {
playSound('block');
p.shielded = true;
addLog(`🛡 ${p.name} 进入格挡状态!`);
await wait(400);
} else if (type === 'skill') {
p.skillUsed = true;
await animSkill(p);
if (p.skill.type === 'burn') {
e.burn = 3;
addLog(`🔥 ${p.name} 使用【${p.skill.name}】!${e.name} 进入燃烧状态持续3回合`);
}
} else {
const heavy = type === 'heavy';
const dmg = e.shielded ? 0 : calcDamage(p.atk, e.def, type);
await animAttack(p, e, heavy);
if (e.shielded) {
addLog(`🛡 ${e.name} 格挡了攻击!`);
e.shielded = false;
} else {
e.hp -= dmg;
addLog(`${heavy ? '💥' : '⚔️'} ${p.name} 攻击 ${e.name},造成 ${dmg} 点伤害${heavy ? '(重击!)' : ''}`);
}
}
// 检查敌人死亡
if (e.hp <= 0) {
e.hp = 0;
await animDeath(e);
playSound('victory');
state.phase = 'gameover';
state.winner = 'player';
addLog(`🏆 ${p.name} 获胜!`);
return;
}
// 燃烧 tick
if (e.burn > 0) {
e.hp -= 5; e.burn--;
addLog(`🔥 ${e.name} 受到燃烧伤害 -5 血(剩余 ${e.burn} 回合)`);
if (e.hp <= 0) {
e.hp = 0;
await animDeath(e);
state.phase = 'gameover';
state.winner = 'player';
addLog(`🏆 ${p.name} 获胜!`);
return;
}
}
// 玩家格挡清除
if (type !== 'block') p.shielded = false;
await wait(300);
state.phase = 'enemy-turn';
// 敌人AI回合
await wait(600);
await enemyTurn();
}
// ============================================================
// 敌人AI
// ============================================================
async function enemyTurn() {
const p = state.player, e = state.enemy;
let action = 'attack';
// 简单AIHP低于30%用技能25%概率重击,偶尔格挡
if (!e.skillUsed && e.hp < e.maxHp * 0.3) {
action = 'skill';
} else if (Math.random() < 0.25) {
action = 'heavy';
} else if (Math.random() < 0.15) {
action = 'block';
}
if (action === 'block') {
playSound('block');
e.shielded = true;
addLog(`🛡 ${e.name} 进入格挡状态!`);
await wait(400);
} else if (action === 'skill') {
e.skillUsed = true;
await animSkill(e);
if (e.skill.type === 'shield') {
e.shielded = true;
addLog(`🛡 ${e.name} 使用【${e.skill.name}】!下回合格挡所有伤害`);
}
} else {
const heavy = action === 'heavy';
const dmg = p.shielded ? 0 : calcDamage(e.atk, p.def, action);
await animAttack(e, p, heavy);
if (p.shielded) {
addLog(`🛡 ${p.name} 格挡了攻击!`);
p.shielded = false;
} else {
p.hp -= dmg;
addLog(`${heavy ? '💥' : '⚔️'} ${e.name} 攻击 ${p.name},造成 ${dmg} 点伤害${heavy ? '(重击!)' : ''}`);
}
}
// 燃烧敌人tick
if (p.burn > 0) {
p.hp -= 5; p.burn--;
}
// 检查玩家死亡
if (p.hp <= 0) {
p.hp = 0;
await animDeath(p);
state.phase = 'gameover';
state.winner = 'enemy';
addLog(`💀 ${e.name} 获胜!`);
return;
}
await wait(300);
state.phase = 'player-turn';
addLog('— 你的回合 —');
}
// 点击重开
canvas.addEventListener('click', () => {
if (state.phase === 'gameover') location.reload();
});
// ============================================================
// 启动
// ============================================================
render();
addLog('⚔️ 对战开始!你先出手。');
</script>
</body>
</html>

View File

@@ -0,0 +1,474 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>角色动画展示</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #1a1a2e;
display: flex;
flex-direction: column;
align-items: center;
padding: 24px;
font-family: Arial, sans-serif;
color: #fff;
gap: 20px;
}
h1 { color: #ffd700; font-size: 20px; letter-spacing: 2px; }
canvas { border: 1px solid #333; border-radius: 8px; }
.controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: center;
}
.anim-btn {
padding: 10px 18px;
border: 2px solid #555;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
background: #16213e;
color: #fff;
transition: all 0.1s;
}
.anim-btn:hover { border-color: #ffd700; color: #ffd700; transform: translateY(-2px); }
.import-area {
display: flex;
align-items: center;
gap: 12px;
background: #16213e;
border: 1px solid #333;
border-radius: 8px;
padding: 12px 20px;
}
.import-area label { font-size: 13px; color: #aaa; }
.import-area input { display: none; }
.import-btn {
padding: 8px 16px;
background: #0f3460;
border: 1px solid #1a5276;
border-radius: 6px;
cursor: pointer;
color: #adf;
font-size: 13px;
}
.desc { font-size: 12px; color: #667; text-align: center; max-width: 600px; line-height: 1.7; }
</style>
</head>
<body>
<h1>🎬 角色动画展示</h1>
<div class="import-area">
<label>导入你画的角色:</label>
<button class="import-btn" onclick="document.getElementById('img-input').click()">📂 选择 PNG</button>
<input type="file" id="img-input" accept="image/png" onchange="loadCustomChar(this)">
<label id="import-hint" style="color:#ffd700;font-size:12px;">(不导入则使用默认角色)</label>
</div>
<canvas id="c" width="600" height="300"></canvas>
<div class="controls">
<button class="anim-btn" onclick="playAnim('idle')">🌀 待机</button>
<button class="anim-btn" onclick="playAnim('attack')">⚔️ 普通攻击</button>
<button class="anim-btn" onclick="playAnim('heavy')">💥 重击</button>
<button class="anim-btn" onclick="playAnim('block')">🛡 格挡</button>
<button class="anim-btn" onclick="playAnim('hit')">😵 受击</button>
<button class="anim-btn" onclick="playAnim('skill')">✨ 释放特技</button>
<button class="anim-btn" onclick="playAnim('death')">💀 死亡</button>
<button class="anim-btn" onclick="playAnim('victory')">🏆 胜利</button>
</div>
<div class="desc">
点击按钮查看不同的动画效果。<br>
所有动画都用 <strong>Phaser Tween</strong> 实现,只需要一张图片,不需要多帧素材。<br>
可以导入你在画图工具里画的角色 PNG看看它配上动画是什么效果。
</div>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const CX = canvas.width / 2;
const CY = canvas.height / 2 - 20;
// Web Audio
const AC = new (window.AudioContext || window.webkitAudioContext)();
function playSound(type) {
const R = {
hit: [300, 50, 0.08, 'sawtooth', 0.35],
heavy: [160, 25, 0.20, 'sawtooth', 0.55],
block: [600, 400, 0.10, 'square', 0.25],
skill: [200, 900, 0.30, 'triangle', 0.40],
death: [350, 40, 0.55, 'sine', 0.30],
victory: null,
};
if (type === 'victory') {
[523,659,784,1047].forEach((f,i) => setTimeout(() => {
const o=AC.createOscillator(), g=AC.createGain();
o.connect(g); g.connect(AC.destination);
o.type='sine'; o.frequency.value=f;
g.gain.setValueAtTime(0.3, AC.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, AC.currentTime+0.12);
o.start(); o.stop(AC.currentTime+0.12);
}, i*140));
return;
}
const r = R[type]; if (!r) return;
const o=AC.createOscillator(), g=AC.createGain();
o.connect(g); g.connect(AC.destination);
o.type=r[3]; o.frequency.setValueAtTime(r[0], AC.currentTime);
o.frequency.exponentialRampToValueAtTime(r[1], AC.currentTime+r[2]);
g.gain.setValueAtTime(r[4], AC.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, AC.currentTime+r[2]);
o.start(); o.stop(AC.currentTime+r[2]);
}
// 角色状态
let charImg = null;
let charFrameCount = 1; // 总帧数(导入 spritesheet 时自动检测)
let charFrameW = 0; // 每帧宽度(像素)
let currentCharFrame = 0; // 当前显示第几帧
const char = {
x: CX, y: CY,
scale: 1, scaleY: 1,
rotation: 0,
alpha: 1,
shakeX: 0,
tint: null, // 'red' | 'blue' | null
glow: 0,
particles: [],
};
// 默认角色:用 canvas 画一个可爱的涂鸦怪
const defaultChar = (() => {
const c = document.createElement('canvas');
c.width = 80; c.height = 80;
const cx = c.getContext('2d');
// 身体
cx.fillStyle = '#39ff14';
cx.beginPath(); cx.ellipse(40,45,32,28,0,0,Math.PI*2); cx.fill();
// 眼睛
cx.fillStyle = '#fff';
cx.beginPath(); cx.arc(28,35,9,0,Math.PI*2); cx.fill();
cx.beginPath(); cx.arc(52,35,9,0,Math.PI*2); cx.fill();
cx.fillStyle = '#000';
cx.beginPath(); cx.arc(30,36,5,0,Math.PI*2); cx.fill();
cx.beginPath(); cx.arc(54,36,5,0,Math.PI*2); cx.fill();
// 嘴巴
cx.strokeStyle = '#000'; cx.lineWidth = 2;
cx.beginPath(); cx.arc(40,50,10,0.2,Math.PI-0.2); cx.stroke();
// 手臂
cx.strokeStyle = '#39ff14'; cx.lineWidth = 5;
cx.beginPath(); cx.moveTo(10,50); cx.lineTo(30,40); cx.stroke();
cx.beginPath(); cx.moveTo(70,50); cx.lineTo(50,40); cx.stroke();
// 轮廓
cx.strokeStyle = 'rgba(0,0,0,0.3)'; cx.lineWidth = 2;
cx.beginPath(); cx.ellipse(40,45,32,28,0,0,Math.PI*2); cx.stroke();
return c;
})();
function loadCustomChar(input) {
const file = input.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
const img = new Image();
img.onload = () => {
charImg = img;
// 自动检测是否是 Spritesheet宽度 >= 高度×1.5 就认为是多帧)
charFrameCount = (img.width >= img.height * 1.5)
? Math.round(img.width / img.height)
: 1;
charFrameW = img.width / charFrameCount;
currentCharFrame = 0;
document.getElementById('import-hint').textContent =
`✅ 已加载:${file.name}(检测到 ${charFrameCount} 帧)`;
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
// ============================================================
// 动画系统
// ============================================================
function wait(ms) { return new Promise(r => setTimeout(r, ms)); }
function lerp(a, b, t) { return a + (b - a) * t; }
let isPlaying = false;
async function playAnim(type) {
if (isPlaying) return;
isPlaying = true;
resetChar();
await animMap[type]();
if (type !== 'death') resetChar();
isPlaying = false;
}
function resetChar() {
Object.assign(char, {
x: CX, y: CY,
scale: 1, scaleY: 1,
rotation: 0,
alpha: 1,
shakeX: 0,
tint: null,
glow: 0,
});
char.particles = [];
}
const animMap = {
async idle() {
// 呼吸:上下浮动循环
for (let cycle = 0; cycle < 4; cycle++) {
for (let i = 0; i <= 20; i++) {
char.y = CY + Math.sin(i / 20 * Math.PI) * (-8);
char.scaleY = 1 + Math.sin(i / 20 * Math.PI) * 0.04;
await wait(25);
}
}
},
async attack() {
const targetX = CX + 110;
playSound('hit');
currentCharFrame = 0; // 待机帧
// 蓄力后缩
for (let i = 0; i <= 8; i++) {
char.x = lerp(CX, CX - 25, i / 8);
char.scale = lerp(1, 0.85, i / 8);
await wait(18);
}
// 冲出时切换到攻击帧
currentCharFrame = charFrameCount > 1 ? 1 : 0;
for (let i = 0; i <= 10; i++) {
char.x = lerp(CX - 25, targetX, i / 10);
char.scale = lerp(0.85, 1.1, i / 10);
await wait(12);
}
spawnParticles(char.x + 40, char.y, '#fff', 8);
// 弹回时切回待机帧
currentCharFrame = 0;
for (let i = 0; i <= 12; i++) {
char.x = lerp(targetX, CX, i / 12);
char.scale = lerp(1.1, 1, i / 12);
await wait(14);
}
},
async heavy() {
playSound('heavy');
// 大幅蓄力
for (let i = 0; i <= 15; i++) {
char.x = lerp(CX, CX - 55, i / 15);
char.scale = lerp(1, 0.7, i / 15);
char.scaleY = lerp(1, 1.3, i / 15);
await wait(18);
}
// 爆发冲出时切到攻击帧
currentCharFrame = charFrameCount > 1 ? 1 : 0;
const targetX = CX + 140;
for (let i = 0; i <= 8; i++) {
char.x = lerp(CX - 55, targetX, i / 8);
char.scale = lerp(0.7, 1.4, i / 8);
char.scaleY = lerp(1.3, 0.8, i / 8);
await wait(10);
}
spawnParticles(char.x + 50, char.y, '#ff8800', 20);
// 弹回切回待机帧
currentCharFrame = 0;
for (let i = 0; i <= 14; i++) {
char.x = lerp(targetX, CX, i / 14);
char.scale = lerp(1.4, 1, i / 14);
char.scaleY = lerp(0.8, 1, i / 14);
await wait(14);
}
},
async block() {
playSound('block');
char.tint = 'blue';
for (let i = 0; i <= 6; i++) {
char.scale = lerp(1, 1.15, i / 6);
char.glow = lerp(0, 1, i / 6);
await wait(20);
}
await wait(600);
for (let i = 0; i <= 8; i++) {
char.scale = lerp(1.15, 1, i / 8);
char.glow = lerp(1, 0, i / 8);
await wait(20);
}
char.tint = null;
},
async hit() {
playSound('hit');
char.tint = 'red';
for (let i = 0; i < 7; i++) {
char.shakeX = (i % 2 === 0 ? 1 : -1) * 12;
char.scale = i === 0 ? 0.9 : 1;
await wait(45);
}
char.shakeX = 0;
char.tint = null;
},
async skill() {
playSound('skill');
// 旋转蓄能
for (let i = 0; i <= 20; i++) {
char.rotation = lerp(0, 360, i / 20);
char.scale = lerp(1, 1.4, i / 20);
char.glow = i / 20;
await wait(20);
}
char.rotation = 0;
spawnParticles(char.x, char.y, '#ffd700', 30);
await wait(100);
for (let i = 0; i <= 10; i++) {
char.scale = lerp(1.4, 1, i / 10);
char.glow = lerp(1, 0, i / 10);
await wait(20);
}
},
async death() {
playSound('death');
for (let i = 0; i <= 25; i++) {
char.rotation = lerp(0, 360, i / 25);
char.scale = lerp(1, 0, i / 25);
char.alpha = lerp(1, 0, i / 25);
char.y = lerp(CY, CY + 60, i / 25);
await wait(30);
}
spawnParticles(CX, CY + 30, '#e94560', 25);
},
async victory() {
playSound('victory');
// 跳跃弹跳
for (let bounce = 0; bounce < 3; bounce++) {
for (let i = 0; i <= 15; i++) {
char.y = CY - Math.sin(i / 15 * Math.PI) * (50 - bounce * 12);
char.scaleX = 1 - Math.sin(i / 15 * Math.PI) * 0.15;
char.scaleY = 1 + Math.sin(i / 15 * Math.PI) * 0.2;
await wait(25);
}
if (bounce < 2) spawnParticles(char.x, char.y + 40, '#ffd700', 12);
}
char.scaleX = 1; char.scaleY = 1;
},
};
// ============================================================
// 粒子系统
// ============================================================
function spawnParticles(x, y, color, count) {
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 2 + Math.random() * 5;
char.particles.push({
x, y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed - 2,
life: 1,
color,
size: 2 + Math.random() * 4,
});
}
}
function updateParticles() {
char.particles = char.particles.filter(p => p.life > 0);
for (const p of char.particles) {
p.x += p.vx;
p.y += p.vy;
p.vy += 0.18;
p.life -= 0.025;
}
}
// ============================================================
// 渲染循环
// ============================================================
function render() {
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, '#1a1a2e');
grad.addColorStop(1, '#16213e');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 地面
ctx.strokeStyle = 'rgba(255,255,255,0.06)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, CY + 60); ctx.lineTo(canvas.width, CY + 60);
ctx.stroke();
updateParticles();
// 粒子
for (const p of char.particles) {
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
// 角色
const img = charImg || defaultChar;
const SIZE = 80;
const sx = char.x + char.shakeX;
const sy = char.y;
ctx.save();
ctx.globalAlpha = char.alpha;
ctx.translate(sx, sy);
ctx.rotate(char.rotation * Math.PI / 180);
ctx.scale(char.scale * (char.scaleX || 1), char.scaleY);
// 发光效果
if (char.glow > 0) {
ctx.shadowColor = char.tint === 'blue' ? '#64b4ff' : '#ffd700';
ctx.shadowBlur = char.glow * 30;
}
// 计算当前帧的源矩形
const fw = charImg ? charFrameW : img.width;
const fh = charImg ? img.height : img.height;
const fx = charImg ? currentCharFrame * charFrameW : 0;
// 颜色叠加
if (char.tint) {
ctx.drawImage(img, fx, 0, fw, fh, -SIZE/2, -SIZE/2, SIZE, SIZE);
ctx.globalCompositeOperation = 'source-atop';
ctx.fillStyle = char.tint === 'red' ? 'rgba(255,80,80,0.55)' : 'rgba(100,150,255,0.5)';
ctx.fillRect(-SIZE/2, -SIZE/2, SIZE, SIZE);
ctx.globalCompositeOperation = 'source-over';
} else {
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, fx, 0, fw, fh, -SIZE/2, -SIZE/2, SIZE, SIZE);
}
ctx.restore();
// 说明文字
ctx.font = '13px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#556';
ctx.fillText('← 点击上方按钮播放动画', CX, canvas.height - 14);
requestAnimationFrame(render);
}
render();
</script>
</body>
</html>

View File

@@ -0,0 +1,460 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>魔幻俄罗斯方块 · Level 0</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #1a1a2e;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: Arial, sans-serif;
color: #fff;
}
.game-container {
display: flex;
gap: 20px;
align-items: flex-start;
}
canvas#board {
border: 2px solid #e94560;
box-shadow: 0 0 20px rgba(233, 69, 96, 0.3);
display: block;
}
.side-panel { width: 120px; }
.panel-box {
background: #16213e;
border: 1px solid #0f3460;
border-radius: 8px;
padding: 12px;
margin-bottom: 14px;
}
.panel-box h3 {
font-size: 11px;
color: #e94560;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 6px;
}
.panel-box p {
font-size: 22px;
font-weight: bold;
}
.controls {
font-size: 11px;
color: #888;
line-height: 2;
}
#overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.75);
justify-content: center;
align-items: center;
}
#overlay.show { display: flex; }
.overlay-box {
background: #16213e;
border: 2px solid #e94560;
border-radius: 12px;
padding: 32px 40px;
text-align: center;
}
.overlay-box h2 { color: #e94560; font-size: 24px; margin-bottom: 12px; }
.overlay-box p { color: #aaa; margin-bottom: 20px; }
.overlay-box span { color: #fff; font-size: 28px; font-weight: bold; }
button {
padding: 10px 28px;
background: #e94560;
color: #fff;
border: none;
border-radius: 6px;
font-size: 15px;
cursor: pointer;
margin-top: 16px;
}
button:hover { background: #c73652; }
</style>
</head>
<body>
<div class="game-container">
<canvas id="board" width="300" height="600"></canvas>
<div class="side-panel">
<div class="panel-box">
<h3>下一个</h3>
<canvas id="next-canvas" width="80" height="80"></canvas>
</div>
<div class="panel-box">
<h3>得分</h3>
<p id="score">0</p>
</div>
<div class="panel-box">
<h3>等级</h3>
<p id="level">1</p>
</div>
<div class="panel-box">
<h3>消行</h3>
<p id="lines">0</p>
</div>
<div class="panel-box controls">
<h3>操作</h3>
← → 移动<br>
↑ 旋转<br>
↓ 加速<br>
空格 直落<br>
P 暂停
</div>
</div>
</div>
<div id="overlay">
<div class="overlay-box">
<h2>游戏结束</h2>
<p>最终得分</p>
<span id="final-score">0</span>
<br>
<button onclick="startGame()">再来一次</button>
</div>
</div>
<script>
// =====================
// 配置(需求文档对应的参数)
// =====================
const CONFIG = {
COLS: 10,
ROWS: 20,
CELL: 30,
BASE_SPEED: 800, // 初始下落间隔(毫秒)
SPEED_STEP: 50, // 每级加快多少毫秒
LINES_PER_LEVEL: 10, // 每消多少行升级
// 消1/2/3/4行的基础得分
SCORE_TABLE: [0, 100, 300, 500, 800],
};
// =====================
// 7种标准方块
// =====================
const TETROMINOES = [
{ shape: [[1,1,1,1]], color: '#00f5ff' }, // I
{ shape: [[1,1],[1,1]], color: '#ffd700' }, // O
{ shape: [[0,1,0],[1,1,1]], color: '#bf5fff' }, // T
{ shape: [[0,1,1],[1,1,0]], color: '#39ff14' }, // S
{ shape: [[1,1,0],[0,1,1]], color: '#ff3131' }, // Z
{ shape: [[1,0,0],[1,1,1]], color: '#ff8c00' }, // J
{ shape: [[0,0,1],[1,1,1]], color: '#0080ff' }, // L
];
// =====================
// 游戏状态变量
// =====================
let board, current, next;
let score, level, lines;
let running, paused;
let lastTs, dropAcc, dropInterval;
let rafId;
const canvas = document.getElementById('board');
const ctx = canvas.getContext('2d');
const nextCvs = document.getElementById('next-canvas');
const nextCtx = nextCvs.getContext('2d');
// =====================
// 启动 / 重置游戏
// =====================
function startGame() {
document.getElementById('overlay').classList.remove('show');
board = Array.from({ length: CONFIG.ROWS }, () => Array(CONFIG.COLS).fill(0));
score = 0; level = 1; lines = 0;
running = true; paused = false;
dropInterval = CONFIG.BASE_SPEED;
dropAcc = 0; lastTs = 0;
updateHUD();
next = newPiece();
spawnPiece();
if (rafId) cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(loop);
}
// =====================
// 生成随机方块对象
// =====================
function newPiece() {
const t = TETROMINOES[Math.floor(Math.random() * TETROMINOES.length)];
return {
shape: t.shape.map(r => [...r]),
color: t.color,
x: Math.floor(CONFIG.COLS / 2) - Math.floor(t.shape[0].length / 2),
y: 0,
};
}
// =====================
// 在顶部生成下一块
// =====================
function spawnPiece() {
current = next;
current.x = Math.floor(CONFIG.COLS / 2) - Math.floor(current.shape[0].length / 2);
current.y = 0;
next = newPiece();
drawNext();
if (hit(current, 0, 0)) endGame();
}
// =====================
// 碰撞检测
// =====================
function hit(piece, dx, dy) {
for (let r = 0; r < piece.shape.length; r++) {
for (let c = 0; c < piece.shape[r].length; c++) {
if (!piece.shape[r][c]) continue;
const nx = piece.x + c + dx;
const ny = piece.y + r + dy;
if (nx < 0 || nx >= CONFIG.COLS) return true;
if (ny >= CONFIG.ROWS) return true;
if (ny >= 0 && board[ny][nx]) return true;
}
}
return false;
}
// =====================
// 顺时针旋转矩阵
// =====================
function rotateCW(shape) {
const R = shape.length, C = shape[0].length;
const out = Array.from({ length: C }, () => Array(R).fill(0));
for (let r = 0; r < R; r++)
for (let c = 0; c < C; c++)
out[c][R - 1 - r] = shape[r][c];
return out;
}
// =====================
// 将当前方块锁定到棋盘
// =====================
function lock() {
for (let r = 0; r < current.shape.length; r++)
for (let c = 0; c < current.shape[r].length; c++)
if (current.shape[r][c] && current.y + r >= 0)
board[current.y + r][current.x + c] = current.color;
clearLines();
spawnPiece();
}
// =====================
// 消行(从下往上扫描)
// =====================
function clearLines() {
let cleared = 0;
for (let r = CONFIG.ROWS - 1; r >= 0; r--) {
if (board[r].every(cell => cell !== 0)) {
board.splice(r, 1);
board.unshift(Array(CONFIG.COLS).fill(0));
cleared++;
r++; // 重新检查移下来的行
}
}
if (cleared === 0) return;
score += CONFIG.SCORE_TABLE[cleared] * level;
lines += cleared;
const newLevel = Math.floor(lines / CONFIG.LINES_PER_LEVEL) + 1;
if (newLevel > level) {
level = newLevel;
dropInterval = Math.max(100, CONFIG.BASE_SPEED - (level - 1) * CONFIG.SPEED_STEP);
}
updateHUD();
}
// =====================
// 主循环
// =====================
function loop(ts) {
rafId = requestAnimationFrame(loop);
if (!running || paused) return;
const dt = ts - lastTs;
lastTs = ts;
dropAcc += dt;
if (dropAcc >= dropInterval) {
if (!hit(current, 0, 1)) current.y++;
else lock();
dropAcc = 0;
}
render();
}
// =====================
// 键盘控制
// =====================
document.addEventListener('keydown', e => {
if (!running) return;
switch (e.key) {
case 'ArrowLeft':
e.preventDefault();
if (!paused && !hit(current, -1, 0)) current.x--;
break;
case 'ArrowRight':
e.preventDefault();
if (!paused && !hit(current, 1, 0)) current.x++;
break;
case 'ArrowDown':
e.preventDefault();
if (!paused) {
if (!hit(current, 0, 1)) current.y++;
else lock();
dropAcc = 0;
}
break;
case 'ArrowUp':
e.preventDefault();
if (!paused) tryRotate();
break;
case ' ':
e.preventDefault();
if (!paused) hardDrop();
break;
case 'p': case 'P':
paused = !paused;
break;
}
});
function tryRotate() {
const rotated = rotateCW(current.shape);
const orig = current.shape;
current.shape = rotated;
// 踢墙:旋转后碰撞就尝试左右移一格
if (hit(current, 0, 0)) {
if (!hit(current, 1, 0)) current.x++;
else if (!hit(current, -1, 0)) current.x--;
else current.shape = orig;
}
}
function hardDrop() {
while (!hit(current, 0, 1)) current.y++;
lock();
}
// =====================
// 渲染
// =====================
function render() {
const S = CONFIG.CELL;
ctx.fillStyle = '#0a0a1a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 网格线
ctx.strokeStyle = 'rgba(255,255,255,0.04)';
for (let r = 0; r < CONFIG.ROWS; r++)
for (let c = 0; c < CONFIG.COLS; c++)
ctx.strokeRect(c * S, r * S, S, S);
// 棋盘上的固定方块
for (let r = 0; r < CONFIG.ROWS; r++)
for (let c = 0; c < CONFIG.COLS; c++)
if (board[r][c]) drawCell(ctx, c, r, board[r][c], S);
// 落点虚影
const ghost = ghostPiece();
ghost.shape.forEach((row, r) =>
row.forEach((v, c) => {
if (!v) return;
ctx.fillStyle = 'rgba(255,255,255,0.12)';
ctx.fillRect((ghost.x+c)*S+1, (ghost.y+r)*S+1, S-2, S-2);
})
);
// 当前方块
current.shape.forEach((row, r) =>
row.forEach((v, c) => {
if (v) drawCell(ctx, current.x+c, current.y+r, current.color, S);
})
);
// 暂停遮罩
if (paused) {
ctx.fillStyle = 'rgba(0,0,0,0.65)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#fff';
ctx.font = 'bold 28px Arial';
ctx.textAlign = 'center';
ctx.fillText('暂停', canvas.width/2, canvas.height/2);
ctx.font = '15px Arial';
ctx.fillStyle = '#aaa';
ctx.fillText('按 P 继续', canvas.width/2, canvas.height/2 + 34);
}
}
function drawCell(context, cx, cy, color, S) {
context.fillStyle = color;
context.fillRect(cx*S+1, cy*S+1, S-2, S-2);
context.fillStyle = 'rgba(255,255,255,0.18)';
context.fillRect(cx*S+1, cy*S+1, S-2, 4);
}
function ghostPiece() {
const g = { shape: current.shape, x: current.x, y: current.y };
while (!hit(g, 0, 1)) g.y++;
return g;
}
function drawNext() {
const S = 20;
nextCtx.fillStyle = '#16213e';
nextCtx.fillRect(0, 0, nextCvs.width, nextCvs.height);
const ox = Math.floor((4 - next.shape[0].length) / 2);
const oy = Math.floor((4 - next.shape.length) / 2);
next.shape.forEach((row, r) =>
row.forEach((v, c) => {
if (!v) return;
nextCtx.fillStyle = next.color;
nextCtx.fillRect((ox+c)*S, (oy+r)*S, S-1, S-1);
})
);
}
function updateHUD() {
document.getElementById('score').textContent = score;
document.getElementById('level').textContent = level;
document.getElementById('lines').textContent = lines;
}
function endGame() {
running = false;
document.getElementById('final-score').textContent = score;
document.getElementById('overlay').classList.add('show');
}
// 启动
startGame();
</script>
</body>
</html>

View File

@@ -0,0 +1,576 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>魔幻俄罗斯方块 · Level 1 示例:炸弹方块</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #1a1a2e;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: Arial, sans-serif;
color: #fff;
}
.game-container { display: flex; gap: 20px; align-items: flex-start; }
canvas#board {
border: 2px solid #e94560;
box-shadow: 0 0 20px rgba(233,69,96,0.3);
display: block;
}
.side-panel { width: 130px; }
.panel-box {
background: #16213e;
border: 1px solid #0f3460;
border-radius: 8px;
padding: 12px;
margin-bottom: 14px;
}
.panel-box h3 {
font-size: 11px;
color: #e94560;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 6px;
}
.panel-box p { font-size: 22px; font-weight: bold; }
.bomb-hint {
font-size: 11px;
color: #ffd700;
line-height: 1.7;
}
.controls { font-size: 11px; color: #888; line-height: 2; }
#overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.75);
justify-content: center;
align-items: center;
}
#overlay.show { display: flex; }
.overlay-box {
background: #16213e;
border: 2px solid #e94560;
border-radius: 12px;
padding: 32px 40px;
text-align: center;
}
.overlay-box h2 { color: #e94560; font-size: 24px; margin-bottom: 12px; }
.overlay-box p { color: #aaa; margin-bottom: 8px; }
.overlay-box span { color: #fff; font-size: 28px; font-weight: bold; }
button {
padding: 10px 28px;
background: #e94560;
color: #fff;
border: none;
border-radius: 6px;
font-size: 15px;
cursor: pointer;
margin-top: 16px;
}
button:hover { background: #c73652; }
/* 爆炸动画层 */
#explosion-canvas {
position: fixed;
pointer-events: none;
top: 0; left: 0;
width: 100%; height: 100%;
}
</style>
</head>
<body>
<canvas id="explosion-canvas"></canvas>
<div class="game-container">
<canvas id="board" width="300" height="600"></canvas>
<div class="side-panel">
<div class="panel-box">
<h3>下一个</h3>
<canvas id="next-canvas" width="80" height="80"></canvas>
</div>
<div class="panel-box">
<h3>得分</h3>
<p id="score">0</p>
</div>
<div class="panel-box">
<h3>等级</h3>
<p id="level">1</p>
</div>
<div class="panel-box">
<h3>消行</h3>
<p id="lines">0</p>
</div>
<div class="panel-box bomb-hint">
<h3>💣 炸弹规则</h3>
出现概率20%<br>
落地爆炸:<br>
炸掉 3×3 范围<br>
爆炸后触发消行<br>
额外奖励 200 分
</div>
<div class="panel-box controls">
<h3>操作</h3>
← → 移动<br>
↑ 旋转<br>
↓ 加速<br>
空格 直落<br>
P 暂停
</div>
</div>
</div>
<div id="overlay">
<div class="overlay-box">
<h2>游戏结束</h2>
<p>最终得分</p>
<span id="final-score">0</span>
<br>
<button onclick="startGame()">再来一次</button>
</div>
</div>
<script>
// =====================
// 配置
// =====================
const CONFIG = {
COLS: 10,
ROWS: 20,
CELL: 30,
BASE_SPEED: 800,
SPEED_STEP: 50,
LINES_PER_LEVEL: 10,
SCORE_TABLE: [0, 100, 300, 500, 800],
// 炸弹方块配置
BOMB_CHANCE: 0.20, // 出现概率 20%
BOMB_RADIUS: 1, // 爆炸半径(炸 3×3 = 半径1
BOMB_BONUS: 200, // 爆炸奖励分
};
// =====================
// 方块定义
// =====================
const TETROMINOES = [
{ shape: [[1,1,1,1]], color: '#00f5ff' },
{ shape: [[1,1],[1,1]], color: '#ffd700' },
{ shape: [[0,1,0],[1,1,1]], color: '#bf5fff' },
{ shape: [[0,1,1],[1,1,0]], color: '#39ff14' },
{ shape: [[1,1,0],[0,1,1]], color: '#ff3131' },
{ shape: [[1,0,0],[1,1,1]], color: '#ff8c00' },
{ shape: [[0,0,1],[1,1,1]], color: '#0080ff' },
];
// 炸弹方块(特殊形状:单格)
const BOMB_PIECE = {
shape: [[1]],
color: '#ff4500',
isBomb: true,
};
// =====================
// 状态
// =====================
let board, current, next;
let score, level, lines;
let running, paused;
let lastTs, dropAcc, dropInterval;
let rafId;
let particles = []; // 爆炸粒子
const canvas = document.getElementById('board');
const ctx = canvas.getContext('2d');
const nextCvs = document.getElementById('next-canvas');
const nextCtx = nextCvs.getContext('2d');
const expCvs = document.getElementById('explosion-canvas');
const expCtx = expCvs.getContext('2d');
// 让爆炸画布覆盖全屏
function resizeExpCanvas() {
expCvs.width = window.innerWidth;
expCvs.height = window.innerHeight;
}
resizeExpCanvas();
window.addEventListener('resize', resizeExpCanvas);
// =====================
// 启动游戏
// =====================
function startGame() {
document.getElementById('overlay').classList.remove('show');
board = Array.from({ length: CONFIG.ROWS }, () => Array(CONFIG.COLS).fill(0));
score = 0; level = 1; lines = 0;
running = true; paused = false;
dropInterval = CONFIG.BASE_SPEED;
dropAcc = 0; lastTs = 0;
particles = [];
updateHUD();
next = newPiece();
spawnPiece();
if (rafId) cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(loop);
}
// =====================
// 生成方块20% 概率炸弹)
// =====================
function newPiece() {
if (Math.random() < CONFIG.BOMB_CHANCE) {
return { ...BOMB_PIECE, shape: [[1]], x: 0, y: 0 };
}
const t = TETROMINOES[Math.floor(Math.random() * TETROMINOES.length)];
return { shape: t.shape.map(r => [...r]), color: t.color, isBomb: false, x: 0, y: 0 };
}
function spawnPiece() {
current = next;
current.x = Math.floor(CONFIG.COLS / 2) - Math.floor(current.shape[0].length / 2);
current.y = 0;
next = newPiece();
drawNext();
if (hit(current, 0, 0)) endGame();
}
// =====================
// 碰撞检测
// =====================
function hit(piece, dx, dy) {
for (let r = 0; r < piece.shape.length; r++) {
for (let c = 0; c < piece.shape[r].length; c++) {
if (!piece.shape[r][c]) continue;
const nx = piece.x + c + dx;
const ny = piece.y + r + dy;
if (nx < 0 || nx >= CONFIG.COLS) return true;
if (ny >= CONFIG.ROWS) return true;
if (ny >= 0 && board[ny][nx]) return true;
}
}
return false;
}
// =====================
// 旋转
// =====================
function rotateCW(shape) {
const R = shape.length, C = shape[0].length;
const out = Array.from({ length: C }, () => Array(R).fill(0));
for (let r = 0; r < R; r++)
for (let c = 0; c < C; c++)
out[c][R-1-r] = shape[r][c];
return out;
}
function tryRotate() {
if (current.isBomb) return; // 炸弹不需要旋转
const rotated = rotateCW(current.shape);
const orig = current.shape;
current.shape = rotated;
if (hit(current, 0, 0)) {
if (!hit(current, 1, 0)) current.x++;
else if (!hit(current, -1, 0)) current.x--;
else current.shape = orig;
}
}
// =====================
// 锁定方块
// =====================
function lock() {
if (current.isBomb) {
// 炸弹落地:触发爆炸
const bx = current.x;
const by = current.y;
explode(bx, by);
} else {
for (let r = 0; r < current.shape.length; r++)
for (let c = 0; c < current.shape[r].length; c++)
if (current.shape[r][c] && current.y + r >= 0)
board[current.y + r][current.x + c] = current.color;
clearLines();
}
spawnPiece();
}
// =====================
// 炸弹爆炸逻辑
// =====================
function explode(cx, cy) {
// 获取爆炸中心在棋盘上的坐标
const R = CONFIG.BOMB_RADIUS;
// 计算爆炸中心像素坐标(用于粒子特效)
const S = CONFIG.CELL;
// 找到 board 画布在屏幕上的位置
const boardRect = canvas.getBoundingClientRect();
const pixelX = boardRect.left + cx * S + S / 2;
const pixelY = boardRect.top + cy * S + S / 2;
// 清除爆炸范围内的所有格子
for (let r = cy - R; r <= cy + R; r++) {
for (let c = cx - R; c <= cx + R; c++) {
if (r >= 0 && r < CONFIG.ROWS && c >= 0 && c < CONFIG.COLS) {
board[r][c] = 0;
}
}
}
// 爆炸奖励分
score += CONFIG.BOMB_BONUS * level;
// 爆炸后触发消行检查
clearLines();
// 产生粒子特效
spawnParticles(pixelX, pixelY);
updateHUD();
}
// =====================
// 消行
// =====================
function clearLines() {
let cleared = 0;
for (let r = CONFIG.ROWS - 1; r >= 0; r--) {
if (board[r].every(cell => cell !== 0)) {
board.splice(r, 1);
board.unshift(Array(CONFIG.COLS).fill(0));
cleared++;
r++;
}
}
if (!cleared) return;
score += CONFIG.SCORE_TABLE[Math.min(cleared, 4)] * level;
lines += cleared;
const newLevel = Math.floor(lines / CONFIG.LINES_PER_LEVEL) + 1;
if (newLevel > level) {
level = newLevel;
dropInterval = Math.max(100, CONFIG.BASE_SPEED - (level-1) * CONFIG.SPEED_STEP);
}
updateHUD();
}
// =====================
// 粒子系统(爆炸特效)
// =====================
function spawnParticles(px, py) {
for (let i = 0; i < 30; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 3 + Math.random() * 6;
particles.push({
x: px, y: py,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: 1.0,
color: `hsl(${Math.floor(Math.random()*60)}, 100%, 60%)`,
size: 3 + Math.random() * 4,
});
}
}
function updateParticles() {
expCtx.clearRect(0, 0, expCvs.width, expCvs.height);
particles = particles.filter(p => p.life > 0);
for (const p of particles) {
p.x += p.vx;
p.y += p.vy;
p.vy += 0.2; // 重力
p.life -= 0.03;
expCtx.globalAlpha = p.life;
expCtx.fillStyle = p.color;
expCtx.beginPath();
expCtx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
expCtx.fill();
}
expCtx.globalAlpha = 1;
}
// =====================
// 主循环
// =====================
function loop(ts) {
rafId = requestAnimationFrame(loop);
if (!running || paused) return;
const dt = ts - lastTs;
lastTs = ts;
dropAcc += dt;
if (dropAcc >= dropInterval) {
if (!hit(current, 0, 1)) current.y++;
else lock();
dropAcc = 0;
}
render();
updateParticles();
}
// =====================
// 键盘
// =====================
document.addEventListener('keydown', e => {
if (!running) return;
switch (e.key) {
case 'ArrowLeft': e.preventDefault(); if (!paused && !hit(current,-1,0)) current.x--; break;
case 'ArrowRight': e.preventDefault(); if (!paused && !hit(current, 1,0)) current.x++; break;
case 'ArrowDown':
e.preventDefault();
if (!paused) { if (!hit(current,0,1)) current.y++; else lock(); dropAcc = 0; }
break;
case 'ArrowUp': e.preventDefault(); if (!paused) tryRotate(); break;
case ' ':
e.preventDefault();
if (!paused) { while (!hit(current,0,1)) current.y++; lock(); }
break;
case 'p': case 'P': paused = !paused; break;
}
});
// =====================
// 渲染
// =====================
function render() {
const S = CONFIG.CELL;
ctx.fillStyle = '#0a0a1a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = 'rgba(255,255,255,0.04)';
for (let r = 0; r < CONFIG.ROWS; r++)
for (let c = 0; c < CONFIG.COLS; c++)
ctx.strokeRect(c*S, r*S, S, S);
for (let r = 0; r < CONFIG.ROWS; r++)
for (let c = 0; c < CONFIG.COLS; c++)
if (board[r][c]) drawCell(ctx, c, r, board[r][c], S);
// 虚影(炸弹不显示虚影)
if (!current.isBomb) {
const ghost = ghostPiece();
ghost.shape.forEach((row, r) =>
row.forEach((v, c) => {
if (!v) return;
ctx.fillStyle = 'rgba(255,255,255,0.1)';
ctx.fillRect((ghost.x+c)*S+1, (ghost.y+r)*S+1, S-2, S-2);
})
);
}
// 当前方块
current.shape.forEach((row, r) =>
row.forEach((v, c) => {
if (!v) return;
if (current.isBomb) {
drawBombCell(ctx, current.x+c, current.y+r, S);
} else {
drawCell(ctx, current.x+c, current.y+r, current.color, S);
}
})
);
if (paused) {
ctx.fillStyle = 'rgba(0,0,0,0.65)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#fff';
ctx.font = 'bold 28px Arial';
ctx.textAlign = 'center';
ctx.fillText('暂停', canvas.width/2, canvas.height/2);
ctx.font = '15px Arial';
ctx.fillStyle = '#aaa';
ctx.fillText('按 P 继续', canvas.width/2, canvas.height/2+34);
}
}
function drawCell(context, cx, cy, color, S) {
context.fillStyle = color;
context.fillRect(cx*S+1, cy*S+1, S-2, S-2);
context.fillStyle = 'rgba(255,255,255,0.18)';
context.fillRect(cx*S+1, cy*S+1, S-2, 4);
}
function drawBombCell(context, cx, cy, S) {
// 炸弹外观:深红底 + 💣 表情
context.fillStyle = '#2a0a0a';
context.fillRect(cx*S+1, cy*S+1, S-2, S-2);
context.strokeStyle = '#ff4500';
context.lineWidth = 2;
context.strokeRect(cx*S+2, cy*S+2, S-4, S-4);
context.font = `${S-8}px serif`;
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText('💣', cx*S + S/2, cy*S + S/2 + 1);
}
function ghostPiece() {
const g = { shape: current.shape, x: current.x, y: current.y };
while (!hit(g, 0, 1)) g.y++;
return g;
}
function drawNext() {
const S = 20;
nextCtx.fillStyle = '#16213e';
nextCtx.fillRect(0, 0, nextCvs.width, nextCvs.height);
if (next.isBomb) {
nextCtx.font = '36px serif';
nextCtx.textAlign = 'center';
nextCtx.textBaseline = 'middle';
nextCtx.fillText('💣', nextCvs.width/2, nextCvs.height/2);
return;
}
const ox = Math.floor((4 - next.shape[0].length) / 2);
const oy = Math.floor((4 - next.shape.length) / 2);
next.shape.forEach((row, r) =>
row.forEach((v, c) => {
if (!v) return;
nextCtx.fillStyle = next.color;
nextCtx.fillRect((ox+c)*S, (oy+r)*S, S-1, S-1);
})
);
}
function updateHUD() {
document.getElementById('score').textContent = score;
document.getElementById('level').textContent = level;
document.getElementById('lines').textContent = lines;
}
function hardDrop() {
while (!hit(current, 0, 1)) current.y++;
lock();
}
function endGame() {
running = false;
document.getElementById('final-score').textContent = score;
document.getElementById('overlay').classList.add('show');
}
startGame();
</script>
</body>
</html>