# สร้างเกมอวกาศ ตอนที่ 4: เพิ่มเลเซอร์และตรวจจับการชนกัน ## แบบทดสอบก่อนเรียน [แบบทดสอบก่อนเรียน](https://ff-quizzes.netlify.app/web/quiz/35) ลองนึกถึงช่วงเวลาที่ลุคใช้ตอร์ปิโดโปรตอนยิงเข้าช่องไอเสียของดาวมรณะใน Star Wars การตรวจจับการชนกันที่แม่นยำนี้เปลี่ยนชะตากรรมของกาแล็กซี่! ในเกม การตรวจจับการชนกันทำงานในลักษณะเดียวกัน - มันจะกำหนดว่าเมื่อใดที่วัตถุมีปฏิสัมพันธ์และจะเกิดอะไรขึ้นต่อไป ในบทเรียนนี้ คุณจะเพิ่มอาวุธเลเซอร์ในเกมอวกาศของคุณและนำการตรวจจับการชนกันมาใช้ เช่นเดียวกับที่นักวางแผนภารกิจของ NASA คำนวณวิถีของยานอวกาศเพื่อหลีกเลี่ยงเศษซาก คุณจะได้เรียนรู้วิธีตรวจจับเมื่อวัตถุในเกมตัดกัน เราจะทำให้มันง่ายขึ้นด้วยการแบ่งเป็นขั้นตอนที่จัดการได้ เมื่อจบบทเรียนนี้ คุณจะมีระบบการต่อสู้ที่สมบูรณ์ซึ่งเลเซอร์สามารถทำลายศัตรูได้ และการชนกันจะกระตุ้นเหตุการณ์ในเกม หลักการตรวจจับการชนกันเหล่านี้ถูกใช้ในทุกอย่างตั้งแต่การจำลองทางฟิสิกส์ไปจนถึงอินเทอร์เฟซเว็บแบบโต้ตอบ ✅ ลองค้นคว้าเกี่ยวกับเกมคอมพิวเตอร์เกมแรกที่เคยเขียนขึ้น มันมีฟังก์ชันอะไรบ้าง? ## การตรวจจับการชนกัน การตรวจจับการชนกันทำงานเหมือนเซ็นเซอร์ตรวจจับระยะใกล้บนโมดูลดวงจันทร์ของ Apollo - มันจะตรวจสอบระยะห่างอย่างต่อเนื่องและส่งสัญญาณเตือนเมื่อวัตถุเข้าใกล้กันมากเกินไป ในเกม ระบบนี้จะกำหนดว่าเมื่อใดที่วัตถุมีปฏิสัมพันธ์และควรเกิดอะไรขึ้นต่อไป วิธีที่เราจะใช้คือการมองว่าวัตถุในเกมทุกชิ้นเป็นรูปสี่เหลี่ยมผืนผ้า คล้ายกับที่ระบบควบคุมการจราจรทางอากาศใช้รูปทรงเรขาคณิตที่เรียบง่ายเพื่อติดตามเครื่องบิน วิธีการแบบสี่เหลี่ยมนี้อาจดูพื้นฐาน แต่มีประสิทธิภาพในการคำนวณและทำงานได้ดีสำหรับสถานการณ์ในเกมส่วนใหญ่ ### การแทนค่ารูปสี่เหลี่ยมผืนผ้า วัตถุในเกมทุกชิ้นต้องมีขอบเขตพิกัด คล้ายกับที่รถสำรวจ Mars Pathfinder กำหนดตำแหน่งของมันบนพื้นผิวดาวอังคาร นี่คือวิธีที่เรากำหนดพิกัดขอบเขตเหล่านี้: ```javascript rectFromGameObject() { return { top: this.y, left: this.x, bottom: this.y + this.height, right: this.x + this.width } } ``` **มาดูรายละเอียดกัน:** - **ขอบบน**: คือจุดเริ่มต้นในแนวตั้งของวัตถุ (ตำแหน่ง y) - **ขอบซ้าย**: คือจุดเริ่มต้นในแนวนอนของวัตถุ (ตำแหน่ง x) - **ขอบล่าง**: เพิ่มความสูงเข้าไปในตำแหน่ง y - ตอนนี้คุณรู้แล้วว่ามันสิ้นสุดที่ไหน! - **ขอบขวา**: เพิ่มความกว้างเข้าไปในตำแหน่ง x - และคุณได้ขอบเขตทั้งหมดแล้ว ### อัลกอริทึมการตัดกัน การตรวจจับการตัดกันของรูปสี่เหลี่ยมผืนผ้าใช้ตรรกะคล้ายกับที่กล้องโทรทรรศน์อวกาศฮับเบิลใช้ในการกำหนดว่าวัตถุท้องฟ้าทับซ้อนกันในมุมมองของมันหรือไม่ อัลกอริทึมจะตรวจสอบการแยก: ```javascript function intersectRect(r1, r2) { return !(r2.left > r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top); } ``` **การทดสอบการแยกทำงานเหมือนระบบเรดาร์:** - รูปสี่เหลี่ยมผืนผ้า 2 อยู่ทางขวาสุดของรูปสี่เหลี่ยมผืนผ้า 1 หรือไม่? - รูปสี่เหลี่ยมผืนผ้า 2 อยู่ทางซ้ายสุดของรูปสี่เหลี่ยมผืนผ้า 1 หรือไม่? - รูปสี่เหลี่ยมผืนผ้า 2 อยู่ด้านล่างสุดของรูปสี่เหลี่ยมผืนผ้า 1 หรือไม่? - รูปสี่เหลี่ยมผืนผ้า 2 อยู่ด้านบนสุดของรูปสี่เหลี่ยมผืนผ้า 1 หรือไม่? หากไม่มีเงื่อนไขเหล่านี้เป็นจริง รูปสี่เหลี่ยมผืนผ้าจะต้องทับซ้อนกัน วิธีนี้คล้ายกับที่ผู้ควบคุมเรดาร์กำหนดว่าเครื่องบินสองลำอยู่ในระยะปลอดภัยหรือไม่ ## การจัดการวงจรชีวิตของวัตถุ เมื่อเลเซอร์ชนศัตรู วัตถุทั้งสองต้องถูกลบออกจากเกม อย่างไรก็ตาม การลบวัตถุระหว่างลูปอาจทำให้เกิดการล่ม - บทเรียนที่ได้เรียนรู้จากระบบคอมพิวเตอร์ยุคแรก ๆ เช่น Apollo Guidance Computer ดังนั้นเราจึงใช้วิธี "ทำเครื่องหมายเพื่อลบ" ซึ่งจะลบวัตถุอย่างปลอดภัยระหว่างเฟรม นี่คือวิธีที่เราทำเครื่องหมายบางสิ่งเพื่อการลบ: ```javascript // Mark object for removal enemy.dead = true; ``` **ทำไมวิธีนี้ถึงได้ผล:** - เราทำเครื่องหมายวัตถุว่า "ตาย" แต่ยังไม่ลบมันทันที - สิ่งนี้ช่วยให้เฟรมเกมปัจจุบันเสร็จสิ้นอย่างปลอดภัย - ไม่มีการล่มจากการพยายามใช้สิ่งที่ถูกลบไปแล้ว! จากนั้นกรองวัตถุที่ถูกทำเครื่องหมายออกก่อนรอบการแสดงผลครั้งต่อไป: ```javascript gameObjects = gameObjects.filter(go => !go.dead); ``` **สิ่งที่การกรองนี้ทำ:** - สร้างรายการใหม่ที่มีเฉพาะวัตถุที่ "ยังมีชีวิตอยู่" - ทิ้งสิ่งที่ถูกทำเครื่องหมายว่าตายไปแล้ว - ทำให้เกมของคุณทำงานได้อย่างราบรื่น - ป้องกันการสะสมของวัตถุที่ถูกทำลายซึ่งทำให้หน่วยความจำบวม ## การนำกลไกเลเซอร์มาใช้ โปรเจกไทล์เลเซอร์ในเกมทำงานบนหลักการเดียวกับตอร์ปิโดโฟตอนใน Star Trek - มันเป็นวัตถุที่แยกออกมาและเคลื่อนที่เป็นเส้นตรงจนกว่าจะชนบางสิ่ง การกดแป้นเว้นวรรคแต่ละครั้งจะสร้างวัตถุเลเซอร์ใหม่ที่เคลื่อนที่ข้ามหน้าจอ เพื่อให้สิ่งนี้ทำงาน เราต้องประสานชิ้นส่วนต่าง ๆ: **ส่วนสำคัญที่ต้องนำมาใช้:** - **สร้าง** วัตถุเลเซอร์ที่เกิดจากตำแหน่งของฮีโร่ - **จัดการ** การป้อนข้อมูลจากแป้นพิมพ์เพื่อกระตุ้นการสร้างเลเซอร์ - **จัดการ** การเคลื่อนที่และวงจรชีวิตของเลเซอร์ - **นำเสนอ** การแสดงผลภาพสำหรับโปรเจกไทล์เลเซอร์ ## การควบคุมอัตราการยิง อัตราการยิงที่ไม่จำกัดจะทำให้เครื่องยนต์เกมทำงานหนักเกินไปและทำให้การเล่นเกมง่ายเกินไป ระบบอาวุธจริงก็เผชิญกับข้อจำกัดที่คล้ายกัน - แม้แต่ปืนเฟเซอร์ของ USS Enterprise ก็ต้องใช้เวลาชาร์จระหว่างการยิง เราจะนำระบบคูลดาวน์มาใช้เพื่อป้องกันการยิงแบบรัว ๆ ในขณะที่ยังคงควบคุมได้อย่างตอบสนอง: ```javascript 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) { // Create laser projectile this.cooldown = new Cooldown(500); } else { // Weapon is still cooling down } } } ``` **วิธีการทำงานของคูลดาวน์:** - เมื่อสร้างขึ้น อาวุธจะเริ่มต้นเป็น "ร้อน" (ยังยิงไม่ได้) - หลังจากช่วงเวลาที่กำหนด มันจะกลายเป็น "เย็น" (พร้อมยิง) - ก่อนยิง เราจะตรวจสอบว่า "อาวุธเย็นหรือยัง?" - สิ่งนี้ป้องกันการคลิกแบบรัว ๆ ในขณะที่ยังคงควบคุมได้อย่างตอบสนอง ✅ อ้างอิงบทเรียนที่ 1 ในซีรีส์เกมอวกาศเพื่อเตือนตัวเองเกี่ยวกับคูลดาวน์ ## การสร้างระบบตรวจจับการชนกัน คุณจะขยายโค้ดเกมอวกาศที่มีอยู่ของคุณเพื่อสร้างระบบตรวจจับการชนกัน เช่นเดียวกับระบบหลีกเลี่ยงการชนกันอัตโนมัติของสถานีอวกาศนานาชาติ เกมของคุณจะตรวจสอบตำแหน่งของวัตถุอย่างต่อเนื่องและตอบสนองต่อการตัดกัน เริ่มต้นจากโค้ดบทเรียนก่อนหน้านี้ของคุณ คุณจะเพิ่มการตรวจจับการชนกันพร้อมกฎเฉพาะที่ควบคุมการมีปฏิสัมพันธ์ของวัตถุ > 💡 **เคล็ดลับ**: สไปรต์เลเซอร์มีอยู่แล้วในโฟลเดอร์ทรัพย์สินของคุณและอ้างอิงในโค้ดของคุณ พร้อมสำหรับการนำไปใช้ ### กฎการชนกันที่ต้องนำมาใช้ **กลไกเกมที่ต้องเพิ่ม:** 1. **เลเซอร์ชนศัตรู**: วัตถุศัตรูจะถูกทำลายเมื่อถูกโปรเจกไทล์เลเซอร์ 2. **เลเซอร์ชนขอบหน้าจอ**: เลเซอร์จะถูกลบเมื่อถึงขอบบนของหน้าจอ 3. **ศัตรูชนฮีโร่**: วัตถุทั้งสองจะถูกทำลายเมื่อชนกัน 4. **ศัตรูถึงด้านล่าง**: เงื่อนไขเกมโอเวอร์เมื่อศัตรูถึงด้านล่างของหน้าจอ ## การตั้งค่าสภาพแวดล้อมการพัฒนา ข่าวดี - เราได้ตั้งค่าพื้นฐานส่วนใหญ่ให้คุณแล้ว! ทรัพยากรเกมและโครงสร้างพื้นฐานทั้งหมดของคุณรออยู่ในโฟลเดอร์ `your-work` พร้อมสำหรับคุณที่จะเพิ่มฟีเจอร์การชนกันที่เจ๋ง ๆ ### โครงสร้างโปรเจกต์ ```bash -| assets -| enemyShip.png -| player.png -| laserRed.png -| index.html -| app.js -| package.json ``` **ทำความเข้าใจโครงสร้างไฟล์:** - **มี** ภาพสไปรต์ทั้งหมดที่จำเป็นสำหรับวัตถุในเกม - **รวม** เอกสาร HTML หลักและไฟล์แอปพลิเคชัน JavaScript - **จัดเตรียม** การกำหนดค่าของแพ็กเกจสำหรับเซิร์ฟเวอร์พัฒนาท้องถิ่น ### การเริ่มเซิร์ฟเวอร์พัฒนา ไปที่โฟลเดอร์โปรเจกต์ของคุณและเริ่มเซิร์ฟเวอร์ท้องถิ่น: ```bash cd your-work npm start ``` **ลำดับคำสั่งนี้:** - **เปลี่ยน**ไดเรกทอรีไปยังโฟลเดอร์โปรเจกต์ที่คุณทำงานอยู่ - **เริ่มต้น**เซิร์ฟเวอร์ HTTP ท้องถิ่นที่ `http://localhost:5000` - **ให้บริการ**ไฟล์เกมของคุณสำหรับการทดสอบและพัฒนา - **เปิดใช้งาน**การพัฒนาแบบสดพร้อมการรีโหลดอัตโนมัติ เปิดเบราว์เซอร์ของคุณและไปที่ `http://localhost:5000` เพื่อดูสถานะเกมปัจจุบันของคุณพร้อมฮีโร่และศัตรูที่แสดงบนหน้าจอ ### การนำไปใช้ทีละขั้นตอน เช่นเดียวกับวิธีการที่ NASA ใช้ในการเขียนโปรแกรมยานอวกาศ Voyager เราจะนำการตรวจจับการชนกันมาใช้อย่างเป็นระบบ โดยสร้างแต่ละส่วนทีละขั้นตอน #### 1. เพิ่มขอบเขตการชนกันของรูปสี่เหลี่ยมผืนผ้า ก่อนอื่น มาสอนวัตถุในเกมของเราให้บอกขอบเขตของมันกัน เพิ่มเมธอดนี้ในคลาส `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 ); } ``` **อัลกอริทึมนี้ทำงานโดย:** - **ทดสอบ**เงื่อนไขการแยกสี่ข้อระหว่างรูปสี่เหลี่ยม - **ส่งคืน** `false` หากเงื่อนไขการแยกใด ๆ เป็นจริง - **ระบุ**การชนกันเมื่อไม่มีการแยก - **ใช้**ตรรกะการปฏิเสธเพื่อการทดสอบการตัดกันที่มีประสิทธิภาพ #### 3. นำระบบการยิงเลเซอร์มาใช้ นี่คือจุดที่น่าตื่นเต้น! มาตั้งค่าระบบการยิงเลเซอร์กัน ##### ค่าคงที่ข้อความ ก่อนอื่น มากำหนดประเภทข้อความบางประเภทเพื่อให้ส่วนต่าง ๆ ของเกมของเราสามารถสื่อสารกันได้: ```javascript KEY_EVENT_SPACE: "KEY_EVENT_SPACE", COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER", COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO", ``` **ค่าคงที่เหล่านี้ให้:** - **มาตรฐาน**ชื่อเหตุการณ์ทั่วทั้งแอปพลิเคชัน - **เปิดใช้งาน**การสื่อสารที่สอดคล้องกันระหว่างระบบเกม - **ป้องกัน**ข้อผิดพลาดในการพิมพ์ในการลงทะเบียนตัวจัดการเหตุการณ์ ##### การจัดการการป้อนข้อมูลจากแป้นพิมพ์ เพิ่มการตรวจจับแป้นเว้นวรรคในตัวฟังเหตุการณ์แป้นพิมพ์ของคุณ: ```javascript } else if(evt.keyCode === 32) { eventEmitter.emit(Messages.KEY_EVENT_SPACE); } ``` **ตัวจัดการการป้อนข้อมูลนี้:** - **ตรวจจับ**การกดแป้นเว้นวรรคโดยใช้ keyCode 32 - **ส่ง**ข้อความเหตุการณ์มาตรฐาน - **เปิดใช้งาน**ตรรกะการยิงที่แยกออกจากกัน ##### การตั้งค่าตัวฟังเหตุการณ์ ลงทะเบียนพฤติกรรมการยิงในฟังก์ชัน `initGame()` ของคุณ: ```javascript eventEmitter.on(Messages.KEY_EVENT_SPACE, () => { if (hero.canFire()) { hero.fire(); } }); ``` **ตัวฟังเหตุการณ์นี้:** - **ตอบสนอง**ต่อเหตุการณ์แป้นเว้นวรรค - **ตรวจสอบ**สถานะคูลดาวน์การยิง - **กระตุ้น**การสร้างเลเซอร์เมื่อได้รับอนุญาต เพิ่มการจัดการการชนกันสำหรับการโต้ตอบระหว่างเลเซอร์และศัตรู: ```javascript eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => { first.dead = true; second.dead = true; }); ``` **ตัวจัดการการชนกันนี้:** - **รับ**ข้อมูลเหตุการณ์การชนกันพร้อมวัตถุทั้งสอง - **ทำเครื่องหมาย**วัตถุทั้งสองเพื่อการลบ - **รับรอง**การทำความสะอาดที่เหมาะสมหลังการชนกัน #### 4. สร้างคลาส Laser นำโปรเจกไทล์เลเซอร์มาใช้ที่เคลื่อนที่ขึ้นด้านบนและจัดการวงจรชีวิตของมันเอง: ```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); } } ``` **การนำคลาสนี้ไปใช้:** - **ขยาย** GameObject เพื่อรับฟังก์ชันพื้นฐาน - **ตั้งค่า**ขนาดที่เหมาะสมสำหรับสไปรต์เลเซอร์ - **สร้าง**การเคลื่อนที่ขึ้นด้านบนโดยอัตโนมัติด้วย `setInterval()` - **จัดการ**การทำลายตัวเองเมื่อถึงด้านบนของหน้าจอ - **จัดการ**เวลาแอนิเมชันและการทำความสะอาดของตัวเอง #### 5. นำระบบตรวจจับการชนกันมาใช้ สร้างฟังก์ชันตรวจจับการชนกันที่ครอบคลุม: ```javascript function updateGameObjects() { const enemies = gameObjects.filter(go => go.type === 'Enemy'); const lasers = gameObjects.filter(go => go.type === "Laser"); // Test laser-enemy collisions lasers.forEach((laser) => { enemies.forEach((enemy) => { if (intersectRect(laser.rectFromGameObject(), enemy.rectFromGameObject())) { eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, { first: laser, second: enemy, }); } }); }); // Remove destroyed objects gameObjects = gameObjects.filter(go => !go.dead); } ``` **ระบบการชนกันนี้:** - **กรอง**วัตถุในเกมตามประเภทเพื่อการทดสอบที่มีประสิทธิภาพ - **ทดสอบ**เลเซอร์ทุกอันกับศัตรูทุกตัวเพื่อการตัดกัน - **ส่ง**เหตุการณ์การชนกันเมื่อพบการตัดกัน - **ทำความสะอาด**วัตถุที่ถูกทำลายหลังการประมวลผลการชนกัน > ⚠️ **สำคัญ**: เพิ่ม `updateGameObjects()` ในลูปเกมหลักของคุณใน `window.onload` เพื่อเปิดใช้งานการตรวจจับการชนกัน #### 6. เพิ่มระบบคูลดาวน์ในคลาส Hero ปรับปรุงคลาส 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; } } ``` **ทำความเข้าใจคลาส Hero ที่ปรับปรุงแล้ว:** - **เริ่มต้น**ตัวจับเวลาคูลดาวน์ที่ศูนย์ (พร้อมยิง) - **สร้าง**วัตถุเลเซอร์ที่วางตำแหน่งเหนือยานฮีโร่ - **ตั้งค่า**ช่วงเวลาคูลดาวน์เพื่อป้องกันการยิงแบบรัว - **ลดลง**ตัวจับเวลาคูลดาวน์โดยใช้การอ --- **ข้อจำกัดความรับผิดชอบ**: เอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI [Co-op Translator](https://github.com/Azure/co-op-translator) แม้ว่าเราจะพยายามให้การแปลมีความถูกต้อง แต่โปรดทราบว่าการแปลโดยอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ ขอแนะนำให้ใช้บริการแปลภาษามืออาชีพ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความผิดที่เกิดจากการใช้การแปลนี้