# สร้างเกมอวกาศ ตอนที่ 6: จบเกมและเริ่มใหม่ ทุกเกมที่ยอดเยี่ยมต้องมีเงื่อนไขการจบเกมที่ชัดเจนและระบบเริ่มใหม่ที่ราบรื่น คุณได้สร้างเกมอวกาศที่น่าประทับใจด้วยการเคลื่อนไหว การต่อสู้ และการทำคะแนน - ตอนนี้ถึงเวลาที่จะเพิ่มส่วนสุดท้ายที่ทำให้เกมสมบูรณ์แบบ เกมของคุณในปัจจุบันดำเนินไปเรื่อย ๆ เหมือนกับยาน Voyager ที่ NASA ปล่อยในปี 1977 ซึ่งยังคงเดินทางในอวกาศมาหลายสิบปีแล้ว แม้ว่าจะเหมาะสำหรับการสำรวจอวกาศ แต่เกมต้องมีจุดสิ้นสุดที่กำหนดเพื่อสร้างประสบการณ์ที่น่าพึงพอใจ วันนี้เราจะเพิ่มเงื่อนไขการชนะ/แพ้ที่เหมาะสมและระบบเริ่มใหม่ เมื่อจบบทเรียนนี้ คุณจะมีเกมที่สมบูรณ์แบบที่ผู้เล่นสามารถเล่นจนจบและเล่นซ้ำได้ เหมือนกับเกมอาร์เคดคลาสสิกที่เป็นที่รู้จักในวงการเกม ## แบบทดสอบก่อนเรียน [แบบทดสอบก่อนเรียน](https://ff-quizzes.netlify.app/web/quiz/39) ## ทำความเข้าใจเงื่อนไขการจบเกม เกมของคุณควรจบเมื่อไหร่? คำถามพื้นฐานนี้ได้กำหนดรูปแบบการออกแบบเกมตั้งแต่ยุคอาร์เคดแรก ๆ Pac-Man จบเมื่อคุณถูกผีจับหรือเก็บจุดทั้งหมด ในขณะที่ Space Invaders จบเมื่อเอเลี่ยนลงมาถึงด้านล่างหรือคุณทำลายพวกมันทั้งหมด ในฐานะผู้สร้างเกม คุณเป็นผู้กำหนดเงื่อนไขการชนะและแพ้ สำหรับเกมอวกาศของเรา นี่คือวิธีการที่พิสูจน์แล้วว่าสร้างการเล่นเกมที่น่าสนใจ: - **ทำลายยานศัตรู `N` ลำ**: เป็นเรื่องปกติที่เกมจะถูกแบ่งออกเป็นระดับต่าง ๆ โดยคุณต้องทำลายยานศัตรู `N` ลำเพื่อผ่านด่าน - **ยานของคุณถูกทำลาย**: มีเกมที่คุณจะแพ้ทันทีหากยานของคุณถูกทำลาย อีกวิธีที่นิยมคือการมีระบบชีวิต ทุกครั้งที่ยานของคุณถูกทำลายจะลดจำนวนชีวิตลง เมื่อชีวิตหมดคุณก็จะแพ้เกม - **คุณสะสมคะแนน `N` คะแนน**: เงื่อนไขการจบเกมอีกแบบคือการสะสมคะแนน วิธีการได้คะแนนขึ้นอยู่กับคุณ แต่โดยทั่วไปจะมีการให้คะแนนจากกิจกรรมต่าง ๆ เช่น การทำลายยานศัตรู หรือการเก็บไอเท็มที่หล่นเมื่อถูกทำลาย - **ผ่านด่าน**: อาจมีเงื่อนไขหลายอย่าง เช่น ทำลายยานศัตรู `X` ลำ สะสมคะแนน `Y` หรือเก็บไอเท็มเฉพาะ ## การเพิ่มฟังก์ชันการเริ่มเกมใหม่ เกมที่ดีจะกระตุ้นให้เล่นซ้ำผ่านระบบเริ่มใหม่ที่ราบรื่น เมื่อผู้เล่นจบเกม (หรือแพ้) พวกเขามักต้องการลองอีกครั้งทันที - ไม่ว่าจะเพื่อทำคะแนนให้ดีกว่าเดิมหรือปรับปรุงการเล่นของตนเอง Tetris เป็นตัวอย่างที่ดี: เมื่อบล็อกของคุณถึงด้านบน คุณสามารถเริ่มเกมใหม่ได้ทันทีโดยไม่ต้องผ่านเมนูที่ซับซ้อน เราจะสร้างระบบเริ่มใหม่ที่รีเซ็ตสถานะเกมอย่างสะอาดและให้ผู้เล่นกลับมาเล่นได้อย่างรวดเร็ว ✅ **สะท้อนความคิด**: ลองคิดถึงเกมที่คุณเคยเล่น เงื่อนไขการจบเกมเป็นอย่างไร และคุณถูกกระตุ้นให้เริ่มใหม่อย่างไร? อะไรทำให้ประสบการณ์การเริ่มใหม่รู้สึกราบรื่นหรือหงุดหงิด? ## สิ่งที่คุณจะสร้าง คุณจะเพิ่มฟีเจอร์สุดท้ายที่เปลี่ยนโปรเจกต์ของคุณให้เป็นประสบการณ์เกมที่สมบูรณ์ ฟีเจอร์เหล่านี้แยกเกมที่สมบูรณ์แบบออกจากต้นแบบพื้นฐาน **นี่คือสิ่งที่เราจะเพิ่มวันนี้:** 1. **เงื่อนไขการชนะ**: ทำลายศัตรูทั้งหมดและรับการเฉลิมฉลองที่เหมาะสม (คุณสมควรได้รับมัน!) 2. **เงื่อนไขการแพ้**: หมดชีวิตและพบกับหน้าจอแพ้ 3. **ระบบเริ่มใหม่**: กด Enter เพื่อกลับมาเล่นอีกครั้ง - เพราะเกมเดียวไม่เคยพอ 4. **การจัดการสถานะ**: เริ่มใหม่ทุกครั้ง - ไม่มีศัตรูที่เหลือหรือข้อผิดพลาดแปลก ๆ จากเกมก่อนหน้า ## เริ่มต้น เตรียมสภาพแวดล้อมการพัฒนา คุณควรมีไฟล์เกมอวกาศทั้งหมดจากบทเรียนก่อนหน้า **โปรเจกต์ของคุณควรมีลักษณะดังนี้:** ```bash -| assets -| enemyShip.png -| player.png -| laserRed.png -| life.png -| index.html -| app.js -| package.json ``` **เริ่มเซิร์ฟเวอร์พัฒนา:** ```bash cd your-work npm start ``` **คำสั่งนี้:** - รันเซิร์ฟเวอร์ในเครื่องที่ `http://localhost:5000` - ให้บริการไฟล์ของคุณอย่างถูกต้อง - รีเฟรชอัตโนมัติเมื่อคุณทำการเปลี่ยนแปลง เปิด `http://localhost:5000` ในเบราว์เซอร์ของคุณและตรวจสอบว่าเกมของคุณทำงานอยู่ คุณควรสามารถเคลื่อนที่ ยิง และโต้ตอบกับศัตรูได้ เมื่อยืนยันแล้ว เราสามารถดำเนินการต่อไปยังการพัฒนา > 💡 **เคล็ดลับ**: เพื่อหลีกเลี่ยงคำเตือนใน Visual Studio Code ให้ประกาศ `gameLoopId` ที่ด้านบนของไฟล์ของคุณเป็น `let gameLoopId;` แทนที่จะประกาศภายในฟังก์ชัน `window.onload` วิธีนี้เป็นไปตามแนวปฏิบัติที่ดีที่สุดในการประกาศตัวแปรใน JavaScript สมัยใหม่ ## ขั้นตอนการพัฒนา ### ขั้นตอนที่ 1: สร้างฟังก์ชันติดตามเงื่อนไขการจบเกม เราต้องการฟังก์ชันเพื่อเฝ้าดูว่าเมื่อใดที่เกมควรจบ เหมือนกับเซ็นเซอร์บนสถานีอวกาศนานาชาติที่เฝ้าตรวจสอบระบบสำคัญ ๆ อย่างต่อเนื่อง ฟังก์ชันเหล่านี้จะตรวจสอบสถานะเกมอย่างต่อเนื่อง ```javascript function isHeroDead() { return hero.life <= 0; } function isEnemiesDead() { const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead); return enemies.length === 0; } ``` **สิ่งที่เกิดขึ้นเบื้องหลัง:** - **ตรวจสอบ** ว่าฮีโร่ของเราหมดชีวิตหรือไม่ (โอ๊ย!) - **นับ** จำนวนศัตรูที่ยังมีชีวิตอยู่ - **คืนค่า** `true` เมื่อสนามรบว่างเปล่าจากศัตรู - **ใช้**ตรรกะ true/false ที่เรียบง่ายเพื่อให้เข้าใจง่าย - **กรอง**ผ่านวัตถุเกมทั้งหมดเพื่อหาผู้รอดชีวิต ### ขั้นตอนที่ 2: อัปเดตตัวจัดการเหตุการณ์สำหรับเงื่อนไขการจบเกม ตอนนี้เราจะเชื่อมโยงการตรวจสอบเงื่อนไขเหล่านี้กับระบบเหตุการณ์ของเกม ทุกครั้งที่เกิดการชน เกมจะประเมินว่ามันกระตุ้นเงื่อนไขการจบเกมหรือไม่ สิ่งนี้สร้างการตอบสนองทันทีสำหรับเหตุการณ์สำคัญในเกม ```javascript eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => { first.dead = true; second.dead = true; hero.incrementPoints(); if (isEnemiesDead()) { eventEmitter.emit(Messages.GAME_END_WIN); } }); eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => { enemy.dead = true; hero.decrementLife(); if (isHeroDead()) { eventEmitter.emit(Messages.GAME_END_LOSS); return; // loss before victory } if (isEnemiesDead()) { eventEmitter.emit(Messages.GAME_END_WIN); } }); eventEmitter.on(Messages.GAME_END_WIN, () => { endGame(true); }); eventEmitter.on(Messages.GAME_END_LOSS, () => { endGame(false); }); ``` **สิ่งที่เกิดขึ้นที่นี่:** - **เลเซอร์ชนศัตรู**: ทั้งคู่หายไป คุณได้คะแนน และเราตรวจสอบว่าคุณชนะหรือไม่ - **ศัตรูชนคุณ**: คุณเสียชีวิต และเราตรวจสอบว่าคุณยังมีชีวิตอยู่หรือไม่ - **ลำดับที่ชาญฉลาด**: เราตรวจสอบการแพ้ก่อน (ไม่มีใครอยากชนะและแพ้ในเวลาเดียวกัน!) - **การตอบสนองทันที**: ทันทีที่มีสิ่งสำคัญเกิดขึ้น เกมจะรับรู้ ### ขั้นตอนที่ 3: เพิ่มค่าคงที่ข้อความใหม่ คุณจะต้องเพิ่มประเภทข้อความใหม่ลงในวัตถุค่าคงที่ `Messages` ค่าคงที่เหล่านี้ช่วยรักษาความสอดคล้องและป้องกันข้อผิดพลาดในการพิมพ์ในระบบเหตุการณ์ของคุณ ```javascript GAME_END_LOSS: "GAME_END_LOSS", GAME_END_WIN: "GAME_END_WIN", ``` **ในส่วนนี้ เราได้:** - **เพิ่ม**ค่าคงที่สำหรับเหตุการณ์จบเกมเพื่อรักษาความสอดคล้อง - **ใช้**ชื่อที่อธิบายได้ชัดเจนซึ่งบ่งบอกถึงวัตถุประสงค์ของเหตุการณ์ - **ปฏิบัติตาม**รูปแบบการตั้งชื่อที่มีอยู่สำหรับประเภทข้อความ ### ขั้นตอนที่ 4: เพิ่มการควบคุมการเริ่มเกมใหม่ ตอนนี้คุณจะเพิ่มการควบคุมคีย์บอร์ดที่อนุญาตให้ผู้เล่นเริ่มเกมใหม่ได้ กดปุ่ม Enter เป็นตัวเลือกที่เหมาะสมเนื่องจากมักเกี่ยวข้องกับการยืนยันการกระทำและการเริ่มเกมใหม่ **เพิ่มการตรวจจับปุ่ม Enter ลงในตัวฟังเหตุการณ์ keydown ที่มีอยู่ของคุณ:** ```javascript else if(evt.key === "Enter") { eventEmitter.emit(Messages.KEY_EVENT_ENTER); } ``` **เพิ่มค่าคงที่ข้อความใหม่:** ```javascript KEY_EVENT_ENTER: "KEY_EVENT_ENTER", ``` **สิ่งที่คุณต้องรู้:** - **ขยาย**ระบบการจัดการเหตุการณ์คีย์บอร์ดที่มีอยู่ของคุณ - **ใช้**ปุ่ม Enter เป็นตัวกระตุ้นการเริ่มเกมใหม่เพื่อประสบการณ์ผู้ใช้ที่เข้าใจง่าย - **ส่ง**เหตุการณ์ที่กำหนดเองที่ส่วนอื่น ๆ ของเกมของคุณสามารถฟังได้ - **รักษา**รูปแบบเดียวกับการควบคุมคีย์บอร์ดอื่น ๆ ของคุณ ### ขั้นตอนที่ 5: สร้างระบบแสดงข้อความ เกมของคุณต้องสื่อสารผลลัพธ์ให้ผู้เล่นทราบอย่างชัดเจน เราจะสร้างระบบข้อความที่แสดงสถานะการชนะและแพ้ด้วยข้อความสีที่แตกต่างกัน คล้ายกับอินเทอร์เฟซเทอร์มินัลของระบบคอมพิวเตอร์ยุคแรก ๆ ที่สีเขียวแสดงถึงความสำเร็จและสีแดงแสดงถึงข้อผิดพลาด **สร้างฟังก์ชัน `displayMessage()`:** ```javascript function displayMessage(message, color = "red") { ctx.font = "30px Arial"; ctx.fillStyle = color; ctx.textAlign = "center"; ctx.fillText(message, canvas.width / 2, canvas.height / 2); } ``` **ทีละขั้นตอน สิ่งที่เกิดขึ้น:** - **ตั้งค่า**ขนาดและรูปแบบตัวอักษรเพื่อให้ข้อความอ่านง่าย - **ใช้**พารามิเตอร์สี โดยมี "สีแดง" เป็นค่าเริ่มต้นสำหรับคำเตือน - **จัดกึ่งกลาง**ข้อความในแนวนอนและแนวตั้งบนแคนวาส - **ใช้**พารามิเตอร์เริ่มต้นของ JavaScript สมัยใหม่เพื่อความยืดหยุ่นของตัวเลือกสี - **ใช้**บริบท 2D ของแคนวาสเพื่อการแสดงผลข้อความโดยตรง **สร้างฟังก์ชัน `endGame()`:** ```javascript function endGame(win) { clearInterval(gameLoopId); // Set a delay to ensure any pending renders complete setTimeout(() => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "black"; ctx.fillRect(0, 0, canvas.width, canvas.height); if (win) { displayMessage( "Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew", "green" ); } else { displayMessage( "You died !!! Press [Enter] to start a new game Captain Pew Pew" ); } }, 200) } ``` **สิ่งที่ฟังก์ชันนี้ทำ:** - **หยุด**ทุกอย่างในที่ - ไม่มีการเคลื่อนที่ของยานหรือเลเซอร์อีกต่อไป - **หยุดชั่วคราว**เล็กน้อย (200ms) เพื่อให้เฟรมสุดท้ายวาดเสร็จ - **ล้าง**หน้าจอให้สะอาดและเปลี่ยนเป็นสีดำเพื่อความดราม่า - **แสดง**ข้อความที่แตกต่างกันสำหรับผู้ชนะและผู้แพ้ - **ใช้สี**เพื่อสื่อสาร - สีเขียวสำหรับข่าวดี สีแดงสำหรับ...ข่าวร้าย - **บอก**ผู้เล่นว่าต้องทำอย่างไรเพื่อกลับมาเล่นอีกครั้ง ### ขั้นตอนที่ 6: เพิ่มฟังก์ชันการรีเซ็ตเกม ระบบรีเซ็ตต้องทำความสะอาดสถานะเกมปัจจุบันอย่างสมบูรณ์และเริ่มเกมใหม่ด้วยเซสชันที่สดใหม่ สิ่งนี้ช่วยให้ผู้เล่นเริ่มต้นใหม่ได้โดยไม่มีข้อมูลที่เหลือจากเกมก่อนหน้า **สร้างฟังก์ชัน `resetGame()`:** ```javascript function resetGame() { if (gameLoopId) { clearInterval(gameLoopId); eventEmitter.clear(); initGame(); gameLoopId = setInterval(() => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "black"; ctx.fillRect(0, 0, canvas.width, canvas.height); drawPoints(); drawLife(); updateGameObjects(); drawGameObjects(ctx); }, 100); } } ``` **มาทำความเข้าใจแต่ละส่วน:** - **ตรวจสอบ**ว่ามีลูปเกมที่กำลังทำงานอยู่ก่อนที่จะรีเซ็ต - **ล้าง**ลูปเกมที่มีอยู่เพื่อหยุดกิจกรรมเกมปัจจุบันทั้งหมด - **ลบ**ตัวฟังเหตุการณ์ทั้งหมดเพื่อป้องกันการรั่วไหลของหน่วยความจำ - **เริ่มต้นใหม่**สถานะเกมด้วยวัตถุและตัวแปรใหม่ - **เริ่มต้น**ลูปเกมใหม่ด้วยฟังก์ชันเกมที่จำเป็นทั้งหมด - **รักษา**ช่วงเวลา 100ms เดิมเพื่อประสิทธิภาพของเกมที่สม่ำเสมอ **เพิ่มตัวจัดการเหตุการณ์ปุ่ม Enter ลงในฟังก์ชัน `initGame()`:** ```javascript eventEmitter.on(Messages.KEY_EVENT_ENTER, () => { resetGame(); }); ``` **เพิ่มเมธอด `clear()` ลงในคลาส EventEmitter ของคุณ:** ```javascript clear() { this.listeners = {}; } ``` **จุดสำคัญที่ต้องจำ:** - **เชื่อมโยง**การกดปุ่ม Enter กับฟังก์ชันการรีเซ็ตเกม - **ลงทะเบียน**ตัวฟังเหตุการณ์นี้ระหว่างการเริ่มต้นเกม - **ให้**วิธีการลบตัวฟังเหตุการณ์ทั้งหมดเมื่อรีเซ็ต - **ป้องกัน**การรั่วไหลของหน่วยความจำโดยการล้างตัวจัดการเหตุการณ์ระหว่างเกม - **รีเซ็ต**วัตถุ listeners ให้เป็นสถานะว่างเปล่าเพื่อการเริ่มต้นใหม่ ## ยินดีด้วย! 🎉 👽 💥 🚀 คุณได้สร้างเกมที่สมบูรณ์แบบจากศูนย์สำเร็จแล้ว เหมือนกับโปรแกรมเมอร์ที่สร้างวิดีโอเกมแรกในยุค 1970 คุณได้เปลี่ยนโค้ดให้กลายเป็นประสบการณ์การเล่นเกมที่มีกลไกเกมและการตอบสนองผู้ใช้ที่เหมาะสม 🚀 💥 👽 **สิ่งที่คุณทำสำเร็จ:** - **เพิ่ม**เงื่อนไขการชนะและแพ้ที่สมบูรณ์พร้อมการตอบสนองผู้ใช้ - **สร้าง**ระบบเริ่มใหม่ที่ราบรื่นสำหรับการเล่นเกมต่อเนื่อง - **ออกแบบ**การสื่อสารภาพที่ชัดเจนสำหรับสถานะเกม - **จัดการ**การเปลี่ยนแปลงสถานะเกมที่ซับซ้อนและการทำความสะอาด - **ประกอบ**ส่วนประกอบทั้งหมดให้เป็นเกมที่เล่นได้อย่างสมบูรณ์ ## ความท้าทาย GitHub Copilot Agent 🚀 ใช้โหมด Agent เพื่อทำความท้าทายต่อไปนี้: **คำอธิบาย:** ปรับปรุงเกมอวกาศโดยเพิ่มระบบความก้าวหน้าของระดับที่มีความยากเพิ่มขึ้นและฟีเจอร์โบนัส **คำสั่ง:** สร้างระบบเกมอวกาศแบบหลายระดับที่แต่ละระดับมียานศัตรูมากขึ้นพร้อมความเร็วและพลังชีวิตที่เพิ่มขึ้น เพิ่มตัวคูณคะแนนที่เพิ่มขึ้นในแต่ละระดับ และเพิ่มพลังพิเศษ (เช่น การยิงเร็วหรือโล่) ที่ปรากฏแบบสุ่มเมื่อศัตรูถูกทำลาย รวมโบนัสการจบระดับและแสดงระดับปัจจุบันบนหน้าจอควบคู่ไปกับคะแนนและชีวิตที่มีอยู่ เรียนรู้เพิ่มเติมเกี่ยวกับ [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) ได้ที่นี่ ## 🚀 ความท้าทายเสริมเพิ่มเติม **เพิ่มเสียงในเกมของคุณ**: เพิ่มประสบการณ์การเล่นเกมของคุณโดยการเพิ่มเอฟเฟกต์เสียง! ลองเพิ่มเสียงสำหรับ: - **การยิงเลเซอร์** เมื่อผู้เล่นยิง - **การทำลายศัตรู** เมื่อยานถูกยิง - **ความเสียหายของฮีโร่** เมื่อผู้เล่นถูกโจมตี - **เพลงชัยชนะ** เมื่อเกมชนะ - **เสียงแพ้** เมื่อเกมแพ้ **ตัวอย่างการเพิ่มเสียง:** ```javascript // Create audio objects const laserSound = new Audio('assets/laser.wav'); const explosionSound = new Audio('assets/explosion.wav'); // Play sounds during game events function playLaserSound() { laserSound.currentTime = 0; // Reset to beginning laserSound.play(); } ``` **สิ่งที่คุณต้องรู้:** - **สร้าง**วัตถุเสียงสำหรับเอฟเฟกต์เสียงต่าง ๆ - **รีเซ็ต** `currentTime` เพื่อให้เอฟเฟกต์เสียงทำงานได้อย่างรวดเร็ว - **จัดการ**นโยบายการเล่นอัตโนมัติของเบราว์เซอร์โดยการกระตุ้นเสียงจากการโต้ตอบของผู้ใช้ - **จัดการ**ระดับเสียงและเวลาเสียงเพื่อประสบการณ์เกมที่ดีขึ้น > 💡 **แหล่งเรียนรู้**: สำรวจ [audio sandbox](https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_audio_play) เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับการเพิ่มเสียงในเกม JavaScript ## แบบทดสอบหลังเรียน [แบบทดสอบหลังเรียน](https://ff-quizzes.netlify.app/web/quiz/40) ## ทบทวนและศึกษาด้วยตนเอง การบ้านของคุณคือการสร้างตัวอย่างเกมใหม่ ดังนั้นลองสำรวจเกมที่น่าสนใจบางเกมเพื่อดูว่าคุณอาจสร้างเกมประเภทใด ## การบ้าน [สร้างตัวอย่างเกม](assignment.md) --- **ข้อจำกัดความรับผิดชอบ**: เอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI [Co-op Translator](https://github.com/Azure/co-op-translator) แม้ว่าเราจะพยายามให้การแปลมีความถูกต้อง แต่โปรดทราบว่าการแปลโดยอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ ขอแนะนำให้ใช้บริการแปลภาษามืออาชีพ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความผิดที่เกิดจากการใช้การแปลนี้