Each topic below is shown in Chinese on the left and English on the right.
页面是怎么组织的
这个游戏是一个单文件 HTML 程序。HTML 负责页面结构,CSS 负责外观,JavaScript 负责游戏状态、 动画、存储和音频。
How the page is organized
The game is a single HTML file with three jobs: the markup creates the pages, the CSS paints the interface, and the JavaScript controls state, animation, storage, and audio.
核心数据
pages保存开始、设置和游戏页面。foods保存当前屏幕上的所有下落物体。hearts、goodCount和badCount保存分数状态。music负责启动和停止 Web Audio 音乐循环。
Core building blocks
pagesstores the start, setup, and game sections.foodsstores every falling object currently on screen.hearts,goodCount, andbadCountstore score state.musicstarts and stops the Web Audio loop.
HTML 和 JavaScript 各自做什么?
HTML 像舞台搭建图,负责把按钮、分数、角色和页面区域放出来。JavaScript 像导演和控制台,负责决定 什么时候切页、什么时候生成食物、什么时候播放音乐、什么时候结束游戏。
What do HTML and JavaScript do?
HTML is like the stage plan: it places the buttons, score, character, and page areas. JavaScript is like the director and control panel: it decides when to switch pages, when to spawn food, when to play music, and when the round ends.
This section explains the frame loop and collision logic in both languages.
游戏循环一句话理解
每一帧,游戏都会测量时间、移动食物、检查碰撞、更新界面,然后请求浏览器继续下一帧。
为什么不用普通定时器?
setInterval 虽然简单,但浏览器忙的时候可能会延迟。requestAnimationFrame
更适合动画,因为它和屏幕刷新同步。
Game loop in one sentence
The game repeats a small set of steps every frame: measure time, move foods, check collisions, update the UI, then ask the browser for the next frame.
Why not use a normal timer?
A fixed timer like setInterval is easier to write, but the browser may delay it when
the tab is busy. requestAnimationFrame is better for smooth animation because it is
synchronized with the screen refresh rate.
食物到达玩家时会发生什么?
每个食物对象都有 x 和 y 坐标。下落物进入接取区域后,如果玩家在水平距离
上足够接近,就会被判定为接住。
适合初学者的重点
这里不需要复杂物理引擎。因为玩家只左右移动,食物只向下移动,所以只要做简单的距离检测就够了。
What happens when food reaches the player?
Each food item has an x and y position. If a falling item enters the catch
zone and the player is close enough horizontally, the item is considered caught.
Important beginner idea
Collision detection does not need advanced physics here. A simple distance check works because the player only moves left and right, and the falling items move straight down.
The audio chapter explains design, controls, and rhythm sync in both languages.
为什么这里使用 Web Audio?
游戏没有携带音频文件,而是在浏览器里直接生成振荡器。这样可以让项目更轻量,也能展示“代码生成音乐” 的工作方式。
Why use Web Audio here?
The game does not ship with an audio file. Instead, it creates oscillators in the browser. That keeps the project lightweight and shows how music can be synthesized with code.
音乐控制器做了什么
- 只在需要时创建
AudioContext。 - 通过低通滤波器让音色更柔和。
- 安排循环旋律和低音线。
- 在关卡结束或关闭音乐时安全停止。
What the music controller does
- Creates an
AudioContextonly when needed. - Uses a low-pass filter so the tones sound softer.
- Schedules a repeating melody and bass line.
- Stops cleanly when the round ends or the player turns music off.
控制方式
音乐有两个独立控制:一个开关控制“播不播放”,一个滑条控制“多大声”。这是一种很好的程序设计方式, 因为“是否播放”和“音量多大”是两种不同的数据。
musicEnabled保存开关状态。musicVolume保存 0 到 1 的音量值。- 滑条直接更新 master gain 节点。
How the controls work
The music has two independent controls: one switch turns sound on or off, and one slider changes loudness. That is a useful pattern in programming because it separates the idea of whether audio plays from how loud it plays.
musicEnabledstores the on/off state.musicVolumestores the number from 0 to 1.- The slider updates the master gain node directly.
互动示例
这个示例不会真正播放声音,只是演示音量和节奏同步的关系。
75%
760 ms per spawn
Interactive control demo
This demo does not play sound. It just shows the relationship between volume and tempo sync.
75%
760 ms per spawn
The code chapter keeps the same sample code, but explains it in both languages.
标注版 startGame()
// 在新一局开始前重置所有状态。
function startGame(difficulty){
currentDifficulty = difficulty;
hearts = MAX_HEARTS;
goodCount = 0;
badCount = 0;
foods.forEach(f => f.el && f.el.remove());
foods = [];
// 切换到游戏页面并把角色放到中间。
showPage('game');
updateStageRect();
charX = stageRect.width / 2;
applyCharTransform();
// 音乐只在玩家点击之后开始。
music.start();
gameActive = true;
rafId = requestAnimationFrame(gameLoop);
}
Annotated startGame()
// Reset the round before it begins.
function startGame(difficulty){
currentDifficulty = difficulty;
hearts = MAX_HEARTS;
goodCount = 0;
badCount = 0;
foods.forEach(f => f.el && f.el.remove());
foods = [];
// Show the game page and move the character to the center.
showPage('game');
updateStageRect();
charX = stageRect.width / 2;
applyCharTransform();
// Music starts only after the player's click.
music.start();
gameActive = true;
rafId = requestAnimationFrame(gameLoop);
}
标注版 spawnFood()
// 决定这次掉落的是好东西还是坏东西。
function spawnFood(){
const cfg = DIFF_CONFIG[currentDifficulty];
const isBad = Math.random() < cfg.badProb;
const emoji = isBad ? BAD_FOODS[...] : GOOD_FOODS[...];
const speed = cfg.minSpeed + Math.random() * (cfg.maxSpeed - cfg.minSpeed);
const x = 24 + Math.random() * (stageRect.width - 48);
// 创建真实的 DOM 节点,让浏览器负责动画。
const el = document.createElement('div');
el.className = 'food';
el.textContent = emoji;
stageEl.appendChild(el);
foods.push({ x, y:-40, speed, type:isBad ? 'bad' : 'good', el });
}
Annotated spawnFood()
// Decide whether the falling object is good or bad.
function spawnFood(){
const cfg = DIFF_CONFIG[currentDifficulty];
const isBad = Math.random() < cfg.badProb;
const emoji = isBad ? BAD_FOODS[...] : GOOD_FOODS[...];
const speed = cfg.minSpeed + Math.random() * (cfg.maxSpeed - cfg.minSpeed);
const x = 24 + Math.random() * (stageRect.width - 48);
// Create a real DOM node so the browser can animate it.
const el = document.createElement('div');
el.className = 'food';
el.textContent = emoji;
stageEl.appendChild(el);
foods.push({ x, y:-40, speed, type:isBad ? 'bad' : 'good', el });
}
What the detailed comments in the game file are teaching
The comments explain why each block exists, not just what it does. For example, the music section explains the browser autoplay rule, the collision section explains why a simple distance check works, and the state section explains the three character moods.
The practice section lets students click through the logic in both languages.
互动走读
点击下面的按钮,按浏览器执行的顺序一步一步查看逻辑。
玩家点击开始按钮,页面从菜单切换到游戏界面。
Interactive walkthrough
Click the buttons below to step through the logic the same way the browser executes it.
The player clicks the start button. The page switches from the menu to the game board.
小测验
哪个对象保存所有正在下落的食物?
Mini quiz
Which object stores all falling foods?
练习建议
找到 startGame()、spawnFood()、updateFoods() 和
createMusicController(),分别解释它们负责什么:初始化、生成、移动还是声音。
Try this in the code
Search for the functions startGame(), spawnFood(),
updateFoods(), and createMusicController(). Then explain to yourself
what each one owns: setup, spawning, movement, or sound.