首页
/ 玩转 Canvas API:用代码在太空游戏中绘制英雄与怪兽阵型

玩转 Canvas API:用代码在太空游戏中绘制英雄与怪兽阵型

2026-09-07 13:32:08作者:韦蓉瑛

本篇围绕 Web-Dev-For-Beginners 开源课程第 6 单元"太空游戏"第二节的课后实践任务(对应 任务原文)展开。任务要求学习者在掌握 getContext('2d')fillRectdrawImage 等 Canvas API 基础后,独立选择一个感兴趣的方向完成一个可视化作品。通过本文,读者将把课程正文的绘图知识转化为一次可提交的创意编码实战:理解 Canvas 坐标系与三步绘图流程、掌握基于 Promise 的异步贴图加载、并用嵌套循环拼出整齐的敌人编队,最终参照本仓库的 参考实现 自检与迭代。

任务定位:从"读懂 Canvas"到"独立创作"

这一节的实践任务位于课程第 6 单元"太空游戏"(Space Game)的第二节。在 课程正文 中,学习者已经跟随讲解把一块空白的 <canvas> 变成了英雄飞船与敌人阵列并存的游戏世界;而任务要求再进一步——不再照抄示例,而是选定 Canvas API 的一个侧面,围绕它做出一个"属于你自己的"视觉项目

任务的直接用途是在 太空游戏整个单元 中充当"动手验证"环节:游戏后续要加入移动、碰撞检测、计分等逻辑(见 6-space-game/3-moving-elements-around6-space-game/6-end-condition),而这一切都建立在"会画、画得准、画得有条理"的基础上。

学习目标

任务开头明确了两条验收标准:

  • 证明你理解了 Canvas API 的基本原理;
  • 用 JavaScript 与 HTML5 canvas 进行创造性问题求解,把学到的绘图能力转化为一件独特作品。

动手前先补课:Canvas 绘图的三块基石

任务假设你已完成课程正文,以下知识点在完成作品时会被反复用到,这里一并梳理(详情见 Canvas 基础章节)。

1. Canvas 元素与坐标系

<canvas> 是 HTML5 提供的可编程绘图表面,一个空 canvas 只是一个透明的空白矩形,真正的能力来自 JavaScript。声明方式通常在页面 body 内:

<canvas id="canvas" width="1024" height="768"></canvas>

本课程的起始文件 your-work/index.html 就采用了 1024×768 的画布尺寸,这正对应任务/课程中"渲染一个 1024×768 的黑色游戏屏幕"的目标。

坐标系与数学课有个关键差别:原点 (0,0) 在左上角,x 向右增大、y 向下增大。这张来自课程正文的网格示意图直观说明了这一约定:

Canvas 坐标系示意:原点位于左上角,x 向右、y 向下

2. 三步绘图流程

所有 Canvas 绘图都遵循同一流程:

// 第 1 步:拿到 canvas 元素引用
const canvas = document.getElementById("canvas");
// 第 2 步:获取 2D 渲染上下文(绘图工具箱)
const ctx = canvas.getContext("2d");
// 第 3 步:设置填充色并绘制矩形(x, y, 宽, 高)
ctx.fillStyle = "#181733";
ctx.fillRect(0, 0, canvas.width, canvas.height);

fillStyle 决定填充颜色,fillRect(x, y, w, h) 决定填充区域。若想画圆,需要组合 beginPath()arc()fill()。这些正是任务技术清单中的核心方法。

3. 异步加载贴图

游戏中使用的精灵(sprite)来自图片文件,加载是异步的,不能用同步代码"等它加载完"。课程给出了一种规范做法:把图片加载封装成返回 Promise 的函数,配合 async/await 使用:

async function loadTexture(path) {
  return new Promise((resolve) => {
    const img = new Image();
    img.src = path;
    img.onload = () => resolve(img);
  });
}

这段代码与起始文件 your-work/app.js 中已提供的 loadTexture 完全一致——学习者只需在上面继续补全 TODO。

选题方向:三类可深入挖掘的创意入口

任务给出了三组灵感,每个方向都对应一个可验证的 Canvas API 侧面:

几何图案类

  • 生成"闪烁星空星系":用 Math.random() 随机定位星星,配合透明度/亮度周期性变化实现"闪烁"——可综合练习 fillRectfillStyleglobalAlpharequestAnimationFrame 动画循环;
  • 用重复几何形状设计纹理:适合训练嵌套循环与坐标递推;
  • 旋转彩色万花筒效果:涉及 translate()rotate() 等变换 API 的深层运用。

交互元素类

  • 跟随鼠标的画笔工具:监听 mousemove 事件并把指针坐标映射到画布坐标,验证对坐标系的理解;
  • 点击变色形状:结合点击事件与命中检测;
  • 移动元素的简易动画循环:搭建后续游戏循环的雏形。

游戏相关图形类

  • 太空游戏滚动背景:与本单元主题直接衔接;
  • 爆炸/魔法粒子特效:锻炼对象数组与逐帧更新能力;
  • 多帧动画精灵:是 太空游戏后续课程 的核心前置技能。

实现建议:结合参考实现理解"如何把想法写出来"

任务建议"聚焦一个特性并深入挖掘",同时给出了通用技术要求:正确使用 getContext('2d')、代码含有效注释、全面测试、使用 const/let 与箭头函数等现代语法。

若你把"游戏相关图形"与课程成品场景作为目标,本仓库的 参考实现 solution/app.js 是一个很好的"对标物",其中包含几个值得模仿的实战手法:

(1)按功能拆分函数,而不是全堆在 onload 里。 参考实现将"加载贴图""绘制文本""绘制敌人"拆成独立函数,主入口只在 window.onload 中编排:

window.onload = async () => {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");
  const heroImg = await loadTexture('assets/player.png');
  const enemyImg = await loadTexture('assets/enemyShip.png');
  ctx.fillStyle = '#181733';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.drawImage(heroImg, canvas.width / 2 - 45, canvas.height - (canvas.height / 4), 90, 90);
  createEnemies(ctx, canvas, enemyImg);
};

(2)绘制英雄时"用算式代替魔法数字"。canvas.width / 2 - 45 让飞船水平居中,用 canvas.height - canvas.height / 4 让飞船落在屏幕下方——这正是课程正文讲授的"数学化定位"思路。

(3)用常量和嵌套循环排布敌人阵型。 课程正文建议先为 5×5 阵型建立常量:

const ENEMY_TOTAL = 5;
const ENEMY_SPACING = 98;
const FORMATION_WIDTH = ENEMY_TOTAL * ENEMY_SPACING;
const START_X = (canvas.width - FORMATION_WIDTH) / 2;
const STOP_X = START_X + FORMATION_WIDTH;

再用两层循环按坐标逐个绘制:

for (let x = START_X; x < STOP_X; x += ENEMY_SPACING) {
  for (let y = 0; y < 50 * 5; y += 50) {
    ctx.drawImage(enemyImg, x, y);
  }
}

外层循环控制列、内层循环控制行,间距由常量统一决定,阵型就能做到整齐且居中。起始文件 your-work/app.js 中预留了空壳函数 createEnemies(ctx, canvas, enemyImg) 等待你补全。参考实现选择了 7 列 × 5 行、单元尺寸 65px 的参数,说明阵型参数完全可以根据你的创意自由调整——这正是任务鼓励的"个性空间"。

课程正文给出的最终效果如下图所示:黑色背景上,上方是 5×5 的敌人方阵,下方居中位置是英雄飞船,为后续"太空侵略者"式玩法奠定视觉基础:

黑色游戏屏幕上的英雄飞船与 5×5 敌人阵列

作品如何自测与打磨:向任务要求逐条对齐

任务把作品质量拆成了三个维度,做完全可以在提交前逐条自检:

标准 自检问题
技术实现 Canvas 设置是否规范(getContext('2d'))?是否综合使用了多种 API?代码能否无报错运行?是否采用了 const/let、箭头函数等现代语法?
创意与设计 概念是否足够原创?是否围绕你选定的一两个 API 特性做了"纵深探索"而非浅尝辄止?
代码质量 是否函数化拆分、有清晰注释、算法高效?

起步脚手架请直接使用 your-work 目录(内含 assets/ 中的 玩家飞船敌机贴图,即英雄与敌人精灵素材),在其所在的 package.json(脚本为 npx http-server -c-1 -p 5000,见 solution/package.json)支持下本地运行:

cd your-work
npm start

浏览器访问 http://localhost:5000 即可实时预览——注意首次打开应是空白页,这是预期现象,补全 app.js 中的 TODO 后刷新浏览器即可看到效果。

提交要求与作品扩展思路

任务规定,最终作品可以交付为单个内嵌 CSS/JavaScript 的 HTML 文件,或文件夹中的若干独立文件;同时应在代码注释中简短说明你的创意取舍及所探索的 Canvas API 特性。

提交前建议围绕以下问题复盘(对应任务原文的"反思问题"):

  1. 你选择了哪个 Canvas API 特性,为什么?
  2. 构建过程中遇到的最大挑战是什么,如何解决的?
  3. 这个作品如何能扩展成更大的应用或游戏?(可参考 太空游戏单元 后续的移动、碰撞检测、计分与结束条件章节,观察一个小绘图作品如何长成完整游戏。)
  4. 下次还想探索哪些 Canvas API 特性?

附:进阶挑战与学习路径

作为课程正文的延伸,完成基础作品后还可以挑战两个方向(见 课程正文末尾):

  • 增强版画布(GitHub Copilot Agent 挑战):在 enhanced-canvas.html 中实现随机位置闪烁的星空背景、随生命值变化颜色的英雄血条(绿 → 黄 → 红),以及以不同速度向下移动的敌机——把静态绘制升级为动态系统;
  • WebGL 挑战:走出 2D,尝试用 WebGL API 在页面中绘制一个 3D 物体。

提示:此任务的保加利亚语版本由 AI 翻译服务(Co-op Translator)生成,英文原版见 6-space-game/2-drawing-to-canvas/assignment.md,涉及关键信息时以英文原版为准。

💡 任务官方的建议:从简单开始,逐步叠加复杂度——一个执行良好的简单项目,远胜一个雄心勃勃却跑不起来的项目。这个建议也适用于任何 Canvas 图形学习过程:先把矩形画准,再谈星系与粒子。

登录后查看全文
热门项目推荐
相关项目推荐