HTML 网页小游戏代码怎么写:一个能直接跑的最小例子

编程狮(w3cschool.cn) 2026-10-29 07:05:31 浏览数 (28)
反馈

写网页小游戏的最简路径只有三块:一份 HTML 骨架、一段 JavaScript 逻辑、一个不断重画的游戏循环。很多人一上来就堆框架,反而把最该理解的结构弄丢了。本文用一个能直接在浏览器跑的「接住下落方块」最小例子,把输入、状态、渲染三步拆开讲,每段都附可复制代码。你不需要任何构建工具,把代码存成 .html 双击打开就能玩。适用范围:现代桌面浏览器;移动端触屏需另加触摸事件;涉及具体 API 的行为以对应版本的规范为准(下文代码按 Web 标准规范编写,未在本机逐一执行,均属编辑推断)。先给结论:网页游戏的本质是「每帧清空画布、按当前状态重画、再读输入更新状态」的无限循环。

网页小游戏怎么写最小例子?

关键结论先给出:游戏循环是整篇的骨架。只要把这个循环写对,加多少规则都是在这三层上做加法,不会越写越乱。新手最容易犯的错,就是还没跑通循环就去接物理引擎、加音效,结果一个问题套着另一个问题,最后连「方块为什么不动」都定位不到。

一、先把最小可运行骨架看清(适用与边界)

先放一个不含任何玩法的空壳,确认它能在浏览器里画出一块画布。这是后续一切的地基,地基稳了再加逻辑才不会被表象迷惑。不要跳过这一步直接写玩法,否则出错时你分不清是骨架问题还是逻辑问题。

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>最小游戏</title></head>
<body>
<canvas id="game" width="320" height="480"></canvas>
<script src="game.js"></script>
</body>
</html>

单独的JavaScript 教程能帮你补齐语法;本文聚焦游戏循环本身。边界要记牢:canvas 的坐标原点在左上角,向右为 x 正、向下为 y 正,这和数学里的直角坐标系相反,很多新手的位置错乱都源于此。画布尺寸是固定的逻辑分辨率,缩放显示由 CSS 控制,不要为了「看起来大」去改 width/height 属性,那只会影响碰撞判定的坐标系。

把逻辑分辨率和显示尺寸区分开,是后面所有坐标计算不出错的前提。逻辑分辨率定在 320×480,意味着你的所有 x 都在 0 到 320 之间、y 在 0 到 480 之间;显示时无论放大多少倍,内部坐标都不变。

二、游戏状态与输入:先把数据结构和键盘事件写上(安装、配置、运行)

游戏状态就是一个普通对象,记录方块的位置与速度;输入则监听键盘,把「左移 / 右移」翻译成状态变化。这一步不画任何东西,只管数据与事件,职责单一才好调试。

const cv = document.getElementById("game");
const ctx = cv.getContext("2d");
const state = { x: 140, y: 0, vy: 2, w: 40, h: 40 };

addEventListener("keydown", (e) => {
  if (e.key === "ArrowLeft") state.x -= 12;
  if (e.key === "ArrowRight") state.x += 12;
});

把输入和状态分开写,是避免逻辑纠缠的第一步。键盘事件只在按键瞬间触发,所以移动是「离散跳格」而非「持续滑行」,想要顺滑移动要改成在循环里读按键状态,那是后话。JavaScript 速查手册里有事件对象的完整字段可查。

注意 state.x 没有任何边界限制,现在按左键能移出画布。这是故意留的缺口,下一节在循环里补上约束,比现在就写一堆 if 更清晰。先让它能动起来,再谈约束,调试思路会顺很多。

三、游戏循环:清屏、画、更新三步走(介绍与对比)

游戏循环是整篇的核心。每帧先清空画布,再按状态画方块,最后更新位置。用 requestAnimationFrame 让浏览器按刷新率调用,比 setInterval 更省电也更顺滑。

function loop() {
  ctx.clearRect(0, 0, cv.width, cv.height);          // 1. 清屏
  ctx.fillRect(state.x, state.y, state.w, state.h);  // 2. 画方块
  state.y += state.vy;                               // 3. 更新
  if (state.y > cv.height) state.y = 0;             // 落到底就回到顶部
  state.x = Math.max(0, Math.min(cv.width - state.w, state.x)); // 边界约束
  requestAnimationFrame(loop);
}
loop();

对比两种写法:用 setInterval 定时调用,逻辑简单但会和浏览器刷新不同步,长帧时会卡顿;用 requestAnimationFrame 则天然对齐显示节奏。新手阶段直接用后者即可,不用纠结 timing。想深入可看Deep Learn Javascript 教程里的动画章节。

这个循环已经是一个完整游戏:方块持续下落、按键左右移动、出界自动回弹。所谓「玩法」只是往第三步里多加几条规则,比如检测方块是否接住了底部的横条。先把这三步刻进脑子里,后面加任何功能都有据可依。

顺带一提,clearRect 必须每帧调用,否则上一帧的方块会残留在画布上,形成拖影。很多「画面越来越糊」的 bug 都因为漏了清屏这一步。

游戏循环清屏画更新三步

四、加一点玩法:接住下落方块(验证与输出)

在状态里加一根底部横条,并在循环里判断方块落到底时是否横向重叠。重叠算接住,分数加一;否则游戏重置。这就是从「会动」到「能玩」的最小跨越。

state.paddle = { x: 120, y: 440, w: 80, h: 12 };
// 在 loop 的更新段之后追加:
const hit = state.y + state.h >= state.paddle.y &&
            state.x + state.w > state.paddle.x &&
            state.x < state.paddle.x + state.paddle.w;
if (state.y + state.h >= state.paddle.y && !hit) state.y = 0; // 没接住,回到顶部

预期结果(未在本机执行):方块落到横条高度时,若横向有重叠则继续、无重叠则回到顶部重新开始。判定用的就是最朴素的矩形相交——两个轴方向都有重叠才算碰上。把判定抽成单独函数后,将来换圆形、加多块都只是改这一处。

验证时若发现「明明接住了却没反应」,十有八九是坐标方向搞反:记得 y 向下为正,所以「落到底」是比较 y + h 与横条的 y,而不是拿 y 直接比。这类方向错误在 2D 游戏里极其常见,定位时先把坐标打印出来对照最稳妥。

五、常见失败与修复(失败、边界、排错)

坑一,画布空白。多半是 getContext 拿到 null,原因常是脚本在 canvas 元素之前执行。把 <script> 放到 body 末尾,或在 DOMContentLoaded 后初始化即可。

坑二,方块不动。循环里忘了再次调用 requestAnimationFrame(loop),函数只跑了一帧就结束。修复方式是确保循环末尾有那一行自调用,可以用日志确认它确实在反复进入。

坑三,按键无反应。监听器绑错了对象,或焦点不在页面。修复方式是绑定到 window 或 document,并在页面内点击一下再试。需要在线快速验证语法时,JavaScript 在线运行可以贴片段直接跑。

坑四,位置越来越歪。把「逻辑分辨率」和「显示尺寸」混为一谈,CSS 放大后坐标对不上。修复方式是固定 canvas 的 width/height 属性,只用 CSS 控制外观,逻辑坐标始终以属性值为准。

四类常见问题与修复

总结

HTML 网页小游戏的最小写法就是「骨架 + 状态 + 循环」三层:HTML 负责一块画布,JavaScript 用对象记录状态并用键盘事件修改它,游戏循环每帧清屏、重画、更新。把输入和状态分开、把判定抽成函数,后续加玩法都是在循环里做加法。遇到空白或不动,优先查脚本执行时机与循环自调用;遇到错位,先分清逻辑分辨率和显示尺寸。把这一份例子跑通,你就有了可以无限扩展的游戏地基,加音效、计分、多关卡都不会再让你无从下手。

常见问题

Q:为什么我的画布什么都没有显示?

最常见是脚本在 canvas 元素之前执行,导致取不到上下文。把脚本放到 body 末尾,或等页面加载完成后再初始化即可解决。

Q:游戏循环用 setInterval 还是 requestAnimationFrame?

优先用 requestAnimationFrame,它会按浏览器刷新节奏调用、更省电也更顺滑;setInterval 容易和显示不同步,长帧时会卡顿。

Q:按键移动为什么是跳格而不是平滑滑动?

因为键盘事件只在按下瞬间触发一次。想要平滑,要在循环里持续读取按键的按下状态,根据状态每帧位移,而不是依赖单次事件。

延伸学习

0 人点赞