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/3-moving-elements-around/README.md

905 lines
43 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": "8c55a2bd4bc0ebe4c88198fd563a9e09",
"translation_date": "2025-11-06T11:31:14+00:00",
"source_file": "6-space-game/3-moving-elements-around/README.md",
"language_code": "vi"
}
-->
# Xây dựng trò chơi không gian Phần 3: Thêm chuyển động
```mermaid
journey
title Your Game Animation Journey
section Movement Basics
Understand motion principles: 3: Student
Learn coordinate updates: 4: Student
Implement basic movement: 4: Student
section Player Controls
Handle keyboard events: 4: Student
Prevent default behaviors: 5: Student
Create responsive controls: 5: Student
section Game Systems
Build game loop: 5: Student
Manage object lifecycle: 5: Student
Implement pub/sub pattern: 5: Student
```
Hãy nghĩ về những trò chơi yêu thích của bạn điều làm chúng hấp dẫn không chỉ là đồ họa đẹp mắt, mà còn là cách mọi thứ di chuyển và phản hồi theo hành động của bạn. Hiện tại, trò chơi không gian của bạn giống như một bức tranh đẹp, nhưng chúng ta sắp thêm chuyển động để làm nó sống động hơn.
Khi các kỹ sư của NASA lập trình máy tính hướng dẫn cho các nhiệm vụ Apollo, họ đã đối mặt với một thách thức tương tự: làm thế nào để tàu vũ trụ phản hồi theo điều khiển của phi công trong khi vẫn tự động duy trì các điều chỉnh hướng đi? Những nguyên tắc chúng ta sẽ học hôm nay phản ánh những khái niệm tương tự quản lý chuyển động do người chơi điều khiển cùng với các hành vi hệ thống tự động.
Trong bài học này, bạn sẽ học cách làm cho tàu vũ trụ lướt qua màn hình, phản hồi theo lệnh của người chơi, và tạo ra các mẫu chuyển động mượt mà. Chúng ta sẽ chia nhỏ mọi thứ thành các khái niệm dễ hiểu và xây dựng chúng một cách tự nhiên.
Kết thúc bài học, người chơi sẽ có thể điều khiển tàu anh hùng bay quanh màn hình trong khi các tàu địch tuần tra phía trên. Quan trọng hơn, bạn sẽ hiểu được các nguyên tắc cốt lõi vận hành hệ thống chuyển động trong trò chơi.
```mermaid
mindmap
root((Game Animation))
Movement Types
Player Controlled
Automatic Motion
Physics Based
Scripted Paths
Event Handling
Keyboard Input
Mouse Events
Touch Controls
Default Prevention
Game Loop
Update Logic
Render Frame
Clear Canvas
Frame Rate Control
Object Management
Position Updates
Collision Detection
Lifecycle Management
State Tracking
Communication
Pub/Sub Pattern
Event Emitters
Message Passing
Loose Coupling
```
## Quiz trước bài học
[Quiz trước bài học](https://ff-quizzes.netlify.app/web/quiz/33)
## Hiểu về chuyển động trong trò chơi
Trò chơi trở nên sống động khi mọi thứ bắt đầu di chuyển, và có hai cách cơ bản để điều này xảy ra:
- **Chuyển động do người chơi điều khiển**: Khi bạn nhấn một phím hoặc click chuột, một thứ gì đó di chuyển. Đây là sự kết nối trực tiếp giữa bạn và thế giới trò chơi.
- **Chuyển động tự động**: Khi trò chơi tự quyết định di chuyển các đối tượng như những tàu địch cần tuần tra màn hình dù bạn có làm gì hay không.
Làm cho các đối tượng di chuyển trên màn hình máy tính đơn giản hơn bạn nghĩ. Nhớ những tọa độ x và y từ lớp toán không? Đó chính xác là những gì chúng ta đang làm việc ở đây. Khi Galileo theo dõi các mặt trăng của sao Mộc vào năm 1610, ông về cơ bản cũng đang làm điều tương tự vẽ các vị trí theo thời gian để hiểu các mẫu chuyển động.
Di chuyển các đối tượng trên màn hình giống như tạo một cuốn sách hoạt hình bạn cần làm theo ba bước đơn giản sau:
```mermaid
flowchart LR
A["Frame N"] --> B["Update Positions"]
B --> C["Clear Canvas"]
C --> D["Draw Objects"]
D --> E["Frame N+1"]
E --> F{Continue?}
F -->|Yes| B
F -->|No| G["Game Over"]
subgraph "Animation Cycle"
H["1. Calculate new positions"]
I["2. Erase previous frame"]
J["3. Render new frame"]
end
style B fill:#e1f5fe
style C fill:#ffebee
style D fill:#e8f5e8
```
1. **Cập nhật vị trí** Thay đổi nơi đối tượng của bạn nên ở (có thể di chuyển nó 5 pixel sang phải)
2. **Xóa khung hình cũ** Làm sạch màn hình để không thấy các vệt mờ
3. **Vẽ khung hình mới** Đặt đối tượng của bạn vào vị trí mới
Làm điều này đủ nhanh, và bùm! Bạn có chuyển động mượt mà khiến người chơi cảm thấy tự nhiên.
Đây là cách nó có thể trông như trong mã:
```javascript
// Set the hero's location
hero.x += 5;
// Clear the rectangle that hosts the hero
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
**Mã này làm gì:**
- **Cập nhật** tọa độ x của tàu anh hùng thêm 5 pixel để di chuyển theo chiều ngang
- **Xóa** toàn bộ khu vực canvas để loại bỏ khung hình trước đó
- **Lấp đầy** canvas bằng màu nền đen
- **Vẽ lại** hình ảnh tàu anh hùng tại vị trí mới
✅ Bạn có thể nghĩ ra lý do tại sao việc vẽ lại tàu anh hùng nhiều khung hình mỗi giây có thể gây ra chi phí hiệu suất không? Đọc về [các giải pháp thay thế cho mẫu này](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas).
## Xử lý sự kiện bàn phím
Đây là nơi chúng ta kết nối đầu vào của người chơi với hành động trong trò chơi. Khi ai đó nhấn phím cách để bắn tia laser hoặc nhấn phím mũi tên để né tránh một tiểu hành tinh, trò chơi của bạn cần phát hiện và phản hồi đầu vào đó.
Các sự kiện bàn phím xảy ra ở cấp độ cửa sổ, nghĩa là toàn bộ cửa sổ trình duyệt của bạn đang lắng nghe các lần nhấn phím đó. Các lần click chuột, mặt khác, có thể được gắn với các phần tử cụ thể (như click vào một nút). Đối với trò chơi không gian của chúng ta, chúng ta sẽ tập trung vào điều khiển bàn phím vì đó là điều mang lại cảm giác arcade cổ điển cho người chơi.
Điều này làm tôi nhớ đến cách các nhà điều hành điện báo vào thế kỷ 19 phải dịch đầu vào mã Morse thành các thông điệp có ý nghĩa chúng ta đang làm điều tương tự, dịch các lần nhấn phím thành lệnh trò chơi.
Để xử lý một sự kiện, bạn cần sử dụng phương thức `addEventListener()` của cửa sổ và cung cấp cho nó hai tham số đầu vào. Tham số đầu tiên là tên của sự kiện, ví dụ `keyup`. Tham số thứ hai là hàm sẽ được gọi khi sự kiện xảy ra.
Đây là một ví dụ:
```javascript
window.addEventListener('keyup', (evt) => {
// evt.key = string representation of the key
if (evt.key === 'ArrowUp') {
// do something
}
});
```
**Phân tích những gì xảy ra ở đây:**
- **Lắng nghe** các sự kiện bàn phím trên toàn bộ cửa sổ
- **Thu thập** đối tượng sự kiện chứa thông tin về phím nào đã được nhấn
- **Kiểm tra** nếu phím nhấn khớp với một phím cụ thể (trong trường hợp này là mũi tên lên)
- **Thực thi** mã khi điều kiện được đáp ứng
Đối với các sự kiện phím, có hai thuộc tính trên đối tượng sự kiện mà bạn có thể sử dụng để xem phím nào đã được nhấn:
- `key` - đây là biểu diễn dạng chuỗi của phím nhấn, ví dụ `'ArrowUp'`
- `keyCode` - đây là biểu diễn dạng số, ví dụ `37`, tương ứng với `ArrowLeft`
✅ Việc thao tác sự kiện phím rất hữu ích ngoài phát triển trò chơi. Bạn có thể nghĩ ra những ứng dụng nào khác cho kỹ thuật này?
```mermaid
sequenceDiagram
participant User
participant Browser
participant EventSystem
participant GameLogic
participant Hero
User->>Browser: Presses ArrowUp key
Browser->>EventSystem: keydown event
EventSystem->>EventSystem: preventDefault()
EventSystem->>GameLogic: emit('KEY_EVENT_UP')
GameLogic->>Hero: hero.y -= 5
Hero->>Hero: Update position
Note over Browser,GameLogic: Event flow prevents browser defaults
Note over GameLogic,Hero: Pub/sub pattern enables clean communication
```
### Các phím đặc biệt: lưu ý!
Một số phím có hành vi tích hợp của trình duyệt có thể gây cản trở trò chơi của bạn. Các phím mũi tên cuộn trang và phím cách nhảy xuống những hành vi bạn không muốn khi ai đó đang cố điều khiển tàu vũ trụ của họ.
Chúng ta có thể ngăn chặn những hành vi mặc định này và để trò chơi xử lý đầu vào thay thế. Điều này tương tự như cách các lập trình viên máy tính thời kỳ đầu phải ghi đè các ngắt hệ thống để tạo ra các hành vi tùy chỉnh chúng ta chỉ đang làm điều đó ở cấp độ trình duyệt. Đây là cách:
```javascript
const onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // Arrow keys
case 32:
e.preventDefault();
break; // Space
default:
break; // do not block other keys
}
};
window.addEventListener('keydown', onKeyDown);
```
**Hiểu mã ngăn chặn này:**
- **Kiểm tra** các mã phím cụ thể có thể gây ra hành vi không mong muốn của trình duyệt
- **Ngăn chặn** hành động mặc định của trình duyệt đối với các phím mũi tên và phím cách
- **Cho phép** các phím khác hoạt động bình thường
- **Sử dụng** `e.preventDefault()` để dừng hành vi tích hợp của trình duyệt
### 🔄 **Kiểm tra sư phạm**
**Hiểu về xử lý sự kiện**: Trước khi chuyển sang chuyển động tự động, hãy đảm bảo bạn có thể:
- ✅ Giải thích sự khác biệt giữa các sự kiện `keydown``keyup`
- ✅ Hiểu tại sao chúng ta ngăn chặn các hành vi mặc định của trình duyệt
- ✅ Mô tả cách các trình lắng nghe sự kiện kết nối đầu vào của người dùng với logic trò chơi
- ✅ Xác định các phím nào có thể gây cản trở điều khiển trò chơi
**Kiểm tra nhanh bản thân**: Điều gì sẽ xảy ra nếu bạn không ngăn chặn hành vi mặc định cho các phím mũi tên?
*Trả lời: Trình duyệt sẽ cuộn trang, gây cản trở chuyển động trong trò chơi*
**Kiến trúc hệ thống sự kiện**: Bây giờ bạn đã hiểu:
- **Lắng nghe cấp độ cửa sổ**: Thu thập sự kiện ở cấp độ trình duyệt
- **Thuộc tính đối tượng sự kiện**: Chuỗi `key` so với số `keyCode`
- **Ngăn chặn mặc định**: Dừng các hành vi không mong muốn của trình duyệt
- **Logic điều kiện**: Phản hồi các tổ hợp phím cụ thể
## Chuyển động do trò chơi tạo ra
Bây giờ hãy nói về các đối tượng di chuyển mà không cần đầu vào của người chơi. Hãy nghĩ về các tàu địch lướt qua màn hình, các viên đạn bay theo đường thẳng, hoặc các đám mây trôi trong nền. Chuyển động tự động này làm cho thế giới trò chơi của bạn cảm giác sống động ngay cả khi không ai chạm vào điều khiển.
Chúng ta sử dụng các bộ hẹn giờ tích hợp của JavaScript để cập nhật vị trí theo các khoảng thời gian đều đặn. Khái niệm này tương tự như cách đồng hồ quả lắc hoạt động một cơ chế đều đặn kích hoạt các hành động theo thời gian. Đây là cách đơn giản nó có thể trông như:
```javascript
const id = setInterval(() => {
// Move the enemy on the y axis
enemy.y += 10;
}, 100);
```
**Mã chuyển động này làm gì:**
- **Tạo** một bộ hẹn giờ chạy mỗi 100 mili giây
- **Cập nhật** tọa độ y của tàu địch thêm 10 pixel mỗi lần
- **Lưu trữ** ID khoảng thời gian để chúng ta có thể dừng nó sau này nếu cần
- **Di chuyển** tàu địch xuống dưới màn hình một cách tự động
## Vòng lặp trò chơi
Đây là khái niệm kết nối mọi thứ lại với nhau vòng lặp trò chơi. Nếu trò chơi của bạn là một bộ phim, vòng lặp trò chơi sẽ là máy chiếu phim, hiển thị khung hình này đến khung hình khác nhanh đến mức mọi thứ dường như di chuyển mượt mà.
Mỗi trò chơi đều có một vòng lặp như thế này chạy phía sau. Đó là một hàm cập nhật tất cả các đối tượng trò chơi, vẽ lại màn hình, và lặp lại quá trình này liên tục. Điều này theo dõi tàu anh hùng của bạn, tất cả các tàu địch, bất kỳ tia laser nào bay xung quanh toàn bộ trạng thái trò chơi.
Khái niệm này làm tôi nhớ đến cách các nhà làm phim hoạt hình thời kỳ đầu như Walt Disney phải vẽ lại các nhân vật từng khung hình để tạo ra ảo giác chuyển động. Chúng ta đang làm điều tương tự, chỉ là với mã thay vì bút chì.
Đây là cách một vòng lặp trò chơi thường trông như, được biểu diễn bằng mã:
```mermaid
flowchart TD
A["Start Game Loop"] --> B["Clear Canvas"]
B --> C["Fill Background"]
C --> D["Update Game Objects"]
D --> E["Draw Hero"]
E --> F["Draw Enemies"]
F --> G["Draw UI Elements"]
G --> H["Wait for Next Frame"]
H --> I{Game Running?}
I -->|Yes| B
I -->|No| J["End Game"]
subgraph "Frame Rate Control"
K["60 FPS = 16.67ms"]
L["30 FPS = 33.33ms"]
M["10 FPS = 100ms"]
end
style B fill:#ffebee
style D fill:#e1f5fe
style E fill:#e8f5e8
style F fill:#e8f5e8
```
```javascript
const gameLoopId = setInterval(() => {
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
}
gameLoop();
}, 200);
```
**Hiểu cấu trúc vòng lặp trò chơi:**
- **Xóa** toàn bộ canvas để loại bỏ khung hình trước đó
- **Lấp đầy** nền bằng một màu đồng nhất
- **Vẽ** tất cả các đối tượng trò chơi tại vị trí hiện tại của chúng
- **Lặp lại** quá trình này mỗi 200 mili giây để tạo ra hoạt hình mượt mà
- **Quản lý** tốc độ khung hình bằng cách kiểm soát thời gian khoảng cách
## Tiếp tục trò chơi không gian
Bây giờ chúng ta sẽ thêm chuyển động vào cảnh tĩnh bạn đã xây dựng trước đó. Chúng ta sẽ biến nó từ một ảnh chụp màn hình thành một trải nghiệm tương tác. Chúng ta sẽ làm từng bước để đảm bảo mỗi phần xây dựng dựa trên phần trước.
Lấy mã từ nơi chúng ta đã dừng lại trong bài học trước (hoặc bắt đầu với mã trong thư mục [Phần II - khởi đầu](../../../../6-space-game/3-moving-elements-around/your-work) nếu bạn cần bắt đầu lại).
**Đây là những gì chúng ta sẽ xây dựng hôm nay:**
- **Điều khiển anh hùng**: Các phím mũi tên sẽ điều khiển tàu vũ trụ của bạn quanh màn hình
- **Chuyển động của địch**: Những tàu ngoài hành tinh sẽ bắt đầu tiến công
Hãy bắt đầu triển khai các tính năng này.
## Các bước được khuyến nghị
Tìm các tệp đã được tạo cho bạn trong thư mục con `your-work`. Nó nên chứa các tệp sau:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
Bạn bắt đầu dự án của mình trong thư mục `your-work` bằng cách gõ:
```bash
cd your-work
npm start
```
**Lệnh này làm gì:**
- **Điều hướng** đến thư mục dự án của bạn
- **Khởi động** một HTTP Server tại địa chỉ `http://localhost:5000`
- **Phục vụ** các tệp trò chơi của bạn để bạn có thể kiểm tra chúng trong trình duyệt
Lệnh trên sẽ khởi động một HTTP Server tại địa chỉ `http://localhost:5000`. Mở trình duyệt và nhập địa chỉ đó, ngay bây giờ nó sẽ hiển thị tàu anh hùng và tất cả các tàu địch; chưa có gì di chuyển - nhưng sẽ sớm thôi!
### Thêm mã
1. **Thêm các đối tượng chuyên dụng** cho `hero`, `enemy``game object`, chúng nên có các thuộc tính `x``y`. (Nhớ phần về [Kế thừa hoặc thành phần](../README.md)).
*GỢI Ý* `game object` nên là đối tượng có `x``y` và khả năng tự vẽ lên canvas.
> **Mẹo**: Bắt đầu bằng cách thêm một lớp `GameObject` mới với constructor được định nghĩa như dưới đây, và sau đó vẽ nó lên canvas:
```javascript
class GameObject {
constructor(x, y) {
this.x = x;
this.y = y;
this.dead = false;
this.type = "";
this.width = 0;
this.height = 0;
this.img = undefined;
}
draw(ctx) {
ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
}
}
```
**Hiểu lớp cơ bản này:**
- **Định nghĩa** các thuộc tính chung mà tất cả các đối tượng trò chơi chia sẻ (vị trí, kích thước, hình ảnh)
- **Bao gồm** một cờ `dead` để theo dõi liệu đối tượng có nên bị loại bỏ hay không
- **Cung cấp** một phương thức `draw()` để hiển thị đối tượng trên canvas
- **Đặt** các giá trị mặc định cho tất cả các thuộc tính mà các lớp con có thể ghi đè
```mermaid
classDiagram
class GameObject {
+x: number
+y: number
+dead: boolean
+type: string
+width: number
+height: number
+img: Image
+draw(ctx)
}
class Hero {
+speed: number
+type: "Hero"
+width: 98
+height: 75
}
class Enemy {
+type: "Enemy"
+width: 98
+height: 50
+setInterval()
}
GameObject <|-- Hero
GameObject <|-- Enemy
class EventEmitter {
+listeners: object
+on(message, listener)
+emit(message, payload)
}
```
Bây giờ, mở rộng `GameObject` để tạo `Hero``Enemy`:
```javascript
class Hero extends GameObject {
constructor(x, y) {
super(x, y);
this.width = 98;
this.height = 75;
this.type = "Hero";
this.speed = 5;
}
}
```
```javascript
class Enemy extends GameObject {
constructor(x, y) {
super(x, y);
this.width = 98;
this.height = 50;
this.type = "Enemy";
const id = setInterval(() => {
if (this.y < canvas.height - this.height) {
this.y += 5;
} else {
console.log('Stopped at', this.y);
clearInterval(id);
}
}, 300);
}
}
```
**Các khái niệm chính trong các lớp này:**
- **Kế tha** t `GameObject` bng cách s dng t khóa `extends`
- **Gi** constructor ca lp cha vi `super(x, y)`
- **Đặt** các kích thước và thuc tính c th cho tng loi đối tượng
- **Trin khai** chuyn động t động cho các tàu địch bng cách s dng `setInterval()`
2. **Thêm các trình xử lý sự kiện phím** để x lý điu hướng phím (di chuyn tàu anh hùng lên/xung trái/phi)
*NHỚ* đây là h ta độ Cartesian, góc trên bên trái là `0,0`. Cũng nh thêm mã để dng *hành vi mặc định*
> **Mẹo**: Tạo hàm `onKeyDown` của bạn và gắn nó vào cửa sổ:
```javascript
const onKeyDown = function (e) {
console.log(e.keyCode);
// Add the code from the lesson above to stop default behavior
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // Arrow keys
case 32:
e.preventDefault();
break; // Space
default:
break; // do not block other keys
}
};
window.addEventListener("keydown", onKeyDown);
```
**Trình xử lý sự kiện này làm gì:**
- **Lng nghe** các s kin nhn phím trên toàn b ca s
- **Ghi li** mã phím để giúp bn g li các phím đang được nhn
- **Ngăn chn** hành vi mc định ca trình duyt đối vi các phím mũi tên và phím cách
- **Cho phép** các phím khác hot động bình thường
Kim tra bng điu khin trình duyt ca bn ti thi đim này, và xem các ln nhn phím được ghi li.
3. **Triển khai** [Mẫu Pub-Sub](../README.md), điu này s gi mã ca bn sch s khi bn tiếp tc các phn còn li.
Mu Publish-Subscribe giúp t chc mã ca bn bng cách tách bit vic phát hin s kin khi x lý s kin. Điu này làm cho mã ca bn d bo trì và d m rng hơn.
Để thc hin phn cui này, bn có th:
1. **Thêm một trình lắng nghe sự kiện** trên ca s:
```javascript
window.addEventListener("keyup", (evt) => {
if (evt.key === "ArrowUp") {
eventEmitter.emit(Messages.KEY_EVENT_UP);
} else if (evt.key === "ArrowDown") {
eventEmitter.emit(Messages.KEY_EVENT_DOWN);
} else if (evt.key === "ArrowLeft") {
eventEmitter.emit(Messages.KEY_EVENT_LEFT);
} else if (evt.key === "ArrowRight") {
eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
}
});
```
**Hệ thống sự kiện này làm gì:**
- **Phát hiện** đầu vào bàn phím và chuyển đổi nó thành các sự kiện trò chơi tùy chỉnh
- **Tách biệt** việc phát hiện đầu vào khỏi logic trò chơi
- **Làm cho** việc thay đổi điều khiển sau này dễ dàng mà không ảnh hưởng đến mã trò chơi
- **Cho phép** nhiều hệ thống phản hồi cùng một đầu vào
```mermaid
flowchart TD
A["Keyboard Input"] --> B["Window Event Listener"]
B --> C["Event Emitter"]
C --> D["KEY_EVENT_UP"]
C --> E["KEY_EVENT_DOWN"]
C --> F["KEY_EVENT_LEFT"]
C --> G["KEY_EVENT_RIGHT"]
D --> H["Hero Movement"]
D --> I["Sound System"]
D --> J["Visual Effects"]
E --> H
F --> H
G --> H
style A fill:#e1f5fe
style C fill:#e8f5e8
style H fill:#fff3e0
```
2. **Tạo một lớp EventEmitter** để phát hành và đăng ký các thông báo:
```javascript
class EventEmitter {
constructor() {
this.listeners = {};
}
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
3. **Thêm các hằng số** và thiết lập EventEmitter:
```javascript
const Messages = {
KEY_EVENT_UP: "KEY_EVENT_UP",
KEY_EVENT_DOWN: "KEY_EVENT_DOWN",
KEY_EVENT_LEFT: "KEY_EVENT_LEFT",
KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
**Hiểu thiết lập này:**
- **Định nghĩa** các hằng số thông báo để tránh lỗi chính tả và dễ dàng tái cấu trúc
- **Khai báo** các biến cho hình ảnh, ngữ cảnh canvas, và trạng thái trò chơi
- **Tạo** một EventEmitter toàn cục cho hệ thống pub-sub
- **Khởi tạo** một mảng để chứa tất cả các đối tượng trong trò chơi
4. **Khởi động trò chơi**
```javascript
function initGame() {
gameObjects = [];
createEnemies();
createHero();
eventEmitter.on(Messages.KEY_EVENT_UP, () => {
hero.y -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
hero.y += 5;
});
eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
hero.x -= 5;
});
4. **Thiết lập vòng lặp trò chơi**
Tái cấu trúc hàm `window.onload` để khởi động trò chơi và thiết lập vòng lặp trò chơi với khoảng thời gian hợp lý. Bạn cũng sẽ thêm tia laser:
```javascript
window.onload = async () => {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
heroImg = await loadTexture("assets/player.png");
enemyImg = await loadTexture("assets/enemyShip.png");
laserImg = await loadTexture("assets/laserRed.png");
initGame();
const gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100);
};
```
**Hiểu về thiết lập trò chơi:**
- **Chờ** trang tải hoàn toàn trước khi bắt đầu
- **Lấy** phần tử canvas và ngữ cảnh render 2D của nó
- **Tải** tất cả các tài nguyên hình ảnh một cách không đồng bộ bằng `await`
- **Bắt đầu** vòng lặp trò chơi chạy với khoảng thời gian 100ms (10 FPS)
- **Xóa** và vẽ lại toàn bộ màn hình mỗi khung hình
5. **Thêm mã** để di chuyển kẻ thù theo một khoảng thời gian nhất định
Tái cấu trúc hàm `createEnemies()` để tạo kẻ thù và thêm chúng vào lớp gameObjects mới:
```javascript
function createEnemies() {
const MONSTER_TOTAL = 5;
const MONSTER_WIDTH = MONSTER_TOTAL * 98;
const START_X = (canvas.width - MONSTER_WIDTH) / 2;
const STOP_X = START_X + MONSTER_WIDTH;
for (let x = START_X; x < STOP_X; x += 98) {
for (let y = 0; y < 50 * 5; y += 50) {
const enemy = new Enemy(x, y);
enemy.img = enemyImg;
gameObjects.push(enemy);
}
}
}
```
**Chức năng của việc tạo kẻ thù:**
- **Tính toán** v trí để đặt k thù gia màn hình
- **To** mt lưới k thù bng cách s dng vòng lp lng nhau
- **Gán** hình nh k thù cho tng đối tượng k thù
- **Thêm** tng k thù vào mng đối tượng trò chơi toàn cc
và thêm mt hàm `createHero()` để thc hin quy trình tương t cho nhân vt chính.
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
**Chức năng của việc tạo nhân vật chính:**
- **Đặt v trí** nhân vt chính gia phía dưới màn hình
- **Gán** hình nh nhân vt chính cho đối tượng nhân vt chính
- **Thêm** nhân vt chính vào mng đối tượng trò chơi để hin th
và cui cùng, thêm mt hàm `drawGameObjects()` để bt đầu v:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
**Hiểu về hàm vẽ:**
- **Duyt qua** tt c các đối tượng trò chơi trong mng
- **Gi** phương thc `draw()` trên tng đối tượng
- **Truyn** ng cnh canvas để các đối tượng có th t hin th
### 🔄 **Kiểm tra sư phạm**
**Hiểu toàn diện hệ thống trò chơi**: Xác minh s thành tho ca bn v toàn b kiến trúc:
- Làm thế nào kế tha cho phép Hero và Enemy chia s các thuc tính chung ca GameObject?
- Ti sao mô hình pub/sub làm cho mã ca bn d bo trì hơn?
- Vai trò ca vòng lp trò chơi trong vic to ra hot nh mượt mà là gì?
- Làm thế nào các trình lng nghe s kin kết ni đầu vào ca người dùng vi hành vi ca đối tượng trò chơi?
**Tích hợp hệ thống**: Trò chơi ca bn hin th hin:
- **Thiết kế hướng đối tượng**: Các lp cơ s vi kế tha chuyên bit
- **Kiến trúc hướng s kin**: Mô hình pub/sub để gim s ph thuc
- **Khung hot nh**: Vòng lp trò chơi vi các cp nht khung hình nht quán
- **X lý đầu vào**: S kin bàn phím vi ngăn chn mc định
- **Qun lý tài nguyên**: Ti hình nh và hin th sprite
**Mẫu chuyên nghiệp**: Bn đã trin khai:
- **Phân tách trách nhim**: Đầu vào, logic và hin th được tách bit
- **Đa hình**: Tt c các đối tượng trò chơi chia s giao din v chung
- **Truyn thông đip**: Giao tiếp sch gia các thành phn
- **Qun lý tài nguyên**: X lý sprite và hot nh hiu qu
K thù ca bn s bt đầu tiến v phía tàu vũ tr ca nhân vt chính!
}
}
```
and add a `createHero()` function to do a similar process for the hero.
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
và cui cùng, thêm mt hàm `drawGameObjects()` để bt đầu v:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
K thù ca bn s bt đầu tiến v phía tàu vũ tr ca nhân vt chính!
---
## Thử thách GitHub Copilot Agent 🚀
Đây là mt th thách s ci thin độ mượt mà ca trò chơi: thêm gii hn màn hình và điu khin mượt mà. Hin ti, nhân vt chính ca bn có th bay ra khi màn hình, và chuyn động có th cm thy git.
**Nhiệm vụ của bạn:** Làm cho tàu vũ tr ca bn cm thy thc tế hơn bng cách trin khai gii hn màn hình và chuyn động mượt mà. Điu này tương t như cách h thng điu khin bay ca NASA ngăn tàu vũ tr vượt quá các thông s hot động an toàn.
**Những gì cần xây dựng:** To mt h thng gi tàu vũ tr ca nhân vt chính trên màn hình, và làm cho điu khin cm thy mượt mà. Khi người chơi gi phím mũi tên, tàu nên lướt liên tc thay vì di chuyn theo tng bước. Hãy cân nhc thêm phn hi hình nh khi tàu đạt đến gii hn màn hình có th là mt hiu ng nh để ch ra rìa khu vc chơi.
Tìm hiu thêm v [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) ti đây.
## 🚀 Thử thách
T chc mã tr nên ngày càng quan trng khi d án phát trin. Bn có th đã nhn thy tp ca mình tr nên đông đúc vi các hàm, biến và lp trn ln vi nhau. Điu này làm tôi nh đến cách các k sư t chc mã ca nhim v Apollo phi to ra các h thng rõ ràng, d bo trì để nhiu nhóm có th làm vic cùng lúc.
**Nhiệm vụ của bạn:**
Hãy suy nghĩ như mt kiến trúc sư phn mm. Làm thế nào bn s t chc mã ca mình để sáu tháng sau, bn (hoc đồng đội) có th hiu được nhng gì đang din ra? Ngay c khi mi th vn nm trong mt tp, bn có th to t chc tt hơn:
- **Nhóm các hàm liên quan** vi các tiêu đề bình lun rõ ràng
- **Phân tách trách nhim** - gi logic trò chơi tách bit khi hin th
- **S dng quy ước đặt tên** nht quán cho biến và hàm
- **To module** hoc namespace để t chc các khía cnh khác nhau ca trò chơi
- **Thêm tài liu** gii thích mc đích ca mi phn chính
**Câu hỏi phản ánh:**
- Phn nào trong mã ca bn khó hiu nht khi bn quay li?
- Làm thế nào bn có th t chc mã để d dàng cho người khác đóng góp?
- Điu gì s xy ra nếu bn mun thêm các tính năng mi như vt phm tăng sc mnh hoc các loi k thù khác nhau?
## Bài kiểm tra sau bài giảng
[Bài kiểm tra sau bài giảng](https://ff-quizzes.netlify.app/web/quiz/34)
## Ôn tập & Tự học
Chúng ta đã xây dng mi th t đầu, điu này rt tuyt để hc, nhưng đây là mt bí mt nh có mt s framework JavaScript tuyt vi ngoài kia có th x lý rt nhiu công vic nng nhc cho bn. Khi bn cm thy thoi mái vi các nguyên tc cơ bn chúng ta đã đề cp, hãy [khám phá những gì có sẵn](https://github.com/collections/javascript-game-engines).
Hãy nghĩ v các framework như có mt hp công c đầy đủ thay vì t làm tng công cụ. Chúng có th gii quyết nhiu thách thc t chc mã mà chúng ta đã nói, cng vi cung cp các tính năng mà bn s mt hàng tun để t xây dng.
**Những điều đáng khám phá:**
- Cách các engine trò chơi t chc mã bn s ngc nhiên vi các mu thông minh mà chúng s dng
- Các mo hiu sut để làm cho trò chơi canvas chy mượt mà như bơ
- Các tính năng JavaScript hin đại có th làm cho mã ca bn sch hơn và d bo trì hơn
- Các cách tiếp cn khác nhau để qun lý các đối tượng trò chơi và mi quan h ca chúng
## 🎯 Lộ trình làm chủ hoạt ảnh trò chơi của bạn
```mermaid
timeline
title Game Animation & Interaction Learning Progression
section Movement Fundamentals (20 minutes)
Animation Principles: Frame-based animation
: Position updates
: Coordinate systems
: Smooth movement
section Event Systems (25 minutes)
User Input: Keyboard event handling
: Default behavior prevention
: Event object properties
: Window-level listening
section Game Architecture (30 minutes)
Object Design: Inheritance patterns
: Base class creation
: Specialized behaviors
: Polymorphic interfaces
section Communication Patterns (35 minutes)
Pub/Sub Implementation: Event emitters
: Message constants
: Loose coupling
: System integration
section Game Loop Mastery (40 minutes)
Real-time Systems: Frame rate control
: Update/render cycle
: State management
: Performance optimization
section Advanced Techniques (45 minutes)
Professional Features: Collision detection
: Physics simulation
: State machines
: Component systems
section Game Engine Concepts (1 week)
Framework Understanding: Entity-component systems
: Scene graphs
: Asset pipelines
: Performance profiling
section Production Skills (1 month)
Professional Development: Code organization
: Team collaboration
: Testing strategies
: Deployment optimization
```
### 🛠️ Tóm tắt bộ công cụ phát triển trò chơi của bạn
Sau khi hoàn thành bài hc này, bn đã làm ch:
- **Nguyên tc hot nh**: Chuyn động da trên khung hình và chuyn đổi mượt mà
- **Lp trình hướng s kin**: X lý đầu vào bàn phím vi qun lý s kin đúng cách
- **Thiết kế hướng đối tượng**: H thng kế tha và giao din đa hình
- **Mu giao tiếp**: Kiến trúc pub/sub để mã d bo trì
- **Kiến trúc vòng lp trò chơi**: Cp nht và hin th theo thi gian thc
- **H thng đầu vào**: Ánh x điu khin người dùng vi ngăn chn hành vi mc định
- **Qun lý tài nguyên**: Ti sprite và k thut hin th hiu qu
### ⚡ **Những gì bạn có thể làm trong 5 phút tới**
- [ ] M console trình duyt và th `addEventListener('keydown', console.log)` để xem các s kin bàn phím
- [ ] To mt phn t div đơn gin và di chuyn nó bng các phím mũi tên
- [ ] Th nghim vi `setInterval` để to chuyn động liên tc
- [ ] Th ngăn chn hành vi mc định vi `event.preventDefault()`
### 🎯 **Những gì bạn có thể hoàn thành trong giờ tới**
- [ ] Hoàn thành bài kim tra sau bài hc và hiu lp trình hướng s kin
- [ ] Xây dng tàu vũ tr nhân vt chính di chuyn vi đầy đủ điu khin bàn phím
- [ ] Trin khai các mu chuyn động mượt mà cho k thù
- [ ] Thêm gii hn để ngăn các đối tượng trò chơi ri khi màn hình
- [ ] To phát hin va chm cơ bn gia các đối tượng trò chơi
### 📅 **Hành trình hoạt ảnh của bạn trong tuần**
- [ ] Hoàn thành trò chơi không gian đầy đủ vi chuyn động và tương tác được tinh chnh
- [ ] Thêm các mu chuyn động nâng cao như đường cong, gia tc và vt lý
- [ ] Trin khai các chuyn đổi mượt mà và các hàm easing
- [ ] To hiu ng ht và h thng phn hi hình nh
- [ ] Ti ưu hóa hiu sut trò chơi để chơi mượt mà 60fps
- [ ] Thêm điu khin cm ng di động và thiết kế đáp ng
### 🌟 **Phát triển tương tác của bạn trong tháng**
- [ ] Xây dng các ng dng tương tác phc tp vi h thng hot nh nâng cao
- [ ] Hc các thư vin hot nh như GSAP hoc to engine hot nh ca riêng bn
- [ ] Đóng góp cho các d án phát trin trò chơi và hot nh mã ngun m
- [ ] Làm ch ti ưu hóa hiu sut cho các ng dng đồ ha chuyên sâu
- [ ] To ni dung giáo dc v phát trin trò chơi và hot nh
- [ ] Xây dng mt danh mc đầu tư th hin k năng lp trình tương tác nâng cao
**ng dng thc tế**: K năng hot nh trò chơi ca bn áp dng trc tiếp vào:
- **Ứng dng web tương tác**: Bng điu khin động và giao din thi gian thc
- **Trc quan hóa d liu**: Biu đồ hot nh và đồ ha tương tác
- **Phn mm giáo dc**: Mô phng tương tác và công c hc tp
- **Phát trin di động**: Trò chơi da trên cm ng và x lý c ch
- **Ứng dng máy tính để bàn**: ng dng Electron vi hot nh mượt mà
- **Hot nh web**: Thư vin CSS và JavaScript hot nh
**Kỹ năng chuyên nghiệp đạt được**: Bn có th:
- **Kiến trúc** h thng hướng s kin m rng vi độ phc tp
- **Trin khai** hot nh mượt mà bng cách s dng các nguyên tc toán hc
- **G li** h thng tương tác phc tp bng công c phát trin trình duyt
- **Ti ưu hóa** hiu sut trò chơi cho các thiết b và trình duyt khác nhau
- **Thiết kế** cu trúc mã d bo trì bng các mu đã được chng minh
**Khái niệm phát triển trò chơi đã làm chủ**:
- **Qun lý tc độ khung hình**: Hiu FPS và điu khin thi gian
- **X lý đầu vào**: H thng bàn phím và s kin đa nn tng
- **Vòng đời đối tượng**: Các mu to, cp nht và hy
- **Đồng b hóa trng thái**: Gi trng thái trò chơi nht quán qua các khung hình
- **Kiến trúc s kin**: Giao tiếp tách bit gia các h thng trò chơi
**Cấp độ tiếp theo**: Bn đã sn sàng để thêm phát hin va chm, h thng đim số, hiu ng âm thanh, hoc khám phá các framework trò chơi hin đại như Phaser hoc Three.js!
🌟 **Thành tựu mở khóa**: Bn đã xây dng mt h thng trò chơi tương tác hoàn chnh vi các mu kiến trúc chuyên nghip!
## Bài tập
[Comment mã của bạn](assignment.md)
---
**Tuyên bố miễn trừ trách nhiệm**:
Tài liu này đã được dch bng dch v dch thut AI [Co-op Translator](https://github.com/Azure/co-op-translator). Mc dù chúng tôi c gng đảm bo độ chính xác, xin lưu ý rng các bn dch t động có th cha li hoc không chính xác. Tài liu gc bng ngôn ng bn địa nên được coi là ngun thông tin chính thc. Đối vi thông tin quan trng, nên s dng dch v dch thut chuyên nghip bi con người. Chúng tôi không chu trách nhim cho bt k s hiu lm hoc din gii sai nào phát sinh t vic s dng bn dch này.