30 KiB
สร้างเกมอวกาศ ตอนที่ 6: จบเกมและเริ่มใหม่
ทุกเกมที่ยอดเยี่ยมต้องมีเงื่อนไขการจบเกมที่ชัดเจนและระบบเริ่มใหม่ที่ราบรื่น คุณได้สร้างเกมอวกาศที่น่าประทับใจด้วยการเคลื่อนไหว การต่อสู้ และการทำคะแนน - ตอนนี้ถึงเวลาที่จะเพิ่มส่วนสุดท้ายที่ทำให้เกมสมบูรณ์แบบ
เกมของคุณในปัจจุบันดำเนินไปเรื่อย ๆ เหมือนกับยาน Voyager ที่ NASA ปล่อยในปี 1977 ซึ่งยังคงเดินทางในอวกาศมาหลายสิบปีแล้ว แม้ว่าจะเหมาะสำหรับการสำรวจอวกาศ แต่เกมต้องมีจุดสิ้นสุดที่กำหนดเพื่อสร้างประสบการณ์ที่น่าพึงพอใจ
วันนี้เราจะเพิ่มเงื่อนไขการชนะ/แพ้ที่เหมาะสมและระบบเริ่มใหม่ เมื่อจบบทเรียนนี้ คุณจะมีเกมที่สมบูรณ์แบบที่ผู้เล่นสามารถเล่นจนจบและเล่นซ้ำได้ เหมือนกับเกมอาร์เคดคลาสสิกที่เป็นที่รู้จักในวงการเกม
แบบทดสอบก่อนเรียน
ทำความเข้าใจเงื่อนไขการจบเกม
เกมของคุณควรจบเมื่อไหร่? คำถามพื้นฐานนี้ได้กำหนดรูปแบบการออกแบบเกมตั้งแต่ยุคอาร์เคดแรก ๆ Pac-Man จบเมื่อคุณถูกผีจับหรือเก็บจุดทั้งหมด ในขณะที่ Space Invaders จบเมื่อเอเลี่ยนลงมาถึงด้านล่างหรือคุณทำลายพวกมันทั้งหมด
ในฐานะผู้สร้างเกม คุณเป็นผู้กำหนดเงื่อนไขการชนะและแพ้ สำหรับเกมอวกาศของเรา นี่คือวิธีการที่พิสูจน์แล้วว่าสร้างการเล่นเกมที่น่าสนใจ:
- ทำลายยานศัตรู
Nลำ: เป็นเรื่องปกติที่เกมจะถูกแบ่งออกเป็นระดับต่าง ๆ โดยคุณต้องทำลายยานศัตรูNลำเพื่อผ่านด่าน - ยานของคุณถูกทำลาย: มีเกมที่คุณจะแพ้ทันทีหากยานของคุณถูกทำลาย อีกวิธีที่นิยมคือการมีระบบชีวิต ทุกครั้งที่ยานของคุณถูกทำลายจะลดจำนวนชีวิตลง เมื่อชีวิตหมดคุณก็จะแพ้เกม
- คุณสะสมคะแนน
Nคะแนน: เงื่อนไขการจบเกมอีกแบบคือการสะสมคะแนน วิธีการได้คะแนนขึ้นอยู่กับคุณ แต่โดยทั่วไปจะมีการให้คะแนนจากกิจกรรมต่าง ๆ เช่น การทำลายยานศัตรู หรือการเก็บไอเท็มที่หล่นเมื่อถูกทำลาย - ผ่านด่าน: อาจมีเงื่อนไขหลายอย่าง เช่น ทำลายยานศัตรู
Xลำ สะสมคะแนนYหรือเก็บไอเท็มเฉพาะ
การเพิ่มฟังก์ชันการเริ่มเกมใหม่
เกมที่ดีจะกระตุ้นให้เล่นซ้ำผ่านระบบเริ่มใหม่ที่ราบรื่น เมื่อผู้เล่นจบเกม (หรือแพ้) พวกเขามักต้องการลองอีกครั้งทันที - ไม่ว่าจะเพื่อทำคะแนนให้ดีกว่าเดิมหรือปรับปรุงการเล่นของตนเอง
Tetris เป็นตัวอย่างที่ดี: เมื่อบล็อกของคุณถึงด้านบน คุณสามารถเริ่มเกมใหม่ได้ทันทีโดยไม่ต้องผ่านเมนูที่ซับซ้อน เราจะสร้างระบบเริ่มใหม่ที่รีเซ็ตสถานะเกมอย่างสะอาดและให้ผู้เล่นกลับมาเล่นได้อย่างรวดเร็ว
✅ สะท้อนความคิด: ลองคิดถึงเกมที่คุณเคยเล่น เงื่อนไขการจบเกมเป็นอย่างไร และคุณถูกกระตุ้นให้เริ่มใหม่อย่างไร? อะไรทำให้ประสบการณ์การเริ่มใหม่รู้สึกราบรื่นหรือหงุดหงิด?
สิ่งที่คุณจะสร้าง
คุณจะเพิ่มฟีเจอร์สุดท้ายที่เปลี่ยนโปรเจกต์ของคุณให้เป็นประสบการณ์เกมที่สมบูรณ์ ฟีเจอร์เหล่านี้แยกเกมที่สมบูรณ์แบบออกจากต้นแบบพื้นฐาน
นี่คือสิ่งที่เราจะเพิ่มวันนี้:
- เงื่อนไขการชนะ: ทำลายศัตรูทั้งหมดและรับการเฉลิมฉลองที่เหมาะสม (คุณสมควรได้รับมัน!)
- เงื่อนไขการแพ้: หมดชีวิตและพบกับหน้าจอแพ้
- ระบบเริ่มใหม่: กด Enter เพื่อกลับมาเล่นอีกครั้ง - เพราะเกมเดียวไม่เคยพอ
- การจัดการสถานะ: เริ่มใหม่ทุกครั้ง - ไม่มีศัตรูที่เหลือหรือข้อผิดพลาดแปลก ๆ จากเกมก่อนหน้า
เริ่มต้น
เตรียมสภาพแวดล้อมการพัฒนา คุณควรมีไฟล์เกมอวกาศทั้งหมดจากบทเรียนก่อนหน้า
โปรเจกต์ของคุณควรมีลักษณะดังนี้:
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| life.png
-| index.html
-| app.js
-| package.json
เริ่มเซิร์ฟเวอร์พัฒนา:
cd your-work
npm start
คำสั่งนี้:
- รันเซิร์ฟเวอร์ในเครื่องที่
http://localhost:5000 - ให้บริการไฟล์ของคุณอย่างถูกต้อง
- รีเฟรชอัตโนมัติเมื่อคุณทำการเปลี่ยนแปลง
เปิด http://localhost:5000 ในเบราว์เซอร์ของคุณและตรวจสอบว่าเกมของคุณทำงานอยู่ คุณควรสามารถเคลื่อนที่ ยิง และโต้ตอบกับศัตรูได้ เมื่อยืนยันแล้ว เราสามารถดำเนินการต่อไปยังการพัฒนา
💡 เคล็ดลับ: เพื่อหลีกเลี่ยงคำเตือนใน Visual Studio Code ให้ประกาศ
gameLoopIdที่ด้านบนของไฟล์ของคุณเป็นlet gameLoopId;แทนที่จะประกาศภายในฟังก์ชันwindow.onloadวิธีนี้เป็นไปตามแนวปฏิบัติที่ดีที่สุดในการประกาศตัวแปรใน JavaScript สมัยใหม่
ขั้นตอนการพัฒนา
ขั้นตอนที่ 1: สร้างฟังก์ชันติดตามเงื่อนไขการจบเกม
เราต้องการฟังก์ชันเพื่อเฝ้าดูว่าเมื่อใดที่เกมควรจบ เหมือนกับเซ็นเซอร์บนสถานีอวกาศนานาชาติที่เฝ้าตรวจสอบระบบสำคัญ ๆ อย่างต่อเนื่อง ฟังก์ชันเหล่านี้จะตรวจสอบสถานะเกมอย่างต่อเนื่อง
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: อัปเดตตัวจัดการเหตุการณ์สำหรับเงื่อนไขการจบเกม
ตอนนี้เราจะเชื่อมโยงการตรวจสอบเงื่อนไขเหล่านี้กับระบบเหตุการณ์ของเกม ทุกครั้งที่เกิดการชน เกมจะประเมินว่ามันกระตุ้นเงื่อนไขการจบเกมหรือไม่ สิ่งนี้สร้างการตอบสนองทันทีสำหรับเหตุการณ์สำคัญในเกม
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 ค่าคงที่เหล่านี้ช่วยรักษาความสอดคล้องและป้องกันข้อผิดพลาดในการพิมพ์ในระบบเหตุการณ์ของคุณ
GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",
ในส่วนนี้ เราได้:
- เพิ่มค่าคงที่สำหรับเหตุการณ์จบเกมเพื่อรักษาความสอดคล้อง
- ใช้ชื่อที่อธิบายได้ชัดเจนซึ่งบ่งบอกถึงวัตถุประสงค์ของเหตุการณ์
- ปฏิบัติตามรูปแบบการตั้งชื่อที่มีอยู่สำหรับประเภทข้อความ
ขั้นตอนที่ 4: เพิ่มการควบคุมการเริ่มเกมใหม่
ตอนนี้คุณจะเพิ่มการควบคุมคีย์บอร์ดที่อนุญาตให้ผู้เล่นเริ่มเกมใหม่ได้ กดปุ่ม Enter เป็นตัวเลือกที่เหมาะสมเนื่องจากมักเกี่ยวข้องกับการยืนยันการกระทำและการเริ่มเกมใหม่
เพิ่มการตรวจจับปุ่ม Enter ลงในตัวฟังเหตุการณ์ keydown ที่มีอยู่ของคุณ:
else if(evt.key === "Enter") {
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}
เพิ่มค่าคงที่ข้อความใหม่:
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
สิ่งที่คุณต้องรู้:
- ขยายระบบการจัดการเหตุการณ์คีย์บอร์ดที่มีอยู่ของคุณ
- ใช้ปุ่ม Enter เป็นตัวกระตุ้นการเริ่มเกมใหม่เพื่อประสบการณ์ผู้ใช้ที่เข้าใจง่าย
- ส่งเหตุการณ์ที่กำหนดเองที่ส่วนอื่น ๆ ของเกมของคุณสามารถฟังได้
- รักษารูปแบบเดียวกับการควบคุมคีย์บอร์ดอื่น ๆ ของคุณ
ขั้นตอนที่ 5: สร้างระบบแสดงข้อความ
เกมของคุณต้องสื่อสารผลลัพธ์ให้ผู้เล่นทราบอย่างชัดเจน เราจะสร้างระบบข้อความที่แสดงสถานะการชนะและแพ้ด้วยข้อความสีที่แตกต่างกัน คล้ายกับอินเทอร์เฟซเทอร์มินัลของระบบคอมพิวเตอร์ยุคแรก ๆ ที่สีเขียวแสดงถึงความสำเร็จและสีแดงแสดงถึงข้อผิดพลาด
สร้างฟังก์ชัน displayMessage():
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():
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():
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():
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
resetGame();
});
เพิ่มเมธอด clear() ลงในคลาส EventEmitter ของคุณ:
clear() {
this.listeners = {};
}
จุดสำคัญที่ต้องจำ:
- เชื่อมโยงการกดปุ่ม Enter กับฟังก์ชันการรีเซ็ตเกม
- ลงทะเบียนตัวฟังเหตุการณ์นี้ระหว่างการเริ่มต้นเกม
- ให้วิธีการลบตัวฟังเหตุการณ์ทั้งหมดเมื่อรีเซ็ต
- ป้องกันการรั่วไหลของหน่วยความจำโดยการล้างตัวจัดการเหตุการณ์ระหว่างเกม
- รีเซ็ตวัตถุ listeners ให้เป็นสถานะว่างเปล่าเพื่อการเริ่มต้นใหม่
ยินดีด้วย! 🎉
👽 💥 🚀 คุณได้สร้างเกมที่สมบูรณ์แบบจากศูนย์สำเร็จแล้ว เหมือนกับโปรแกรมเมอร์ที่สร้างวิดีโอเกมแรกในยุค 1970 คุณได้เปลี่ยนโค้ดให้กลายเป็นประสบการณ์การเล่นเกมที่มีกลไกเกมและการตอบสนองผู้ใช้ที่เหมาะสม 🚀 💥 👽
สิ่งที่คุณทำสำเร็จ:
- เพิ่มเงื่อนไขการชนะและแพ้ที่สมบูรณ์พร้อมการตอบสนองผู้ใช้
- สร้างระบบเริ่มใหม่ที่ราบรื่นสำหรับการเล่นเกมต่อเนื่อง
- ออกแบบการสื่อสารภาพที่ชัดเจนสำหรับสถานะเกม
- จัดการการเปลี่ยนแปลงสถานะเกมที่ซับซ้อนและการทำความสะอาด
- ประกอบส่วนประกอบทั้งหมดให้เป็นเกมที่เล่นได้อย่างสมบูรณ์
ความท้าทาย GitHub Copilot Agent 🚀
ใช้โหมด Agent เพื่อทำความท้าทายต่อไปนี้:
คำอธิบาย: ปรับปรุงเกมอวกาศโดยเพิ่มระบบความก้าวหน้าของระดับที่มีความยากเพิ่มขึ้นและฟีเจอร์โบนัส
คำสั่ง: สร้างระบบเกมอวกาศแบบหลายระดับที่แต่ละระดับมียานศัตรูมากขึ้นพร้อมความเร็วและพลังชีวิตที่เพิ่มขึ้น เพิ่มตัวคูณคะแนนที่เพิ่มขึ้นในแต่ละระดับ และเพิ่มพลังพิเศษ (เช่น การยิงเร็วหรือโล่) ที่ปรากฏแบบสุ่มเมื่อศัตรูถูกทำลาย รวมโบนัสการจบระดับและแสดงระดับปัจจุบันบนหน้าจอควบคู่ไปกับคะแนนและชีวิตที่มีอยู่
เรียนรู้เพิ่มเติมเกี่ยวกับ agent mode ได้ที่นี่
🚀 ความท้าทายเสริมเพิ่มเติม
เพิ่มเสียงในเกมของคุณ: เพิ่มประสบการณ์การเล่นเกมของคุณโดยการเพิ่มเอฟเฟกต์เสียง! ลองเพิ่มเสียงสำหรับ:
- การยิงเลเซอร์ เมื่อผู้เล่นยิง
- การทำลายศัตรู เมื่อยานถูกยิง
- ความเสียหายของฮีโร่ เมื่อผู้เล่นถูกโจมตี
- เพลงชัยชนะ เมื่อเกมชนะ
- เสียงแพ้ เมื่อเกมแพ้
ตัวอย่างการเพิ่มเสียง:
// 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 เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับการเพิ่มเสียงในเกม JavaScript
แบบทดสอบหลังเรียน
ทบทวนและศึกษาด้วยตนเอง
การบ้านของคุณคือการสร้างตัวอย่างเกมใหม่ ดังนั้นลองสำรวจเกมที่น่าสนใจบางเกมเพื่อดูว่าคุณอาจสร้างเกมประเภทใด
การบ้าน
ข้อจำกัดความรับผิดชอบ:
เอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI Co-op Translator แม้ว่าเราจะพยายามให้การแปลมีความถูกต้อง แต่โปรดทราบว่าการแปลโดยอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ ขอแนะนำให้ใช้บริการแปลภาษามืออาชีพ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความผิดที่เกิดจากการใช้การแปลนี้