ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Web-Dev-For-Beginners 太空射击游戏第 4 课:激光武器与矩形碰撞检测实战指南

Web-Dev-For-Beginners 太空射击游戏第 4 课:激光武器与矩形碰撞检测实战指南 Web-Dev-For-Beginners 太空射击游戏第 4 课激光武器与矩形碰撞检测实战指南【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners本篇技术指南以 Web-Dev-For-Beginners 开源课程中《Build a Space Game Part 4: Adding A Laser and Detect Collisions》一课对应德语译文 translations/de/6-space-game/4-collision-detection/README.md为核心骨架系统讲解如何在 Canvas 游戏中为英雄飞船添加激光武器、实现基于矩形的碰撞检测、通过标记删除机制管理对象生命周期并用冷却系统限制开火频率。读者学完后将能独立实现一个具备激光射击、敌我碰撞、计分与胜负判定功能的完整网页游戏战斗系统并理解这套方法在物理模拟、交互界面、机器人避障等真实场景中的通用性。课程定位从绘制到交互的转折点在前三课中你已经完成了游戏画布绘制、英雄与敌人的移动控制。本课是整个太空游戏系列的战斗系统搭建阶段——课程原文用了一个生动的比喻就像《星球大战》中卢克的质子鱼雷精准命中死星排气口一样碰撞检测决定了游戏对象何时交互、交互后发生什么源码文档。课程结束时你将获得一套完整可运行的战斗系统按空格键发射激光激光向上飞行激光命中敌人时敌人被摧毁敌人与英雄相撞时双方同归于尽并扣除生命值敌人到达屏幕底部触发失败敌人被清空触发胜利。这些碰撞原理与航天器规避太空碎片、空中交通管制跟踪飞机的底层数学完全一致把每个对象抽象成矩形用分离轴判断是否相交。核心概念一把游戏对象表示为矩形课程采用的计算模型是轴对齐矩形边界框AABB。每个游戏对象由位置(x, y)与尺寸(width, height)共同定义四条边上边top对象的 y 坐标左边left对象的 x 坐标下边bottomy height右边rightx width。课程给出了GameObject上的边界计算函数rectFromGameObject() { return { top: this.y, left: this.x, bottom: this.y this.height, right: this.x this.width } }在仓库的完整解答 6-space-game/4-collision-detection/solution/app.js 中该方法被定义在GameObject基类第 36–43 行并被Hero、Enemy、Laser三个子类通过继承共用——这正体现了课程强调的标准化接口无论对象类型如何碰撞检测只与rectFromGameObject()返回的矩形打交道。核心概念二相交检测的分离测试算法判断两个矩形是否重叠课程采用分离测试而非直接计算重叠面积只要存在任意一条分离轴两个矩形必然不相交。四个分离条件分别是矩形 2 完全在矩形 1 的右侧r2.left r1.right矩形 2 完全在矩形 1 的左侧r2.right r1.left矩形 2 完全在矩形 1 的下方r2.top r1.bottom矩形 2 完全在矩形 1 的上方r2.bottom r1.topfunction intersectRect(r1, r2) { return !(r2.left r1.right || r2.right r1.left || r2.top r1.bottom || r2.bottom r1.top); }从源码看solution/app.js 第 126–128 行正是这个实现。课程特别解释了为什么分离测试比求交集大小更高效每个测试只需 2 次比较绝大多数情况下第一个条件就会短路返回避免了浮点乘除非常适合每帧要执行成百上千次的实时游戏循环。对象生命周期为什么不能直接删除当激光击中敌人时两个对象都需要从游戏中移除。但课程强调了一个关键的工程教训在遍历循环中途直接删除对象会导致崩溃或跳过元素例如forEach迭代时修改数组长度。解决方案是标记删除mark for deletion模式完整状态流转为具体做法分两步第一步只打标记不立即删除。// Objekt zur Entfernung markieren enemy.dead true;第二步在下一帧渲染前用filter生成只含存活对象的新数组。gameObjects gameObjects.filter(go !go.dead);课程点明了这种做法带来的三个收益当前帧可以安全执行完毕、不会因使用已删除对象而崩溃、避免被摧毁对象长期堆积造成内存膨胀。查看 solution/app.js 第 228 行实际解答正是在updateGameObjects()末尾执行这行过滤GameObject构造函数第 25 行默认this.dead false任何对象创建之初都处于存活状态。激光武器事件驱动的开火机制课程把激光视为离散对象每次按空格键生成一个新的Laser实例它沿直线向上飞行直到命中目标或飞出屏幕。实现需要协调四个组件创建在英雄位置生成激光对象输入处理键盘事件触发开火运动与生命周期激光向上移动、越界自毁视觉用laserRed.png精灵渲染激光。消息常量系统间通信的契约课程先定义了一组消息常量避免硬编码字符串造成拼写错误KEY_EVENT_SPACE: KEY_EVENT_SPACE, COLLISION_ENEMY_LASER: COLLISION_ENEMY_LASER, COLLISION_ENEMY_HERO: COLLISION_ENEMY_HERO,在 solution/app.js 第 130–138 行的Messages对象中还额外包含了方向键四个事件KEY_EVENT_UP/DOWN/LEFT/RIGHT以及本课尚未涉及的胜利/失败事件。整套事件机制由自定义的EventEmitter类支撑第 2–19 行on(message, listener)注册监听器emit(message, payload)广播事件。键盘输入空格键触发发射} else if(evt.keyCode 32) { eventEmitter.emit(Messages.KEY_EVENT_SPACE); }在 solution/app.js 第 169–183 行可以看到完整的keyup监听器方向键用evt.key如ArrowUp判断空格键用evt.keyCode 32判断回车键用于重新开始游戏。输入层与战斗逻辑完全解耦——键盘只负责emit事件具体响应由initGame()中注册的监听器决定。事件监听开火与碰撞响应在initGame()中注册空格键监听eventEmitter.on(Messages.KEY_EVENT_SPACE, () { if (hero.canFire()) { hero.fire(); } });再注册激光-敌人碰撞监听注意此处与课程文档略有差异——solution/app.js 第 263–271 行在标记双方dead的同时还会调用hero.incrementPoints()加分并在敌人全部消灭后广播胜利事件eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) { first.dead true; second.dead true; });课程特意指出updateGameObjects()检测到相交时只负责emit事件具体规则谁死、是否加分、是否胜利由监听器决定——这就是分离关注点设计物理检测与游戏规则互不干扰。冷却系统控制开火速率课程用一个类比引入冷却概念连企业号星舰的相位炮都需要充能时间。无限开火会让引擎过载、游戏失衡因此需要速率限制。文档给出的抽象模型CooldownWeapon演示了核心思想武器创建时处于热状态不可发射setTimeout到期后变为冷状态可发射发射前检查状态class Cooldown { constructor(time) { this.cool false; setTimeout(() { this.cool true; }, time); } } class Weapon { constructor() { this.cooldown null; } fire() { if (!this.cooldown || this.cooldown.cool) { // Erstelle Laserprojektile this.cooldown new Cooldown(500); } else { // Waffe kühlt noch ab } } }在实际解答中冷却逻辑被直接集成进Hero类见下一节用数值cooldown从 500 递减到 0canFire()判断cooldown 0。两种实现殊途同归课程文档版本的setTimeout更贴近事件驱动源码版本的递减计数则与游戏主循环的帧节奏更契合。分步搭建六步实现完整碰撞系统课程给出了一条清晰的实施路线图矩形边界 → 相交检测 → 激光系统 → 事件处理 → 碰撞规则 → 冷却系统。下面逐步骤展开并对照源码给出完整上下文。第 1 步为GameObject添加矩形边界方法将rectFromGameObject()加入基类见核心概念一。注意Hero、Enemy、Laser子类都通过super(x, y)调用基类构造函数并各自设置width、height、type、img因此边界方法一次定义、处处可用。第 2 步实现相交检测函数intersectRect(r1, r2)函数体与核心概念二完全一致。第 3 步实现激光开火系统这一步包含三个子任务前面已逐一介绍定义Messages常量、在keyup监听器中识别空格键、在initGame()中注册KEY_EVENT_SPACE与COLLISION_ENEMY_LASER监听器。第 4 步创建Laser类class Laser extends GameObject { constructor(x, y) { super(x, y); this.width 9; this.height 33; this.type Laser; this.img laserImg; let id setInterval(() { if (this.y 0) { this.y - 15; } else { this.dead true; clearInterval(id); } }, 100); } }要点拆解尺寸9 × 33与素材 laserRed.png 的实际像素一致每 100ms 向上移动 15 像素即每秒 150 像素当y 0到达屏幕顶部时把自己标记为dead并clearInterval清理定时器——这就是课程所说的自我管理生命周期素材已在assets目录中就绪index.html 中canvas width1024 height768为游戏画布。第 5 步实现碰撞检测系统function updateGameObjects() { const enemies gameObjects.filter(go go.type Enemy); const lasers gameObjects.filter(go go.type Laser); // Teste Laser-Gegner-Kollisionen lasers.forEach((laser) { enemies.forEach((enemy) { if (intersectRect(laser.rectFromGameObject(), enemy.rectFromGameObject())) { eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, { first: laser, second: enemy, }); } }); }); // Entferne zerstörte Objekte gameObjects gameObjects.filter(go !go.dead); }课程强调必须把updateGameObjects()加入window.onload中的主游戏循环碰撞检测才会每帧执行。从 solution/app.js 第 206–229 行可以看到解答版本还额外做了敌人-英雄碰撞检测enemies.forEach((enemy) { const heroRect hero.rectFromGameObject(); if (intersectRect(heroRect, enemy.rectFromGameObject())) { eventEmitter.emit(Messages.COLLISION_ENEMY_HERO, { enemy }); } });第 6 步为Hero类添加冷却系统class Hero extends GameObject { constructor(x, y) { super(x, y); this.width 99; this.height 75; this.type Hero; this.speed { x: 0, y: 0 }; this.cooldown 0; } fire() { gameObjects.push(new Laser(this.x 45, this.y - 10)); this.cooldown 500; let id setInterval(() { if (this.cooldown 0) { this.cooldown - 100; } else { clearInterval(id); } }, 200); } canFire() { return this.cooldown 0; } }行为分析cooldown初始为 0即英雄一出生就能开火fire()在英雄右上方x 45,y - 10生成激光——因为英雄宽度 9945恰好把激光定位在机头中心偏左-10让它出现在飞船上方开火后cooldown 500每 200ms 递减 100共 5 次递减、历时约 1 秒恢复到 0canFire()返回cooldown 0空格监听器据此决定是否放行。可验证的边界行为源码版本solution/app.js 第 60–68 行在递减到 0 时会立即clearInterval(id)避免定时器空转泄漏——这是文档版本未展开、但在完整解答中体现的工程细节。第 6.5 步解答补充敌人与游戏状态解答还实现了课程文档提到的其余碰撞规则Enemy类每 300ms 向下移动 5 像素到达画布底部y height canvas.height即停COLLISION_ENEMY_HERO监听器中敌人dead true、英雄调用decrementLife()扣一条命初始life 3命尽则广播失败isEnemiesDead()检查存活敌人数量为零则广播胜利endGame(win)停止主循环显示 Victory!!! Pew Pew... 或 You died !!! 提示按回车调用resetGame()重新开局。开发环境与运行方式课程文档提供了完整的项目结构your-work目录已就绪-| assets -| enemyShip.png -| player.png -| laserRed.png -| index.html -| app.js -| package.json启动本地开发服务器cd your-work npm start该命令实际执行npx http-server -c-1 -p 5000见 your-work/package.json 与 solution/package.json 的scripts.start字段随后浏览器访问http://localhost:5000即可看到英雄与敌人渲染在画布上。-c-1表示禁用缓存保证每次刷新都是最新代码。想要参考完整实现可直接查看 solution/app.js 或运行cd solution npm start。验证与调试清单课程给出了四组可操作的验证方式用方向键移动英雄确认位移每帧生效源码中每次按键位移 5 像素用空格键开火观察冷却期间连按无响应、冷却结束后恢复发射观察激光击中敌人后两者同时消失dead标记 filter清理观察英雄生命值图标life.png35×27随碰撞递减。进一步的调试建议在浏览器 DevTools 中给intersectRect或updateGameObjects打断点逐步查看矩形坐标修改Laser的移动间隔100ms或步长15px观察碰撞时机变化修改Hero.fire()中的cooldown 500为不同数值如 200 或 1000对比射速手感在updateGameObjects中加console.log实时追踪碰撞事件。课后练习与进阶方向本课配备的作业 Erkunden Sie Kollisionen 要求你用矩形碰撞原理创作一个小型交互游戏并给出了评估标准碰撞检测准确性、代码质量、交互体验、创意性与五个创意方向小行星带穿行、碰碰车竞技场、陨石防御、收集游戏、领地争夺。课程文档还提出了两条经典的进阶挑战添加爆炸效果素材资源见 Space Art 资源目录可让激光击中敌人时播放爆炸帧序列实现能量道具Power-Up系统创建继承GameObject的PowerUp类实现英雄与道具的碰撞检测至少包含提升射速缩短冷却与临时护盾两种类型并以随机时间与位置生成。如果希望继续深入课程的长期路线还覆盖圆形/多边形等复杂碰撞形状、粒子系统、物理动量模拟、空间分区优化如网格、四叉树、以及 Matter.js 等成熟物理引擎的接入。小结本课沉淀的核心技能对照课程文档与 解答源码你在这节课掌握的是一套可迁移到任何实时交互系统的能力组合碰撞数学矩形表示、分离轴测试、边界坐标计算生命周期管理标记删除、帧间清理、定时器资源释放事件驱动架构EventEmitter解耦输入、物理、规则三层速率限制冷却计数与状态检查保证手感与性能游戏循环集成每帧updateGameObjects → drawGameObjects的更新-渲染节奏solution/app.js 第 371–379 行。这些模式同样适用于拖拽交互、触控手势识别、数据可视化点击命中测试、机器人路径规划等真实场景——这正是课程反复强调碰撞检测无处不在的原因。【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表