Web-Dev-For-Beginners 空间游戏第 4 课作业实战:从矩形相交到自定义碰撞小游戏
在 Web-Dev-For-Beginners 的"构建太空游戏"系列第 4 课中,你已经在太空船游戏里实现了激光武器与基于矩形相交的碰撞检测系统。本篇文章以该课的课后作业(Explore collisions)为核心骨架,完整讲解如何把课上掌握的矩形相交算法、对象生命周期管理与事件驱动架构迁移到一个全新的自定义小游戏中,最终产出一个拥有完整游戏规则、视觉反馈与可扩展玩法的可运行作品。
读完本文,你将掌握矩形碰撞检测从算法到工程落地的完整路径:如何为游戏对象抽象矩形边界、用分离测试判定相交、安全地销毁对象、用事件系统解耦碰撞响应,并据此设计与实现陨石躲避、碰碰车、太空垃圾收集等自定义玩法。
作业全景:把碰撞检测"玩"出新花样
该作业并非要求你重写太空游戏,而是验证一个更本质的能力:能否把第 4 课(Building a Space Game Part 4: Adding A Laser and Detect Collisions)中"把一切游戏对象当作矩形、检测相交并驱动游戏事件"的思想迁移到一个你自创的小型交互游戏中。作业原文要求作品必须包含:
- 多个可移动对象,可通过键盘或鼠标输入控制;
- 基于矩形相交原理的碰撞检测系统(即课上
intersectRect算法); - 碰撞发生时的视觉反馈(对象销毁、颜色变化、特效);
- 让碰撞"有意义"的游戏规则——碰撞不是炫技,而是胜负、得分与成长的核心机制。
仓库为学习路径提供了天然的分层素材:your-work 与 solution 两个子目录都位于课程目录内,your-work/app.js 是只包含"英雄移动 + 敌人下落 + 渲染"的前一课基线代码,而 solution/app.js 是加入了激光、碰撞与冷却系统的完整参考答案,非常适合作为你自创游戏时对照参考的"标准件库"。
前置理论:成文前必须吃透的四块基石
作业的"技术实现要求"直指第 4 课的核心概念,动手编码前应确保以下四点已被内化(对应课程 README 的 Pedagogical Check-in 章节)。
1. 矩形边界:把每个对象变成四个数
矩形法认为,无论精灵长什么样,碰撞判定都只关心它的外接矩形。GameObject.rectFromGameObject() 把位置与尺寸换算成四条边界:
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width,
};
}
- 顶边 = 对象起始的纵向坐标
y; - 左边 = 起始的横向坐标
x; - 底边 =
y + height(注意 Canvas 坐标系 y 轴向下); - 右边 =
x + width。
该方法的工程意义在于提供统一的边界接口:无论敌人、英雄还是自创游戏里的收集品,全部暴露相同的 top/left/bottom/right 结构,相交函数无需关心对象具体类型。
2. 相交判定:分离测试而非重叠计算
function intersectRect(r1, r2) {
return !(
r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top
);
}
算法核心理念是"只要有一个方向分离就不相交",反向取非即得相交。相比直接求重叠区域,它只需 4 次比较、无除法无开方,在每帧对 N 个对象做 O(N²) 级两两测试时依旧高效——这正是作业要求"proper use of rectangle-based collision detection"所指的正确姿势。
3. 对象生命周期:标记死亡,帧间清理
游戏循环中禁止在遍历时直接删除对象(会导致跳过元素甚至崩溃)。正确模式是课上强调的"mark for deletion":
enemy.dead = true; // 标记,不立即删除
gameObjects = gameObjects.filter(go => !go.dead); // 帧末统一过滤
从源码结构可以确认这套模式贯穿参考答案:GameObject 构造时就初始化 this.dead = false,solution/app.js 中所有对象共享该生命周期标记。
4. 事件驱动 + 冷却:解耦碰撞响应,抑制狂点
课程采用发布订阅(Pub-Sub)模式让"检测到碰撞"与"如何响应"解耦。消息常量集中定义:
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
同时用 Cooldown 机制给武器加装"回充时间"(参考代码为 500ms),防止空格连发刷屏。你的自定义游戏若包含射击或加速道具,这一节代码可直接复用。
需求拆解与玩法选型
作业要求作品具备明确的碰撞规则,并给出了五个灵感方向。下表把"创意概念"翻译成"碰撞规则的落地方式",便于你直接选型:
| 建议场景 | 玩家对象 | 交互对象 | 碰撞后果(规则示例) |
|---|---|---|---|
| 陨石地带(Asteroid field) | 飞船 | 漂浮陨石 | 撞陨石 → 损命/销毁;合理操控穿行加分 |
| 碰碰车(Bumper cars) | 玩家车 | AI 车群 | 撞击 → 双方弹开或减分 |
| 陨石防御(Meteor defense) | 地面炮台 | 下落陨石 | 炮弹击中陨石 → 双方销毁、得分 |
| 收集游戏(Collection game) | 角色 | 道具 + 障碍物 | 碰道具 → 加分/消失;碰障碍 → 减命 |
| 领地控制(Territory control) | 己方单位 | 敌方单位 | 占据区域/清除对方 → 计分 |
选型建议:优先选择能让"至少两种不同类型对象互动"的方案(如激光 vs 敌人 + 英雄 vs 敌人),因为作业评分细则中"Exemplary"档明确要求 multiple object types with sophisticated interaction rules。例如"陨石防御"天然包含"子弹-陨石"(双方销毁、加分)与"陨石-基地"(减命、Game Over)两类碰撞,一次作业就能覆盖多种交互规则。
工程落地:架构、代码与验证
搭建开发环境
仓库为课程准备了可直接运行的前一课基线。将 6-space-game/4-collision-detection/your-work 目录视为你的工作副本,其结构为:
assets/ # enemyShip.png、player.png、laserRed.png、life.png 等精灵
index.html # 承载 <canvas id="canvas" width="1024" height="768">
app.js # 主逻辑(你的改造现场)
package.json # npm start = npx http-server -c-1 -p 5000
启动本地服务器:
cd your-work
npm start
浏览器访问 http://localhost:5000 即可看到当前游戏状态。需要说明两点前提:
- 素材已就绪:课程提示激光精灵已放入 assets 并被代码加载——查看 your-work/app.js,可见
laserImg = await loadTexture('assets/laserRed.png')已提前加载,你在自创游戏中可直接复用这份loadTexture异步加载模式; - package.json 无第三方依赖,
start脚本仅调用npx http-server,零安装成本即可起步。
推荐架构:自创游戏的核心类骨架
作业明确要求 clean code organization with appropriate class structure。把第 4 课解法(solution/app.js)的类分层提炼为模板,即可套用到你的任意玩法:
class GameObject { // 基类:坐标、尺寸、精灵、dead 标记、draw/rect 方法
constructor(x, y) { this.x = x; this.y = y; this.dead = false; /* ... */ }
draw(ctx) { ctx.drawImage(this.img, this.x, this.y, this.width, this.height); }
rectFromGameObject() { /* 返回 top/left/bottom/right */ }
}
class Player extends GameObject { /* 玩家:键盘/鼠标控制、fire()、冷却、生命与得分 */ }
class Enemy extends GameObject { /* AI:自主移动逻辑 */ }
class Projectile extends GameObject { /* 子弹:直线移动 + 出界自毁 */ }
关键实现技巧(均以课堂代码为基底):
- 输入事件化:在
keydown中先preventDefault()拦截方向键与空格(避免页面滚动),在keyup中通过eventEmitter.emit(...)广播; - 碰撞响应解耦:
initGame()内用eventEmitter.on(Messages.COLLISION_XX, handler)注册处理函数,处理函数只做"置死标记 + 改分数 + 触发特效"; - 主循环收敛:参考 solution/app.js 的
window.onload,每 100ms 一帧执行"清屏 → 更新逻辑(含碰撞检测)→ 绘制 → 绘制 UI",务必保证碰撞检测函数被真实调用。
一套可直接迁移的碰撞核心
把课上与解法中的碰撞调度函数作为"通用碰撞引擎"迁入自创游戏,替换其中的对象类型即可:
function updateGameObjects() {
const enemies = gameObjects.filter(go => go.type === 'Enemy');
const lasers = gameObjects.filter(go => go.type === 'Laser');
lasers.forEach(laser => {
enemies.forEach(enemy => {
if (intersectRect(laser.rectFromGameObject(), enemy.rectFromGameObject())) {
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, { first: laser, second: enemy });
}
});
});
gameObjects = gameObjects.filter(go => !go.dead); // 帧间统一回收
}
在此基础上,solution/app.js 展示了两条值得模仿的"规则引擎"写法:
- 激光命中敌人:双双击毙,
hero.incrementPoints()加分,敌人清空后触发胜利事件; - 敌人撞英雄:只销毁敌人,
hero.decrementLife()扣命,生命归零触发失败事件。
自创游戏可把"Enemy/Laser"替换为"障碍/玩家""道具/玩家"等自定义类型对,从而自然满足"Multiple collision types with different behaviors"的加分项。
进阶与加分项的实现次序
作业 Bonus challenges 从低到高排列,建议按下列顺序渐进实现,同时对照评分细则自测:
- 计分系统:最小代价是给玩家对象挂
points并在碰撞 handler 中累加(解法中incrementPoints()每次 +100 可作参考值); - 多种碰撞行为:为不同对象对注册不同的
COLLISION_*消息,如"道具加分""陷阱减命""边界反弹"; - 粒子/视觉特效:碰撞瞬间向
gameObjects推入若干短生命周期、自带dead计时器的特效对象——它天然契合生命周期模式,无需任何新机制; - 音效:在事件 handler 中播放不同音效即可区分碰撞类型;
- 渐进难度:借助课程强调的
setInterval/setTimeout定时机制(如敌人y += 5每 300ms 一次),可将移动间隔随时间缩短,或在每轮胜利后增加生成数量; - 进阶彩蛋:课程 Challenge 提到素材集中还有爆炸相关美术资源(见 spaceArt 素材说明),可在击中敌人时叠加爆炸帧,属于远超合格线的视觉打磨。
用评分细则反向验收你的作品
作业末尾给出的 Rubric 是唯一官方验收标准,四条维度务必逐一自查:
| 维度 | 达标自测清单(对应 Exemplary 档) |
|---|---|
| Collision Detection | 多种对象类型共存;每种对象对都有独立交互规则;相交判定在移动中持续准确 |
| Code Quality | 基类 GameObject + 子类继承结构清晰;事件消息用常量而非魔法字符串;关键函数有注释 |
| User Interaction | 键盘/鼠标响应无延迟;碰撞有即时视觉反馈;规则让玩家"玩得懂、想再玩" |
| Creativity | 有原创概念、视觉统一打磨、至少一种"意料之外"的碰撞行为 |
最后,回到课程 README 的自我检验题检验理解:"如果碰撞发生时立即删除对象而不是标记再过滤会怎样?"——答案正是作业与本文反复强调的帧中删除导致迭代崩溃或对象跳帧。能在你的自定义游戏中稳定复现"标记死亡 → 帧末过滤"这一流程,就说明你已经把第 4 课的知识从"看懂"升级到了"会用"。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00