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/vi/6-space-game/4-collision-detection/README.md

311 lines
13 KiB

<!--
CO_OP_TRANSLATOR_METADATA:
{
"original_hash": "2e83e38c35dc003f046d7cc0bbfd4920",
"translation_date": "2025-08-27T22:36:43+00:00",
"source_file": "6-space-game/4-collision-detection/README.md",
"language_code": "vi"
}
-->
# Xây dựng Trò chơi Không gian Phần 4: Thêm Tia Laser và Phát hiện Va chạm
## Câu hỏi trước bài học
[Câu hỏi trước bài học](https://ff-quizzes.netlify.app/web/quiz/35)
Trong bài học này, bạn sẽ học cách bắn tia laser bằng JavaScript! Chúng ta sẽ thêm hai yếu tố vào trò chơi:
- **Tia laser**: tia laser này được bắn từ tàu của nhân vật chính và bay thẳng lên trên.
- **Phát hiện va chạm**, như một phần của việc triển khai khả năng *bắn*, chúng ta cũng sẽ thêm một số quy tắc trò chơi thú vị:
- **Tia laser trúng kẻ thù**: Kẻ thù sẽ bị tiêu diệt nếu bị tia laser bắn trúng.
- **Tia laser chạm đỉnh màn hình**: Tia laser sẽ bị phá hủy nếu chạm vào phần trên cùng của màn hình.
- **Kẻ thù và nhân vật chính va chạm**: Kẻ thù và nhân vật chính sẽ bị phá hủy nếu va chạm với nhau.
- **Kẻ thù chạm đáy màn hình**: Kẻ thù và nhân vật chính sẽ bị phá hủy nếu kẻ thù chạm vào đáy màn hình.
Tóm lại, bạn -- *nhân vật chính* -- cần bắn hạ tất cả kẻ thù bằng tia laser trước khi chúng kịp di chuyển xuống đáy màn hình.
✅ Hãy tìm hiểu một chút về trò chơi máy tính đầu tiên từng được viết. Chức năng của nó là gì?
Hãy cùng trở thành anh hùng nào!
## Phát hiện va chạm
Làm thế nào để phát hiện va chạm? Chúng ta cần nghĩ về các đối tượng trong trò chơi như những hình chữ nhật di chuyển. Tại sao lại như vậy? Bởi vì hình ảnh được sử dụng để vẽ một đối tượng trò chơi là một hình chữ nhật: nó có `x`, `y`, `width``height`.
Nếu hai hình chữ nhật, ví dụ như nhân vật chính và kẻ thù *giao nhau*, thì bạn có một va chạm. Điều gì xảy ra tiếp theo sẽ phụ thuộc vào các quy tắc của trò chơi. Để triển khai phát hiện va chạm, bạn cần:
1. Một cách để lấy đại diện hình chữ nhật của một đối tượng trò chơi, như sau:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width
}
}
```
2. Một hàm so sánh, hàm này có thể trông như sau:
```javascript
function intersectRect(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
```
## Làm thế nào để phá hủy các đối tượng
Để phá hy các đối tượng trong trò chơi, bn cn thông báo cho trò chơi rng nó không nên v đối tượng này na trong vòng lp trò chơi được kích hot theo mt khong thi gian nht định. Mt cách để làm điu này là đánh du mt đối tượng trò chơi là *đã chết* khi có điu gì đó xy ra, như sau:
```javascript
// collision happened
enemy.dead = true
```
Sau đó, bn có th x lý các đối tượng *đã chết* trước khi v li màn hình, như sau:
```javascript
gameObjects = gameObject.filter(go => !go.dead);
```
## Làm thế nào để bắn tia laser
Bn tia laser có nghĩa là phn hi mt s kin phím và to ra mt đối tượng di chuyn theo mt hướng nht định. Vì vy, chúng ta cn thc hin các bước sau:
1. **Tạo một đối tượng tia laser**: t đỉnh tàu ca nhân vt chính, đối tượng này s bt đầu di chuyn lên trên v phía đỉnh màn hình ngay khi được to.
2. **Gắn mã vào một sự kiện phím**: chúng ta cn chn mt phím trên bàn phím để đại din cho hành động bn tia laser ca người chơi.
3. **Tạo một đối tượng trò chơi trông giống như tia laser** khi phím được nhn.
## Thời gian chờ cho tia laser
Tia laser cn được bn mi khi bn nhn mt phím, ví d như phím *space*. Để ngăn trò chơi to ra quá nhiu tia laser trong mt khong thi gian ngn, chúng ta cn khc phc điu này. Cách khc phc là trin khai mt cái gi là *thời gian chờ*, mt b đếm thi gian, đảm bo rng tia laser ch có th được bn sau mt khong thi gian nht định. Bn có th trin khai điu này như sau:
```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.
}
}
}
```
Tham kho bài hc 1 trong lot bài trò chơi không gian để nhc li v *thời gian chờ*.
## Những gì cần xây dựng
Bn s s dng mã hin có (mà bn nên đã dn dp và tái cu trúc) t bài hc trước và m rng nó. Bt đầu vi mã t phn II hoc s dng mã ti [Phần III - khởi đầu](../../../../../../../../../your-work).
> mẹo: tia laser mà bạn sẽ làm việc đã có sẵn trong thư mục tài nguyên và được tham chiếu bởi mã của bạn.
- **Thêm phát hin va chm**, khi tia laser va chm vi mt đối tượng, các quy tc sau s được áp dng:
1. **Tia laser trúng kẻ thù**: k thù s chết nếu b tia laser bn trúng.
2. **Tia laser chạm đỉnh màn hình**: Tia laser s b phá hy nếu chm vào phn trên cùng ca màn hình.
3. **Kẻ thù và nhân vật chính va chạm**: k thù và nhân vt chính s b phá hy nếu va chm vi nhau.
4. **Kẻ thù chạm đáy màn hình**: K thù và nhân vt chính s b phá hy nếu k thù chm vào đáy màn hình.
## Các bước đề xuất
Tìm các tp đã được to sn cho bn trong thư mc con `your-work`. Nó s cha các tp sau:
```bash
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| index.html
-| app.js
-| package.json
```
Bn bt đầu d án trong thư mc `your_work` bng cách gõ:
```bash
cd your-work
npm start
```
Lnh trên s khi động mt HTTP Server ti địa ch `http://localhost:5000`. M trình duyt và nhp địa ch đó, hin ti nó s hin th nhân vt chính và tt c k thù, nhưng chưa có gì di chuyn c :).
### Thêm mã
1. **Thiết lập đại diện hình chữ nhật cho đối tượng trò chơi để xử lý va chạm**. Mã dưới đây cho phép bn ly đại din hình ch nht ca mt `GameObject`. Chnh sa lp GameObject ca bn để m rng nó:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width,
};
}
```
2. **Thêm mã kiểm tra va chạm**. Đây s là mt hàm mi kim tra xem hai hình ch nht có giao nhau hay không:
```javascript
function intersectRect(r1, r2) {
return !(
r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top
);
}
```
3. **Thêm khả năng bắn tia laser**
1. **Thêm thông báo sự kiện phím**. Phím *space* s to ra mt tia laser ngay phía trên tàu ca nhân vt chính. Thêm ba hng s vào đối tượng Messages:
```javascript
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
```
1. **Xử lý phím space**. Chnh sa hàm `window.addEventListener` keyup để x lý phím space:
```javascript
} else if(evt.keyCode === 32) {
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
}
```
1. **Thêm trình lắng nghe**. Chnh sa hàm `initGame()` để đảm bo rng nhân vt chính có th bn khi phím space được nhn:
```javascript
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
if (hero.canFire()) {
hero.fire();
}
```
và thêm một hàm `eventEmitter.on()` mới để đảm bảo hành vi khi kẻ thù va chạm với tia laser:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
})
```
1. **Di chuyển đối tượng**, Đảm bảo rằng tia laser di chuyển dần lên đỉnh màn hình. Bạn sẽ tạo một lớp Laser mới mở rộng từ `GameObject`, như bạn đã làm trước đây:
```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. **Xử lý va chạm**, Triển khai các quy tắc va chạm cho tia laser. Thêm một hàm `updateGameObjects()` để kiểm tra các đối tượng va chạm:
```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);
}
```
Đảm bảo thêm `updateGameObjects()` vào vòng lặp trò chơi trong `window.onload`.
4. **Triển khai thời gian chờ** cho tia laser, để nó chỉ có thể được bắn sau một khoảng thời gian nhất định.
Cuối cùng, chỉnh sửa lớp Hero để nó có thể xử lý thời gian chờ:
```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;
}
}
```
Đến thời điểm này, trò chơi của bạn đã có một số chức năng! Bạn có thể di chuyển bằng các phím mũi tên, bắn tia laser bằng phím space, và kẻ thù sẽ biến mất khi bạn bắn trúng chúng. Làm tốt lắm!
---
## 🚀 Thử thách
Thêm hiệu ứng nổ! Hãy xem các tài nguyên trò chơi trong [kho Space Art](../../../../6-space-game/solution/spaceArt/readme.txt) và thử thêm hiệu ứng nổ khi tia laser bắn trúng người ngoài hành tinh.
## Câu hỏi sau bài học
[Câu hỏi sau bài học](https://ff-quizzes.netlify.app/web/quiz/36)
## Ôn tập & Tự học
Thử nghiệm với các khoảng thời gian trong trò chơi của bạn cho đến nay. Điều gì xảy ra khi bạn thay đổi chúng? Đọc thêm về [các sự kiện thời gian trong JavaScript](https://www.freecodecamp.org/news/javascript-timing-events-settimeout-and-setinterval/).
## Bài tập
[Khám phá va chạm](assignment.md)
---
**Tuyên bố miễn trừ trách nhiệm**:
Tài liệu này đã được dịch bằng dịch vụ dịch thuật AI [Co-op Translator](https://github.com/Azure/co-op-translator). Mặc dù chúng tôi cố gắng đảm bảo độ chính xác, xin lưu ý rằng các bản dịch tự động có thể chứa lỗi hoặc không chính xác. Tài liệu gốc bằng ngôn ngữ bản địa nên được coi là nguồn thông tin chính thức. Đối với các thông tin quan trọng, khuyến nghị sử dụng dịch vụ dịch thuật chuyên nghiệp bởi con người. Chúng tôi không chịu trách nhiệm cho bất kỳ sự hiểu lầm hoặc diễn giải sai nào phát sinh từ việc sử dụng bản dịch này.