一只会"唱歌"的竹雕蝉。像儿时一样,在手心搓动竹竿,竹蝉随之旋转;转得够快,蝉翼振动,唱出夏天的蝉鸣。支持触控、鼠标、键盘,还能用摄像头对着镜头搓手来玩。
「竹知了」(竹蝉)是一种传统民间玩具:一根竹签顶端插着一只竹片削成的蝉,用双手手心快速搓动竹签,竹蝉飞转,蝉翼振动发出酷似蝉鸣的声音。本作把这个童年夏天的手艺,做成了 3D 仿真网页游戏。
- 仿真优先:搓动 → 旋转 → 翼振 → 蝉鸣,环环相扣,转速、音高、音量、翼振频率、残影、地面拖尾都随同一个物理量 ω 联动
- 一种手感,四种输入:触控 / 鼠标拖拽、键盘按住、摄像头搓手,都换算成同一个角速度模型,手感一致
- 夏日竹影:暖阳、竹林、光尘、高枝上停着的蝉,营造盛夏氛围;楷体题字 + 等宽数据 + 蝉翼白面板,克制而有意境
一个完整的操作闭环:
搓动(触屏/鼠标/键盘/摄像头)
│ 输入 → 角加速度 ω̇
▼
竹竿旋转(ω 积分,摩擦衰减)
│ ω 超过起唱阈值
▼
蝉翼振动 + 蝉鸣(音调/响度随 ω)
│ 松手
▼
转速自然衰减 → 停唱
- 挑战模式在这个闭环上叠加目标:连鸣挑战看「连续鸣叫多久」、极速挑战看「冲到多高转速」、冲分挑战看「连击刷多少分」,各有实时计分与最高纪录。
| 功能 | 说明 |
|---|---|
| 🎋 竹质立体质感 | MeshStandard 材质 + 颜色/凹凸双贴图 + 程序化环境反射:真实几何竹节凸起、镜面光泽、木纹节疤 |
| 🔄 360° 旋转观察 | 「🔄 视角」一键切换,360° 环绕 + 顶部到底部无死角俯仰,滚轮/双指缩放近看 |
| 🌀 真实旋转物理 | 搓动加能、摩擦衰减、转速上限、进动晃动,转速表实时显示 RPM |
| 💫 高速旋转可视化 | 蝉翼残影 + 翼扫环形模糊盘 + 地面螺旋拖尾光纹,随转速旋转显现,直观感受转速快慢 |
| ☀️ 太阳实影 | 方向光(太阳)投射真实阴影,竹竿/蝉身/蝉翼在地面投出残影,随旋转转动 |
| 🦗 蝉鸣合成 | 零音频文件,Web Audio 实时合成:锯齿波 + 翼振 AM 调制 + 气声;载波/颤音频率与响度随转速连续变化,停转自然衰减消失 |
| 🎮 四种模式 | 禅意(无限时)+ 连鸣挑战(连续鸣叫时长)+ 极速挑战(30 秒冲最高转速)+ 冲分挑战(60 秒连击冲分),各有实时计分与最高纪录 |
| 📷 摄像头搓手 | 轻量帧差 + 运动振荡检测,零模型依赖,灵敏度滑块可调;画面仅本地处理 |
| 🔊 环境音 + 事件音 | 夏日风 + 远处虫鸣底噪,可单独静音;起唱/连击/结束音效 |
| 📱 多端兼容 | 手机 / 平板 / 桌面响应式,触控 + 鼠标 + 键盘 + 摄像头 |
| ♿ 无障碍 | 键盘可操作、:focus-visible、prefers-reduced-motion 减弱动效 |
| ⚡ 性能自适应 | 按设备自动分高/中/低三档(粒子、竹林、阴影、DPR),页面隐藏自动暂停 |
双击 index.html,即可在任意现代浏览器(Chrome / Edge / Firefox / Safari)中打开。无需安装、无需联网、无需构建——Three.js 已内置在 vendor/ 目录。
摄像头功能建议通过本地服务器方式(方式二)使用,见下方说明。
# Python 3
python -m http.server 8090 --directory . --bind 0.0.0.0
# 或 Node.js
npx serve .访问:
- 本机:
http://127.0.0.1:8090/index.html - 局域网(同一 Wi-Fi 的手机/平板):
http://<你的局域网IP>:8090/index.html
💡 摄像头需要「安全上下文」:
http://localhost/http://127.0.0.1或https://均可。直接双击file://时,多数现代浏览器同样视为安全上下文,摄像头通常可用。
在画面任意位置快速来回搓动(横向往复或画圈皆可),竹蝉就会转起来:
- 触屏 / 鼠标:按住拖动,搓得越快转得越快
- 键盘:按住
A/D或←/→持续搓动 - 摄像头:点右上角「摄像头」,对着镜头快速搓手(拇指搓食指等往复动作),可拖灵敏度滑块调节
- 转速表(RPM)实时显示;速度条上的竖线是起唱阈值
- 转速超过阈值 → 蝉翼振动、唱出蝉鸣,音高音量随转速升高
- 松手后摩擦自然减速,低于阈值即"蝉鸣中断"
- 点右上角「🔄 视角」进入观察模式:拖动旋转视角(360° 环绕、上下俯仰),滚轮(桌面)/ 双指(触屏)缩放
- 再点一次回到搓动模式,两种手势互不干扰
- 禅意:无限时,随心搓听,记录蝉鸣累计时长与巅峰转速
- 连鸣挑战:让蝉连续鸣叫尽可能久;一停唱(0.8 秒宽限)即结算,记录「最长连续鸣叫」
- 极速挑战:30 秒内冲到最高转速,记录「巅峰转速」与「平均转速」
- 冲分挑战:60 秒冲分。得分 = 速度加成 × 连击倍率;连续蝉鸣每 3 秒升一档倍率(×1→×5),停唱即清零
三种挑战各有实时计分与最高纪录(localStorage 持久化)。
单文件、零构建、无框架。index.html 内按职责分段,运行时各模块通过共享状态协同:
输入(Pointer/键盘/摄像头)──┐
├─→ 物理(ω)─┬─→ 3D 玩具动画 + 特效
│ ├─→ 音频合成(蝉鸣/环境)
│ ├─→ 游戏计分/模式
│ └─→ UI(RPM/得分/提示)
└─────────────────→ localStorage(最高纪录)
requestAnimationFrame(tick)
├─ 物理:ω += (输入角加速度 − ω·摩擦系数) · dt,上限 45 rad/s
├─ 游戏:模式计时 / 计分 / 连击 / 连鸣宽限结算
├─ 音频:载波频率、翼振颤音频率、响度随 ω 连续更新
├─ UI:RPM、得分、倒计时、提示文案更新
├─ 玩具:旋转、翼振、残影、模糊盘、地面拖尾、进动晃动
├─ 相机:观察旋转 / 视差 / 天空跟随 / 渲染
└─ 渲染:renderer.render(scene, camera)
触屏/鼠标拖拽 ── 指针速度 × 0.05 ──┐
键盘 A/D / ←→ ── 固定 22 rad/s² ───┤→ 角加速度 ω̇ ─→ 积分得 ω(上限 45)
摄像头搓手 ──── 搓动强度 × 80 ─────┘ └─ 摩擦 0.55·ω(自然衰减)
场景:天空渐变球 + 竹林 + 地面 + 光尘粒子 + 太阳光晕
玩具:竹竿 + 竹节凸起 + 竹雕蝉(身/眼/颈环/双翼)
光照:太阳方向光(投射阴影)+ 半球光 + 轮廓光 + 程序化环境反射
特效:翼残影(偏移∝ω)+ 翼扫模糊盘 + 地面螺旋拖尾(随ω旋转)
阴影:PCFSoftShadowMap,玩具各部件 castShadow,地面 receiveShadow
锯齿波载波(3.3k + ω·50 Hz) → 带通滤波 → 响度(smoothstep·0.55) → 主增益
正弦 LFO(72 + ω·1.9 Hz) → 增益 → 调制载波幅度(蝉鸣颤音)
白噪声 → 低通 → 气声增益 → 叠加
环境:白噪声低通(风) + 带通噪声开关门(虫鸣)
全程零音频文件,全部振荡器 + 噪声实时合成
getUserMedia → 视频帧 → 降采样 96×72 灰度
→ 帧差运动能量(噪声门限 0.005,×16 增益×灵敏度)
→ 一维块匹配估计位移 → 方向反转频率
→ 综合搓动强度(快起慢放平滑)→ 注入物理
ω > 阈值 ─→ 蝉鸣中 ─→ 按模式计分(连鸣时长/巅峰转速/连击得分)
└─ 回合结束 ─→ 比较最高纪录 ─→ localStorage 持久化
localStorage:zhuchan.best(得分)/ zhuchan.bestStreak(连鸣)/ zhuchan.bestRPM(转速)/ zhuchan.muted(静音)
- HTML5 + CSS3 + 原生 JavaScript(ES2020)——无框架、无构建
- Three.js r160(UMD,本地内置
vendor/three.min.js,缺失时自动回退 CDN) - WebGL:3D 场景、程序化纹理、阴影、粒子
- Web Audio API:蝉鸣、环境音、事件音全部实时合成,零音频文件
- getUserMedia + Canvas 帧差:摄像头动作检测,零模型依赖
- localStorage:最高纪录、静音偏好持久化(隐私模式下降级为内存)
| 模块 | 实现要点 |
|---|---|
| 物理模型 | ω += 输入角加速度·dt,ω -= ω·摩擦系数·dt,上限 45 rad/s;拖拽速度→角加速度系数 0.05 |
| 蝉鸣合成 | 零音频文件;锯齿波载波(3.3k + ω·50 Hz) → 带通 → 翼振频率(72 + ω·1.9 Hz) AM 调制 + 低通气声;载波/颤音频率、颤音深度与响度(smoothstep) 均随 ω 连续变化、自然衰减 |
| 程序化纹理 | Canvas 2D 生成:竹节纹理(渐变+节环+纤维)、蝉翼(外形+翼脉 alpha)、竹叶、地面斑驳、光晕 |
| 玩具模型 | 竹竿圆柱 + LatheGeometry 蝉身 + Torus 蝉红颈环 + 两侧 PlaneGeometry 蝉翼(绕体侧枢轴上下扇动) |
| 竹质质感 | MeshStandardMaterial + 颜色/凹凸双贴图 + PMREM 程序化环境反射;真实几何竹节凸起(Torus 节点)+ 主光/轮廓光 |
| 旋转观察 | 球坐标相机(方位角 360° + 俯仰角 0.05→π-0.05 无死角),拖动旋转、滚轮与双指缩放,经「视角」模式与搓动手势解耦 |
| 旋转残影/模糊 | 三对半透明"影翼"随转速拖后(偏移 ∝ ω)+ 环形模糊盘(透明度 smoothstep ∝ ω),静止时完全隐藏 |
| 地面拖尾 | 玩具正下方地面圆形螺旋光纹,随 ω 旋转(转速越快转越快)并淡入,把旋转速度呈现在地面 |
| 太阳实影 | renderer.shadowMap + PCFSoftShadowMap;太阳方向光 castShadow、地面 receiveShadow、玩具各部件 castShadow |
| 摄像头检测 | 降采样 96×72 灰度 → 帧差能量(×16 增益,噪声门限) + 一维块匹配位移 → 方向反转频率放大搓动强度,快起慢放平滑,灵敏度可调 |
| 状态机 | menu → playing → result;禅意/连鸣/极速/冲分四模式;visibilitychange 暂停循环/音频/摄像头 |
| 质量档 | 按 deviceMemory / 核数 / 屏宽自动分三档,控制粒子数、竹林数量、阴影与 DPR |
project2/
├── index.html # 唯一交付文件:设计系统 + HUD + 全部逻辑(约 1900 行,按注释区块分段)
├── vendor/
│ └── three.min.js # Three.js r160 UMD(本地内置,离线可用)
├── README.md # 本文档
├── LICENSE # MIT
└── .gitignore # 忽略 .pwtools/ .shots/ .npm-cache/ 等本地开发辅助目录
index.html内部按注释区块清晰分段:设计系统/音频引擎/程序化纹理/3D 场景/玩具模型/物理与输入/游戏状态机/摄像头/UI/主循环,便于直接定位修改。
| 能力 | 支持情况 |
|---|---|
| 浏览器 | Chrome / Edge / Firefox / Safari(近 5 年版本,需支持 WebGL) |
| 设备 | 320px 手机 → 1920px 桌面;触控 + 鼠标 + 键盘 + 摄像头 |
| 摄像头 | 需安全上下文 + 用户主动授权;无摄像头/拒绝授权有友好降级 |
| 减弱动效 | ✅ prefers-reduced-motion: reduce(光尘静止、相机视差瞬时、过渡瞬时) |
| 键盘操作 | A/D、←/→ 搓动;所有按钮可 Tab / Enter 操作 |
| 性能 | DPR 上限自适应;页面隐藏自动暂停动画、挂起音频、关闭摄像头 |
| 深色/浅色 | 画面为固定夏景主题,UI 面板蝉翼白,不随系统深色模式切换 |
摄像头功能默认关闭,仅在用户点击「摄像头」按钮并授权后开启。视频画面仅在本地处理(用于计算"搓动"动作),不会上传、不会保存、不会录制。关闭按钮或切换后台时立即停止摄像头。
开发过程中使用 playwright-core + 系统 Chrome 进行了自动化浏览器验证(file:// 与本地 HTTP 两种方式)。
- 加载/渲染:three.js 加载、WebGL canvas 渲染、调试钩子暴露、开始屏可见
- 核心玩法:进入 playing、冲分模式生效、拖拽转速上升、达到蝉鸣、得分累计、键盘搓动
- 音频:停转响度=0、载波/响度随转速单调升高、实际载波与 ω 精确对应、停转响度自然衰减
- 旋转可视化:静止无残影/模糊/拖尾、高速残影/模糊/地面拖尾出现
- 旋转观察:开启/关闭、俯仰上下限(近顶部↔近底部)、拖动旋转、滚轮缩放
- 摄像头:预览出现、按钮点亮、可关闭、检测到往复搓动(合成帧)、静止不误触发、搓手强度越大转速越高
- 模式:禅意切换、连鸣 HUD/计时/中断结算、极速 HUD/倒计时递减、成绩面板
- 移动端:竖屏触摸搓动、横竖屏无水平溢出
- 稳定性:全程零控制台错误、零页面异常
file://双击离线:✅prefers-reduced-motion:✅- WebGL 缺失降级:✅
- 像素级采样:非黑屏、竹林绿主导、暖阳光晕、玩具居中
复测方式:
cd .pwtools && npm i playwright-core && node test.js(另node filetest.js验证离线/减弱动效,本机需已装系统 Chrome)。
项目按「需求 → 实现 → 验证」迭代推进,每个版本都通过自动化测试回归:
| 版本 | 需求/反馈 | 实现 | 验证 |
|---|---|---|---|
| v1.0 | 3D 竹知了仿真网页游戏,响应式多端 | Three.js 场景 + 玩具模型 + 旋转物理 + 蝉鸣合成 + 双模式 + 摄像头动作检测 | 20 项 |
| v1.1 | 竹质立体质感 + 旋转观察 | Phong 材质 + 凹凸贴图 + 竹节高光 + 球坐标相机旋转观察 | 24 项 |
| v1.2 | 竹子不够立体、旋转角度过小 | 真实几何竹节凸起 + PMREM 环境反射 + MeshStandard;俯仰 0.05→π-0.05 无死角 | 28 项 |
| v1.3 | 必须实时合成、零音频、音调响度随转速连续变化 | 载波/翼振/响度随 ω 连续 + 颤音深度加深 + 自然衰减 | 34 项 |
| v1.4 | 高速旋转可视化(运动模糊/残影) | 三对影翼残影 + 翼扫环形模糊盘 | 37 项 |
| v1.5 | 地面拖尾呈现转速 | 地面螺旋光纹随 ω 旋转淡入 | 38 项 |
| v1.6 | 太阳照着竹子形成残影 | 太阳方向光投射真实阴影(PCFSoft) | 39 项 |
| v1.7 | 摄像头识别不到搓手 | 检测算法重写:分辨率/能量增益/往复阈值/平滑 | 41 项 |
| v1.8 | 摄像头不按强度增大转速 | ×16 增益 + 噪声门限 + 灵敏度滑块 + CAM_RATE 80 | 42 项 |
| v1.9 | 加可玩挑战(连续鸣叫秒数/冲转速)+ 实时计分 + 最好成绩 | 连鸣/极速/冲分三挑战,各自实时计分与最高纪录 | 47 项 |
编辑 index.html 中 <script> 顶部的 CONFIG 对象即可全局调参:
var CONFIG = {
OMEGA_MAX: 45, // 转速上限 rad/s
FRICTION: 0.55, // 摩擦衰减系数(越大停得越快)
SING_THRESHOLD: 6, // 起唱阈值 rad/s
FULL_SING: 18, // 满唱阈值
DRAG_K: 0.05, // 拖拽速度 → 角加速度
KEY_RATE: 22, // 键盘搓动角加速度
CAM_RATE: 80, // 摄像头满强度角加速度
RUSH_SEC: 30, // 极速挑战时长
SCORE_SEC: 60 // 冲分挑战时长
};- 在
MODE_ORDER/MODE_NAME中加入新模式的 key 与名称 - 在
Game.update中按模式添加计分/计时逻辑,在Game.endRound中添加结算分支 - 在
UI.refreshMode/UI.update中添加对应 HUD 区块的显隐与数值更新 - 在开始屏
.mode-grid中添加一个<button data-mode="...">
- 音色:改
Sound.update中的carrier = 3300 + omega * 50、wingRate = 72 + omega * 1.9 - 残影/拖尾:改
tick中ghostWings的对数/偏移/透明度、wingFan与groundTrailMat的 smoothstep 区间 - 配色:改
<style>顶部:root的 CSS 变量(竹青/竹黄/蝉翼白/墨/日金/蝉红)
MIT License © 2026 — 自由使用、修改与分享。