|
|
<!--
|
|
|
CO_OP_TRANSLATOR_METADATA:
|
|
|
{
|
|
|
"original_hash": "022bbb5c869091b98f19e408e0c51d5d",
|
|
|
"translation_date": "2025-10-23T21:17:20+00:00",
|
|
|
"source_file": "6-space-game/3-moving-elements-around/README.md",
|
|
|
"language_code": "th"
|
|
|
}
|
|
|
-->
|
|
|
# สร้างเกมอวกาศ ตอนที่ 3: เพิ่มการเคลื่อนไหว
|
|
|
|
|
|
ลองนึกถึงเกมโปรดของคุณ – สิ่งที่ทำให้เกมน่าดึงดูดไม่ใช่แค่กราฟิกที่สวยงาม แต่เป็นวิธีที่ทุกอย่างเคลื่อนไหวและตอบสนองต่อการกระทำของคุณ ตอนนี้เกมอวกาศของคุณเหมือนภาพวาดที่สวยงาม แต่เรากำลังจะเพิ่มการเคลื่อนไหวเพื่อทำให้มันมีชีวิตชีวา
|
|
|
|
|
|
เมื่อวิศวกรของ NASA เขียนโปรแกรมคอมพิวเตอร์นำทางสำหรับภารกิจ Apollo พวกเขาเผชิญกับความท้าทายที่คล้ายกัน: จะทำให้ยานอวกาศตอบสนองต่อการควบคุมของนักบินในขณะที่ยังคงรักษาการปรับเส้นทางอัตโนมัติได้อย่างไร หลักการที่เราจะเรียนรู้ในวันนี้สะท้อนแนวคิดเดียวกัน – การจัดการการเคลื่อนไหวที่ควบคุมโดยผู้เล่นควบคู่ไปกับพฤติกรรมของระบบอัตโนมัติ
|
|
|
|
|
|
ในบทเรียนนี้ คุณจะได้เรียนรู้วิธีทำให้ยานอวกาศลอยไปบนหน้าจอ ตอบสนองต่อคำสั่งของผู้เล่น และสร้างรูปแบบการเคลื่อนไหวที่ราบรื่น เราจะอธิบายทุกอย่างให้เข้าใจง่ายและเป็นขั้นตอน
|
|
|
|
|
|
เมื่อจบบทเรียนนี้ ผู้เล่นจะสามารถบังคับยานฮีโร่ของพวกเขาไปรอบ ๆ หน้าจอ ในขณะที่ยานศัตรูลาดตระเวนอยู่ด้านบน และที่สำคัญที่สุด คุณจะเข้าใจหลักการสำคัญที่ขับเคลื่อนระบบการเคลื่อนไหวในเกม
|
|
|
|
|
|
## แบบทดสอบก่อนเรียน
|
|
|
|
|
|
[แบบทดสอบก่อนเรียน](https://ff-quizzes.netlify.app/web/quiz/33)
|
|
|
|
|
|
## ทำความเข้าใจการเคลื่อนไหวในเกม
|
|
|
|
|
|
เกมจะมีชีวิตชีวาเมื่อสิ่งต่าง ๆ เริ่มเคลื่อนไหว และโดยพื้นฐานแล้วมีสองวิธีที่สิ่งนี้เกิดขึ้น:
|
|
|
|
|
|
- **การเคลื่อนไหวที่ควบคุมโดยผู้เล่น**: เมื่อคุณกดปุ่มหรือคลิกเมาส์ สิ่งต่าง ๆ จะเคลื่อนไหว นี่คือการเชื่อมต่อโดยตรงระหว่างคุณกับโลกของเกม
|
|
|
- **การเคลื่อนไหวอัตโนมัติ**: เมื่อเกมตัดสินใจเคลื่อนย้ายสิ่งต่าง ๆ เอง – เช่น ยานศัตรูที่ต้องลาดตระเวนบนหน้าจอไม่ว่าคุณจะทำอะไรหรือไม่ก็ตาม
|
|
|
|
|
|
การทำให้วัตถุเคลื่อนที่บนหน้าจอคอมพิวเตอร์นั้นง่ายกว่าที่คุณคิด จำพิกัด x และ y จากชั้นเรียนคณิตศาสตร์ได้ไหม? นั่นแหละที่เรากำลังทำงานด้วย เมื่อกาลิเลโอเฝ้าสังเกตดวงจันทร์ของดาวพฤหัสบดีในปี 1610 เขาก็ทำสิ่งเดียวกัน – วางตำแหน่งตามเวลาเพื่อทำความเข้าใจรูปแบบการเคลื่อนไหว
|
|
|
|
|
|
การเคลื่อนย้ายสิ่งต่าง ๆ บนหน้าจอเหมือนกับการสร้างภาพเคลื่อนไหวแบบพลิกหน้า – คุณต้องทำตามสามขั้นตอนง่าย ๆ นี้:
|
|
|
|
|
|
1. **อัปเดตตำแหน่ง** – เปลี่ยนตำแหน่งของวัตถุ (อาจจะเลื่อนไปทางขวา 5 พิกเซล)
|
|
|
2. **ลบเฟรมเก่า** – ล้างหน้าจอเพื่อไม่ให้เห็นร่องรอยที่หลงเหลือ
|
|
|
3. **วาดเฟรมใหม่** – วางวัตถุในตำแหน่งใหม่
|
|
|
|
|
|
ทำสิ่งนี้เร็วพอ แล้วบูม! คุณจะได้การเคลื่อนไหวที่ราบรื่นและรู้สึกเป็นธรรมชาติสำหรับผู้เล่น
|
|
|
|
|
|
นี่คือตัวอย่างในโค้ด:
|
|
|
|
|
|
```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);
|
|
|
```
|
|
|
|
|
|
**สิ่งที่โค้ดนี้ทำ:**
|
|
|
- **อัปเดต** พิกัด x ของฮีโร่โดย 5 พิกเซลเพื่อเคลื่อนที่ในแนวนอน
|
|
|
- **ล้าง** พื้นที่แคนวาสทั้งหมดเพื่อลบเฟรมก่อนหน้า
|
|
|
- **เติม** พื้นหลังของแคนวาสด้วยสีดำ
|
|
|
- **วาดใหม่** ภาพฮีโร่ในตำแหน่งใหม่
|
|
|
|
|
|
✅ คุณคิดว่าการวาดฮีโร่ใหม่หลายเฟรมต่อวินาทีอาจส่งผลต่อประสิทธิภาพได้อย่างไร? อ่านเพิ่มเติมเกี่ยวกับ [ทางเลือกสำหรับรูปแบบนี้](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas)
|
|
|
|
|
|
## จัดการเหตุการณ์คีย์บอร์ด
|
|
|
|
|
|
นี่คือจุดที่เราจะเชื่อมโยงการป้อนข้อมูลของผู้เล่นกับการกระทำในเกม เมื่อมีคนกด spacebar เพื่อยิงเลเซอร์หรือกดปุ่มลูกศรเพื่อหลบดาวเคราะห์น้อย เกมของคุณต้องตรวจจับและตอบสนองต่อการป้อนข้อมูลนั้น
|
|
|
|
|
|
เหตุการณ์คีย์บอร์ดเกิดขึ้นในระดับหน้าต่าง หมายความว่าหน้าต่างเบราว์เซอร์ทั้งหมดของคุณกำลังฟังการกดปุ่มเหล่านั้น ในทางกลับกัน การคลิกเมาส์สามารถผูกกับองค์ประกอบเฉพาะ (เช่น การคลิกปุ่ม) สำหรับเกมอวกาศของเรา เราจะเน้นที่การควบคุมด้วยคีย์บอร์ดเพราะนั่นคือสิ่งที่ให้ความรู้สึกเหมือนเกมอาร์เคดคลาสสิก
|
|
|
|
|
|
สิ่งนี้ทำให้ฉันนึกถึงวิธีที่ผู้ปฏิบัติการโทรเลขในศตวรรษที่ 19 ต้องแปลการป้อนข้อมูลรหัสมอร์สให้เป็นข้อความที่มีความหมาย – เรากำลังทำสิ่งที่คล้ายกัน โดยแปลงการกดปุ่มให้เป็นคำสั่งในเกม
|
|
|
|
|
|
ในการจัดการเหตุการณ์ คุณต้องใช้เมธอด `addEventListener()` ของหน้าต่างและให้พารามิเตอร์สองตัวเป็นอินพุต พารามิเตอร์แรกคือชื่อของเหตุการณ์ เช่น `keyup` พารามิเตอร์ที่สองคือฟังก์ชันที่จะถูกเรียกใช้เมื่อเหตุการณ์เกิดขึ้น
|
|
|
|
|
|
ตัวอย่าง:
|
|
|
|
|
|
```javascript
|
|
|
window.addEventListener('keyup', (evt) => {
|
|
|
// evt.key = string representation of the key
|
|
|
if (evt.key === 'ArrowUp') {
|
|
|
// do something
|
|
|
}
|
|
|
});
|
|
|
```
|
|
|
|
|
|
**การอธิบายสิ่งที่เกิดขึ้นในที่นี้:**
|
|
|
- **ฟัง** เหตุการณ์คีย์บอร์ดในหน้าต่างทั้งหมด
|
|
|
- **จับ** ออบเจ็กต์เหตุการณ์ที่มีข้อมูลเกี่ยวกับปุ่มที่ถูกกด
|
|
|
- **ตรวจสอบ** ว่าปุ่มที่กดตรงกับปุ่มเฉพาะหรือไม่ (ในกรณีนี้คือปุ่มลูกศรขึ้น)
|
|
|
- **ดำเนินการ** โค้ดเมื่อเงื่อนไขตรงกัน
|
|
|
|
|
|
สำหรับเหตุการณ์คีย์ มีสองคุณสมบัติในเหตุการณ์ที่คุณสามารถใช้เพื่อดูว่าปุ่มใดถูกกด:
|
|
|
|
|
|
- `key` - เป็นตัวแทนข้อความของปุ่มที่กด เช่น `'ArrowUp'`
|
|
|
- `keyCode` - เป็นตัวแทนตัวเลข เช่น `37` ซึ่งตรงกับ `ArrowLeft`
|
|
|
|
|
|
✅ การจัดการเหตุการณ์คีย์มีประโยชน์นอกเหนือจากการพัฒนาเกม คุณคิดว่าเทคนิคนี้สามารถนำไปใช้ในด้านอื่น ๆ ได้อย่างไร?
|
|
|
|
|
|
### ปุ่มพิเศษ: ข้อควรระวัง!
|
|
|
|
|
|
บางปุ่มมีพฤติกรรมในตัวเบราว์เซอร์ที่อาจรบกวนเกมของคุณ ปุ่มลูกศรเลื่อนหน้า และ spacebar กระโดดลง – พฤติกรรมที่คุณไม่ต้องการเมื่อมีคนพยายามควบคุมยานอวกาศของพวกเขา
|
|
|
|
|
|
เราสามารถป้องกันพฤติกรรมเริ่มต้นเหล่านี้และให้เกมของเราจัดการการป้อนข้อมูลแทน สิ่งนี้คล้ายกับวิธีที่โปรแกรมเมอร์คอมพิวเตอร์ยุคแรกต้องเขียนทับการขัดจังหวะของระบบเพื่อสร้างพฤติกรรมที่กำหนดเอง – เราแค่ทำในระดับเบราว์เซอร์ นี่คือวิธี:
|
|
|
|
|
|
```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);
|
|
|
```
|
|
|
|
|
|
**การทำความเข้าใจโค้ดป้องกันนี้:**
|
|
|
- **ตรวจสอบ** รหัสปุ่มเฉพาะที่อาจทำให้เกิดพฤติกรรมเบราว์เซอร์ที่ไม่ต้องการ
|
|
|
- **ป้องกัน** การกระทำเริ่มต้นของเบราว์เซอร์สำหรับปุ่มลูกศรและ spacebar
|
|
|
- **อนุญาต** ให้ปุ่มอื่นทำงานตามปกติ
|
|
|
- **ใช้** `e.preventDefault()` เพื่อหยุดพฤติกรรมในตัวของเบราว์เซอร์
|
|
|
|
|
|
## การเคลื่อนไหวที่เกิดจากเกม
|
|
|
|
|
|
ตอนนี้เรามาพูดถึงวัตถุที่เคลื่อนไหวโดยไม่มีการป้อนข้อมูลจากผู้เล่น ลองนึกถึงยานศัตรูที่ล่องลอยบนหน้าจอ กระสุนที่บินเป็นเส้นตรง หรือเมฆที่ลอยอยู่ในพื้นหลัง การเคลื่อนไหวอัตโนมัตินี้ทำให้โลกของเกมของคุณรู้สึกมีชีวิตชีวาแม้ไม่มีใครแตะต้องการควบคุม
|
|
|
|
|
|
เราใช้ตัวจับเวลาที่มีอยู่ใน JavaScript เพื่ออัปเดตตำแหน่งในช่วงเวลาปกติ แนวคิดนี้คล้ายกับวิธีการทำงานของนาฬิกาลูกตุ้ม – กลไกปกติที่กระตุ้นการกระทำที่สม่ำเสมอและตรงเวลา นี่คือวิธีที่ง่าย:
|
|
|
|
|
|
```javascript
|
|
|
const id = setInterval(() => {
|
|
|
// Move the enemy on the y axis
|
|
|
enemy.y += 10;
|
|
|
}, 100);
|
|
|
```
|
|
|
|
|
|
**สิ่งที่โค้ดการเคลื่อนไหวนี้ทำ:**
|
|
|
- **สร้าง** ตัวจับเวลาที่ทำงานทุก ๆ 100 มิลลิวินาที
|
|
|
- **อัปเดต** พิกัด y ของศัตรูโดย 10 พิกเซลทุกครั้ง
|
|
|
- **เก็บ** ID ของช่วงเวลาเพื่อหยุดมันในภายหลังหากจำเป็น
|
|
|
- **เคลื่อนย้าย** ศัตรูลงบนหน้าจอโดยอัตโนมัติ
|
|
|
|
|
|
## วงลูปของเกม
|
|
|
|
|
|
นี่คือแนวคิดที่ผูกทุกอย่างเข้าด้วยกัน – วงลูปของเกม หากเกมของคุณเป็นภาพยนตร์ วงลูปของเกมจะเป็นเครื่องฉายภาพยนตร์ที่แสดงเฟรมต่อเฟรมอย่างรวดเร็วจนทุกอย่างดูเหมือนเคลื่อนไหวอย่างราบรื่น
|
|
|
|
|
|
ทุกเกมมีวงลูปเหล่านี้ทำงานอยู่เบื้องหลัง เป็นฟังก์ชันที่อัปเดตวัตถุเกมทั้งหมด วาดหน้าจอใหม่ และทำซ้ำกระบวนการนี้อย่างต่อเนื่อง สิ่งนี้ช่วยติดตามฮีโร่ของคุณ ศัตรูทั้งหมด เลเซอร์ที่บินไปรอบ ๆ – สถานะของเกมทั้งหมด
|
|
|
|
|
|
แนวคิดนี้ทำให้ฉันนึกถึงวิธีที่นักวาดภาพยนตร์ยุคแรก ๆ เช่น Walt Disney ต้องวาดตัวละครใหม่เฟรมต่อเฟรมเพื่อสร้างภาพลวงตาของการเคลื่อนไหว เรากำลังทำสิ่งเดียวกัน เพียงแค่ใช้โค้ดแทนดินสอ
|
|
|
|
|
|
นี่คือสิ่งที่วงลูปของเกมมักจะมีลักษณะเป็นโค้ด:
|
|
|
|
|
|
```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);
|
|
|
```
|
|
|
|
|
|
**การทำความเข้าใจโครงสร้างวงลูปของเกม:**
|
|
|
- **ล้าง** แคนวาสทั้งหมดเพื่อลบเฟรมก่อนหน้า
|
|
|
- **เติม** พื้นหลังด้วยสีทึบ
|
|
|
- **วาด** วัตถุเกมทั้งหมดในตำแหน่งปัจจุบันของพวกมัน
|
|
|
- **ทำซ้ำ** กระบวนการนี้ทุก ๆ 200 มิลลิวินาทีเพื่อสร้างภาพเคลื่อนไหวที่ราบรื่น
|
|
|
- **จัดการ** อัตราเฟรมโดยการควบคุมช่วงเวลาเวลา
|
|
|
|
|
|
## การพัฒนาเกมอวกาศต่อไป
|
|
|
|
|
|
ตอนนี้เราจะเพิ่มการเคลื่อนไหวให้กับฉากนิ่งที่คุณสร้างไว้ก่อนหน้านี้ เรากำลังจะเปลี่ยนมันจากภาพหน้าจอเป็นประสบการณ์แบบโต้ตอบ เราจะทำงานผ่านขั้นตอนนี้ทีละขั้นตอนเพื่อให้แน่ใจว่าแต่ละส่วนสร้างขึ้นจากส่วนก่อนหน้า
|
|
|
|
|
|
ดึงโค้ดจากที่ที่เราหยุดในบทเรียนก่อนหน้า (หรือเริ่มต้นด้วยโค้ดในโฟลเดอร์ [Part II- starter](../../../../6-space-game/3-moving-elements-around/your-work) หากคุณต้องการเริ่มต้นใหม่)
|
|
|
|
|
|
**นี่คือสิ่งที่เรากำลังสร้างวันนี้:**
|
|
|
- **การควบคุมฮีโร่**: ใช้ปุ่มลูกศรเพื่อบังคับยานอวกาศของคุณบนหน้าจอ
|
|
|
- **การเคลื่อนไหวของศัตรู**: ยานเอเลี่ยนเหล่านั้นจะเริ่มการโจมตี
|
|
|
|
|
|
มาเริ่มต้นการเพิ่มฟีเจอร์เหล่านี้กันเถอะ
|
|
|
|
|
|
## ขั้นตอนที่แนะนำ
|
|
|
|
|
|
ค้นหาไฟล์ที่ถูกสร้างไว้ให้คุณในโฟลเดอร์ `your-work` มันควรมีสิ่งต่อไปนี้:
|
|
|
|
|
|
```bash
|
|
|
-| assets
|
|
|
-| enemyShip.png
|
|
|
-| player.png
|
|
|
-| index.html
|
|
|
-| app.js
|
|
|
-| package.json
|
|
|
```
|
|
|
|
|
|
เริ่มโปรเจกต์ของคุณในโฟลเดอร์ `your-work` โดยพิมพ์:
|
|
|
|
|
|
```bash
|
|
|
cd your-work
|
|
|
npm start
|
|
|
```
|
|
|
|
|
|
**สิ่งที่คำสั่งนี้ทำ:**
|
|
|
- **นำทาง** ไปยังไดเรกทอรีโปรเจกต์ของคุณ
|
|
|
- **เริ่มต้น** HTTP Server ที่อยู่ `http://localhost:5000`
|
|
|
- **ให้บริการ** ไฟล์เกมของคุณเพื่อทดสอบในเบราว์เซอร์
|
|
|
|
|
|
คำสั่งด้านบนจะเริ่ม HTTP Server ที่อยู่ `http://localhost:5000` เปิดเบราว์เซอร์และใส่ที่อยู่นั้น ตอนนี้มันควรจะแสดงฮีโร่และศัตรูทั้งหมด; แต่ยังไม่มีอะไรเคลื่อนไหว – ยัง!
|
|
|
|
|
|
### เพิ่มโค้ด
|
|
|
|
|
|
1. **เพิ่มออบเจ็กต์เฉพาะ** สำหรับ `hero` และ `enemy` และ `game object` ซึ่งควรมีคุณสมบัติ `x` และ `y` (จำส่วนเกี่ยวกับ [Inheritance หรือ composition](../README.md))
|
|
|
|
|
|
*คำแนะนำ* `game object` ควรเป็นออบเจ็กต์ที่มี `x` และ `y` และความสามารถในการวาดตัวเองลงบนแคนวาส
|
|
|
|
|
|
> **เคล็ดลับ**: เริ่มต้นด้วยการเพิ่มคลาส `GameObject` ใหม่พร้อมตัวสร้างที่กำหนดไว้ดังนี้ และวาดมันลงบนแคนวาส:
|
|
|
|
|
|
```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);
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**การทำความเข้าใจคลาสพื้นฐานนี้:**
|
|
|
- **กำหนด** คุณสมบัติทั่วไปที่วัตถุเกมทั้งหมดมีร่วมกัน (ตำแหน่ง ขนาด ภาพ)
|
|
|
- **รวมถึง** ธง `dead` เพื่อติดตามว่าวัตถุควรถูกลบหรือไม่
|
|
|
- **ให้** เมธอด `draw()` ที่แสดงวัตถุบนแคนวาส
|
|
|
- **ตั้งค่า** ค่าเริ่มต้นสำหรับคุณสมบัติทั้งหมดที่คลาสลูกสามารถเขียนทับได้
|
|
|
|
|
|
ตอนนี้ ขยาย `GameObject` เพื่อสร้าง `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);
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**แนวคิดสำคัญในคลาสเหล่านี้:**
|
|
|
- **สืบทอด** จาก `GameObject` โดยใช้คำสำคัญ `extends`
|
|
|
- **เรียก** ตัวสร้างของคลาสแม่ด้วย `super(x, y)`
|
|
|
- **ตั้งค่า** ขนาดและคุณสมบัติเฉพาะสำหรับแต่ละประเภทของวัตถุ
|
|
|
- **นำไปใช้** การเคลื่อนไหวอัตโนมัติสำหรับศัตรูโดยใช้ `setInterval()`
|
|
|
|
|
|
2. **เพิ่มตัวจัดการเหตุการณ์คีย์** เพื่อจัดการการนำทางด้วยคีย์ (เคลื่อนย้ายฮีโร่ขึ้น/ลง ซ้าย/ขวา)
|
|
|
|
|
|
*จำไว้* ว่ามันเป็นระบบคาร์ทีเซียน มุมบนซ้ายคือ `0,0` และอย่าลืมเพิ่มโค้ดเพื่อหยุด *พฤติกรรมเริ่มต้น*
|
|
|
|
|
|
> **เคล็ดลับ**: สร้างฟังก์ชัน `onKeyDown` ของคุณและผูกมันกับหน้าต่าง:
|
|
|
|
|
|
```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);
|
|
|
```
|
|
|
|
|
|
**สิ่งที่ตัวจัดการเหตุการณ์นี้ทำ:**
|
|
|
- **ฟัง** เหตุการณ์ keydown ในหน้าต่างทั้งหมด
|
|
|
- **บันทึก** รหัสคีย์เพื่อช่วยคุณดีบักว่าปุ่มใดถูกกด
|
|
|
- **ป้องกัน** พฤติกรรมเริ่มต้นของเบราว์เซอร์สำหรับปุ่มลูกศรและ spacebar
|
|
|
- **อนุญาต** ให้ปุ่มอื่นทำงานตามปกติ
|
|
|
|
|
|
ตรวจสอบคอนโซลเบราว์เซอร์ของคุณ ณ จุดนี้ และดูการกดปุ่มที่ถูกบันทึกไว้
|
|
|
|
|
|
3. **นำไปใช้** [Pub sub pattern](../README.md) สิ่งนี้จะช่วยให้โค้ดของคุณสะอาดเมื่อคุณทำตามส่วนที่เหลือ
|
|
|
|
|
|
รูปแบบ Publish-Subscribe ช่วยจัดระเบียบโค้ดของคุณโดยแยกการตรวจจับเหตุการณ์ออกจากการจัดการเหตุการณ์ สิ่งนี้ทำให้โค้ดของคุณมีความเป็นโมดูลและดูแลรักษาได้ง่ายขึ้น
|
|
|
|
|
|
ในการทำส่วนสุดท้ายนี้ คุณสามารถ:
|
|
|
|
|
|
1. **เพิ่มตัวฟังเหตุการณ์** ในหน้าต่าง:
|
|
|
|
|
|
```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);
|
|
|
}
|
|
|
});
|
|
|
```
|
|
|
|
|
|
**สิ่งที่ระบบเหตุการณ์นี้ทำ:**
|
|
|
- **ตรวจจับ** การป้อนข้อมูลคีย์บอร์ดและแปลงเป็นเหตุการณ์เกมที่กำหนดเอง
|
|
|
- **แยก** การตรวจจับการป้อนข้อมูลออกจากตรรกะของเกม
|
|
|
- **ทำให้** การเปลี่ยนแปลงการควบคุมในภายหลังง่ายขึ้นโดยไม่กระทบต่อโค้ดเกม
|
|
|
- **อนุญาต** ให้ระบบหลายระบบ
|
|
|
- **สร้าง** ตารางของศัตรูโดยใช้ลูปซ้อนกัน
|
|
|
- **กำหนด** ภาพศัตรูให้กับวัตถุศัตรูแต่ละตัว
|
|
|
- **เพิ่ม** ศัตรูแต่ละตัวลงในอาร์เรย์วัตถุเกมแบบ global
|
|
|
|
|
|
และเพิ่มฟังก์ชัน `createHero()` เพื่อทำกระบวนการคล้ายกันสำหรับฮีโร่
|
|
|
|
|
|
```javascript
|
|
|
function createHero() {
|
|
|
hero = new Hero(
|
|
|
canvas.width / 2 - 45,
|
|
|
canvas.height - canvas.height / 4
|
|
|
);
|
|
|
hero.img = heroImg;
|
|
|
gameObjects.push(hero);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**สิ่งที่การสร้างฮีโร่ทำ:**
|
|
|
- **กำหนดตำแหน่ง** ฮีโร่ให้อยู่ตรงกลางด้านล่างของหน้าจอ
|
|
|
- **กำหนด** ภาพฮีโร่ให้กับวัตถุฮีโร่
|
|
|
- **เพิ่ม** ฮีโร่ลงในอาร์เรย์วัตถุเกมเพื่อการแสดงผล
|
|
|
|
|
|
และสุดท้าย เพิ่มฟังก์ชัน `drawGameObjects()` เพื่อเริ่มการวาด
|
|
|
|
|
|
```javascript
|
|
|
function drawGameObjects(ctx) {
|
|
|
gameObjects.forEach(go => go.draw(ctx));
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**ทำความเข้าใจฟังก์ชันการวาด:**
|
|
|
- **วนลูป** ผ่านวัตถุเกมทั้งหมดในอาร์เรย์
|
|
|
- **เรียกใช้** เมธอด `draw()` บนวัตถุแต่ละตัว
|
|
|
- **ส่งผ่าน** context ของ canvas เพื่อให้วัตถุสามารถแสดงผลตัวเองได้
|
|
|
|
|
|
ศัตรูของคุณควรเริ่มเคลื่อนที่เข้าหายานอวกาศฮีโร่ของคุณ!
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
และสุดท้าย เพิ่มฟังก์ชัน `drawGameObjects()` เพื่อเริ่มการวาด
|
|
|
|
|
|
```javascript
|
|
|
function drawGameObjects(ctx) {
|
|
|
gameObjects.forEach(go => go.draw(ctx));
|
|
|
}
|
|
|
```
|
|
|
|
|
|
ศัตรูของคุณควรเริ่มเคลื่อนที่เข้าหายานอวกาศฮีโร่ของคุณ!
|
|
|
|
|
|
---
|
|
|
|
|
|
## GitHub Copilot Agent Challenge 🚀
|
|
|
|
|
|
นี่คือความท้าทายที่จะช่วยปรับปรุงความสมบูรณ์ของเกมของคุณ: การเพิ่มขอบเขตและการควบคุมที่ลื่นไหล ปัจจุบันฮีโร่ของคุณสามารถบินออกนอกหน้าจอได้ และการเคลื่อนไหวอาจดูไม่ราบรื่น
|
|
|
|
|
|
**ภารกิจของคุณ:** ทำให้ยานอวกาศของคุณรู้สึกสมจริงมากขึ้นโดยการเพิ่มขอบเขตหน้าจอและการควบคุมที่ลื่นไหล นี่คล้ายกับระบบควบคุมการบินของ NASA ที่ป้องกันไม่ให้ยานอวกาศเกินพารามิเตอร์การปฏิบัติการที่ปลอดภัย
|
|
|
|
|
|
**สิ่งที่ต้องสร้าง:** สร้างระบบที่ทำให้ยานอวกาศฮีโร่ของคุณอยู่ในหน้าจอ และทำให้การควบคุมรู้สึกลื่นไหล เมื่อผู้เล่นกดปุ่มลูกศรค้างไว้ ยานควรลื่นไหลต่อเนื่องแทนที่จะเคลื่อนที่เป็นขั้นๆ ลองพิจารณาเพิ่มการตอบสนองภาพเมื่อยานถึงขอบหน้าจอ – อาจเป็นเอฟเฟกต์เล็กๆ เพื่อบ่งบอกถึงขอบพื้นที่เล่น
|
|
|
|
|
|
เรียนรู้เพิ่มเติมเกี่ยวกับ [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) ที่นี่
|
|
|
|
|
|
## 🚀 Challenge
|
|
|
|
|
|
การจัดระเบียบโค้ดมีความสำคัญมากขึ้นเมื่อโปรเจกต์เติบโต คุณอาจสังเกตเห็นว่าไฟล์ของคุณเริ่มเต็มไปด้วยฟังก์ชัน ตัวแปร และคลาสที่ปะปนกัน นี่ทำให้นึกถึงวิศวกรที่จัดระเบียบโค้ดของภารกิจ Apollo ที่ต้องสร้างระบบที่ชัดเจนและดูแลรักษาได้ ซึ่งทีมหลายทีมสามารถทำงานร่วมกันได้
|
|
|
|
|
|
**ภารกิจของคุณ:**
|
|
|
คิดแบบสถาปนิกซอฟต์แวร์ คุณจะจัดระเบียบโค้ดของคุณอย่างไรเพื่อให้หกเดือนจากนี้ คุณ (หรือเพื่อนร่วมทีม) สามารถเข้าใจสิ่งที่เกิดขึ้นได้? แม้ว่าทุกอย่างจะยังอยู่ในไฟล์เดียวในตอนนี้ คุณสามารถสร้างการจัดระเบียบที่ดีขึ้นได้:
|
|
|
|
|
|
- **จัดกลุ่มฟังก์ชันที่เกี่ยวข้อง** พร้อมหัวข้อคอมเมนต์ที่ชัดเจน
|
|
|
- **แยกความรับผิดชอบ** - แยกตรรกะของเกมออกจากการแสดงผล
|
|
|
- **ใช้ชื่อที่สอดคล้องกัน** สำหรับตัวแปรและฟังก์ชัน
|
|
|
- **สร้างโมดูล** หรือ namespace เพื่อจัดระเบียบส่วนต่างๆ ของเกม
|
|
|
- **เพิ่มเอกสารประกอบ** ที่อธิบายวัตถุประสงค์ของแต่ละส่วนสำคัญ
|
|
|
|
|
|
**คำถามสะท้อน:**
|
|
|
- ส่วนใดของโค้ดของคุณที่เข้าใจยากที่สุดเมื่อคุณกลับมาดู?
|
|
|
- คุณจะจัดระเบียบโค้ดอย่างไรเพื่อให้ง่ายขึ้นสำหรับคนอื่นที่จะมีส่วนร่วม?
|
|
|
- จะเกิดอะไรขึ้นถ้าคุณต้องการเพิ่มฟีเจอร์ใหม่ เช่น power-ups หรือประเภทศัตรูที่แตกต่างกัน?
|
|
|
|
|
|
## Post-Lecture Quiz
|
|
|
|
|
|
[Post-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/34)
|
|
|
|
|
|
## Review & Self Study
|
|
|
|
|
|
เรากำลังสร้างทุกอย่างตั้งแต่เริ่มต้น ซึ่งยอดเยี่ยมสำหรับการเรียนรู้ แต่มีความลับเล็กๆ น้อยๆ – มีเฟรมเวิร์ก JavaScript ที่น่าทึ่งบางตัวที่สามารถจัดการงานหนักให้คุณได้ เมื่อคุณรู้สึกสบายใจกับพื้นฐานที่เราได้ครอบคลุมแล้ว คุ้มค่าที่จะ [สำรวจสิ่งที่มีอยู่](https://github.com/collections/javascript-game-engines)
|
|
|
|
|
|
คิดถึงเฟรมเวิร์กเหมือนมีชุดเครื่องมือที่ครบครันแทนที่จะสร้างเครื่องมือทุกชิ้นด้วยตัวเอง พวกมันสามารถแก้ปัญหาความท้าทายในการจัดระเบียบโค้ดที่เราพูดถึงได้มากมาย และยังมีฟีเจอร์ที่อาจใช้เวลาหลายสัปดาห์ในการสร้างเอง
|
|
|
|
|
|
**สิ่งที่ควรสำรวจ:**
|
|
|
- วิธีที่ game engines จัดระเบียบโค้ด – คุณจะทึ่งกับรูปแบบที่ชาญฉลาดที่พวกเขาใช้
|
|
|
- เทคนิคการเพิ่มประสิทธิภาพเพื่อทำให้เกม canvas ทำงานได้อย่างราบรื่น
|
|
|
- ฟีเจอร์ JavaScript สมัยใหม่ที่สามารถทำให้โค้ดของคุณสะอาดและดูแลรักษาได้ง่ายขึ้น
|
|
|
- วิธีการต่างๆ ในการจัดการวัตถุเกมและความสัมพันธ์ของพวกมัน
|
|
|
|
|
|
## Assignment
|
|
|
|
|
|
[Comment your code](assignment.md)
|
|
|
|
|
|
---
|
|
|
|
|
|
**ข้อจำกัดความรับผิดชอบ**:
|
|
|
เอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI [Co-op Translator](https://github.com/Azure/co-op-translator) แม้ว่าเราจะพยายามให้การแปลมีความถูกต้อง แต่โปรดทราบว่าการแปลโดยอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ ขอแนะนำให้ใช้บริการแปลภาษามืออาชีพ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความผิดที่เกิดจากการใช้การแปลนี้ |