You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/zh/6-space-game/4-collision-detection/README.md

309 lines
10 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!--
CO_OP_TRANSLATOR_METADATA:
{
"original_hash": "2e83e38c35dc003f046d7cc0bbfd4920",
"translation_date": "2025-08-23T23:00:27+00:00",
"source_file": "6-space-game/4-collision-detection/README.md",
"language_code": "zh"
}
-->
# 构建太空游戏第四部分:添加激光和检测碰撞
## 课前测验
[课前测验](https://ff-quizzes.netlify.app/web/quiz/35)
在本课中,你将学习如何使用 JavaScript 发射激光!我们将为游戏添加以下两项内容:
- **激光**:从英雄的飞船发射,垂直向上移动。
- **碰撞检测**:作为实现射击功能的一部分,我们将添加一些有趣的游戏规则:
- **激光击中敌人**:敌人被激光击中后会死亡。
- **激光击中屏幕顶部**:激光击中屏幕顶部后会被销毁。
- **敌人与英雄碰撞**:敌人与英雄相撞后双方都会被销毁。
- **敌人击中屏幕底部**:敌人击中屏幕底部后,敌人和英雄都会被销毁。
简而言之,你——*英雄*——需要在敌人到达屏幕底部之前用激光击中所有敌人。
✅ 进行一些研究,了解第一款计算机游戏的功能是什么?
让我们一起成为英雄吧!
## 碰撞检测
如何进行碰撞检测?我们需要将游戏中的对象视为移动的矩形。你可能会问为什么?因为用于绘制游戏对象的图像是一个矩形:它具有 `x`、`y`、`width` 和 `height` 属性。
如果两个矩形(例如英雄和敌人)*相交*,就发生了碰撞。碰撞后应该发生什么取决于游戏规则。为了实现碰撞检测,你需要以下内容:
1. 获取游戏对象的矩形表示方法,例如:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width
}
}
```
2. 比较函数,这个函数可以像这样:
```javascript
function intersectRect(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
```
## 如何销毁对象
在游戏中销毁对象需要让游戏知道在触发某个间隔的游戏循环中不再绘制该对象。一种方法是当某些事件发生时将游戏对象标记为*死亡*,例如:
```javascript
// collision happened
enemy.dead = true
```
然后在重新绘制屏幕之前处理*死亡*对象,例如:
```javascript
gameObjects = gameObject.filter(go => !go.dead);
```
## 如何发射激光
发射激光意味着响应按键事件并创建一个向某个方向移动的对象。因此,我们需要完成以下步骤:
1. **创建激光对象**:从英雄飞船的顶部发射,创建后开始向屏幕顶部移动。
2. **绑定按键事件代码**:选择键盘上的某个按键(例如空格键)来代表玩家发射激光。
3. **创建一个看起来像激光的游戏对象**:当按键被按下时。
## 激光的冷却时间
激光需要在每次按键时发射,例如按下*空格键*。为了防止游戏在短时间内生成过多激光,我们需要解决这个问题。解决方法是实现所谓的*冷却时间*,即一个计时器,确保激光只能在一定时间间隔内发射一次。可以这样实现:
```javascript
class Cooldown {
constructor(time) {
this.cool = false;
setTimeout(() => {
this.cool = true;
}, time)
}
}
class Weapon {
constructor {
}
fire() {
if (!this.cooldown || this.cooldown.cool) {
// produce a laser
this.cooldown = new Cooldown(500);
} else {
// do nothing - it hasn't cooled down yet.
}
}
}
```
回顾太空游戏系列第一课,了解有关*冷却时间*的内容。
## 要构建的内容
你将使用上一课中清理和重构过的代码进行扩展。可以从第二部分的代码开始,也可以使用 [第三部分的起始代码](../../../../../../../../../your-work)
> 提示:你将使用的激光已经在你的资源文件夹中,并且代码中已引用。
- **添加碰撞检测**:当激光与某物体碰撞时,需遵循以下规则:
1. **激光击中敌人**:敌人被激光击中后会死亡。
2. **激光击中屏幕顶部**:激光击中屏幕顶部后会被销毁。
3. **敌人与英雄碰撞**:敌人与英雄相撞后双方都会被销毁。
4. **敌人击中屏幕底部**:敌人击中屏幕底部后,敌人和英雄都会被销毁。
## 推荐步骤
找到在 `your-work` 子文件夹中为你创建的文件。它应该包含以下内容:
```bash
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| index.html
-| app.js
-| package.json
```
通过输入以下命令启动你的项目:
```bash
cd your-work
npm start
```
上述命令将在地址 `http://localhost:5000` 上启动一个 HTTP 服务器。打开浏览器并输入该地址,目前它应该渲染英雄和所有敌人,但还没有任何移动。
### 添加代码
1. **设置游戏对象的矩形表示以处理碰撞** 以下代码允许你获取 `GameObject` 的矩形表示。编辑你的 GameObject 类以扩展它:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width,
};
}
```
2. **添加检测碰撞的代码** 这是一个新函数,用于测试两个矩形是否相交:
```javascript
function intersectRect(r1, r2) {
return !(
r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top
);
}
```
3. **添加激光发射功能**
1. **添加按键事件消息**。按下*空格键*时应在英雄飞船上方创建一个激光。为 Messages 对象添加三个常量:
```javascript
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
```
1. **处理空格键**。编辑 `window.addEventListener` keyup 函数以处理空格键:
```javascript
} else if(evt.keyCode === 32) {
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
}
```
1. **添加监听器**。编辑 `initGame()` 函数以确保按下空格键时英雄可以发射激光:
```javascript
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
if (hero.canFire()) {
hero.fire();
}
```
并添加一个新的 `eventEmitter.on()` 函数以确保当敌人与激光碰撞时的行为:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
})
```
1. **移动对象**,确保激光逐渐移动到屏幕顶部。你将创建一个新的 Laser 类,继承 `GameObject`,如之前所做:
```javascript
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)
}
}
```
1. **处理碰撞**,为激光实现碰撞规则。添加一个 `updateGameObjects()` 函数,用于测试碰撞对象是否发生击中:
```javascript
function updateGameObjects() {
const enemies = gameObjects.filter(go => go.type === 'Enemy');
const lasers = gameObjects.filter((go) => go.type === "Laser");
// laser hit something
lasers.forEach((l) => {
enemies.forEach((m) => {
if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
first: l,
second: m,
});
}
});
});
gameObjects = gameObjects.filter(go => !go.dead);
}
```
确保在 `window.onload` 的游戏循环中添加 `updateGameObjects()`
4. **实现激光的冷却时间**,确保激光只能在一定时间间隔内发射。
最后,编辑 Hero 类以实现冷却功能:
```javascript
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;
}
}
```
到此为止,你的游戏已经具备了一些功能!你可以使用箭头键导航,用空格键发射激光,并且当击中敌人时敌人会消失。干得好!
---
## 🚀 挑战
添加爆炸效果!查看 [Space Art 仓库](../../../../6-space-game/solution/spaceArt/readme.txt) 中的游戏资源,尝试在激光击中外星人时添加爆炸效果。
## 课后测验
[课后测验](https://ff-quizzes.netlify.app/web/quiz/36)
## 复习与自学
尝试调整游戏中的时间间隔,观察会发生什么变化?阅读更多关于 [JavaScript 定时事件](https://www.freecodecamp.org/news/javascript-timing-events-settimeout-and-setinterval/) 的内容。
## 作业
[探索碰撞](assignment.md)
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于重要信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。