30 KiB
สร้างเกมอวกาศ ตอนที่ 4: เพิ่มเลเซอร์และตรวจจับการชนกัน
แบบทดสอบก่อนเรียน
ลองนึกถึงช่วงเวลาที่ลุคใช้ตอร์ปิโดโปรตอนยิงเข้าช่องไอเสียของดาวมรณะใน Star Wars การตรวจจับการชนกันที่แม่นยำนี้เปลี่ยนชะตากรรมของกาแล็กซี่! ในเกม การตรวจจับการชนกันทำงานในลักษณะเดียวกัน - มันจะกำหนดว่าเมื่อใดที่วัตถุมีปฏิสัมพันธ์และจะเกิดอะไรขึ้นต่อไป
ในบทเรียนนี้ คุณจะเพิ่มอาวุธเลเซอร์ในเกมอวกาศของคุณและนำการตรวจจับการชนกันมาใช้ เช่นเดียวกับที่นักวางแผนภารกิจของ NASA คำนวณวิถีของยานอวกาศเพื่อหลีกเลี่ยงเศษซาก คุณจะได้เรียนรู้วิธีตรวจจับเมื่อวัตถุในเกมตัดกัน เราจะทำให้มันง่ายขึ้นด้วยการแบ่งเป็นขั้นตอนที่จัดการได้
เมื่อจบบทเรียนนี้ คุณจะมีระบบการต่อสู้ที่สมบูรณ์ซึ่งเลเซอร์สามารถทำลายศัตรูได้ และการชนกันจะกระตุ้นเหตุการณ์ในเกม หลักการตรวจจับการชนกันเหล่านี้ถูกใช้ในทุกอย่างตั้งแต่การจำลองทางฟิสิกส์ไปจนถึงอินเทอร์เฟซเว็บแบบโต้ตอบ
✅ ลองค้นคว้าเกี่ยวกับเกมคอมพิวเตอร์เกมแรกที่เคยเขียนขึ้น มันมีฟังก์ชันอะไรบ้าง?
การตรวจจับการชนกัน
การตรวจจับการชนกันทำงานเหมือนเซ็นเซอร์ตรวจจับระยะใกล้บนโมดูลดวงจันทร์ของ Apollo - มันจะตรวจสอบระยะห่างอย่างต่อเนื่องและส่งสัญญาณเตือนเมื่อวัตถุเข้าใกล้กันมากเกินไป ในเกม ระบบนี้จะกำหนดว่าเมื่อใดที่วัตถุมีปฏิสัมพันธ์และควรเกิดอะไรขึ้นต่อไป
วิธีที่เราจะใช้คือการมองว่าวัตถุในเกมทุกชิ้นเป็นรูปสี่เหลี่ยมผืนผ้า คล้ายกับที่ระบบควบคุมการจราจรทางอากาศใช้รูปทรงเรขาคณิตที่เรียบง่ายเพื่อติดตามเครื่องบิน วิธีการแบบสี่เหลี่ยมนี้อาจดูพื้นฐาน แต่มีประสิทธิภาพในการคำนวณและทำงานได้ดีสำหรับสถานการณ์ในเกมส่วนใหญ่
การแทนค่ารูปสี่เหลี่ยมผืนผ้า
วัตถุในเกมทุกชิ้นต้องมีขอบเขตพิกัด คล้ายกับที่รถสำรวจ Mars Pathfinder กำหนดตำแหน่งของมันบนพื้นผิวดาวอังคาร นี่คือวิธีที่เรากำหนดพิกัดขอบเขตเหล่านี้:
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width
}
}
มาดูรายละเอียดกัน:
- ขอบบน: คือจุดเริ่มต้นในแนวตั้งของวัตถุ (ตำแหน่ง y)
- ขอบซ้าย: คือจุดเริ่มต้นในแนวนอนของวัตถุ (ตำแหน่ง x)
- ขอบล่าง: เพิ่มความสูงเข้าไปในตำแหน่ง y - ตอนนี้คุณรู้แล้วว่ามันสิ้นสุดที่ไหน!
- ขอบขวา: เพิ่มความกว้างเข้าไปในตำแหน่ง x - และคุณได้ขอบเขตทั้งหมดแล้ว
อัลกอริทึมการตัดกัน
การตรวจจับการตัดกันของรูปสี่เหลี่ยมผืนผ้าใช้ตรรกะคล้ายกับที่กล้องโทรทรรศน์อวกาศฮับเบิลใช้ในการกำหนดว่าวัตถุท้องฟ้าทับซ้อนกันในมุมมองของมันหรือไม่ อัลกอริทึมจะตรวจสอบการแยก:
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 ดังนั้นเราจึงใช้วิธี "ทำเครื่องหมายเพื่อลบ" ซึ่งจะลบวัตถุอย่างปลอดภัยระหว่างเฟรม
นี่คือวิธีที่เราทำเครื่องหมายบางสิ่งเพื่อการลบ:
// Mark object for removal
enemy.dead = true;
ทำไมวิธีนี้ถึงได้ผล:
- เราทำเครื่องหมายวัตถุว่า "ตาย" แต่ยังไม่ลบมันทันที
- สิ่งนี้ช่วยให้เฟรมเกมปัจจุบันเสร็จสิ้นอย่างปลอดภัย
- ไม่มีการล่มจากการพยายามใช้สิ่งที่ถูกลบไปแล้ว!
จากนั้นกรองวัตถุที่ถูกทำเครื่องหมายออกก่อนรอบการแสดงผลครั้งต่อไป:
gameObjects = gameObjects.filter(go => !go.dead);
สิ่งที่การกรองนี้ทำ:
- สร้างรายการใหม่ที่มีเฉพาะวัตถุที่ "ยังมีชีวิตอยู่"
- ทิ้งสิ่งที่ถูกทำเครื่องหมายว่าตายไปแล้ว
- ทำให้เกมของคุณทำงานได้อย่างราบรื่น
- ป้องกันการสะสมของวัตถุที่ถูกทำลายซึ่งทำให้หน่วยความจำบวม
การนำกลไกเลเซอร์มาใช้
โปรเจกไทล์เลเซอร์ในเกมทำงานบนหลักการเดียวกับตอร์ปิโดโฟตอนใน Star Trek - มันเป็นวัตถุที่แยกออกมาและเคลื่อนที่เป็นเส้นตรงจนกว่าจะชนบางสิ่ง การกดแป้นเว้นวรรคแต่ละครั้งจะสร้างวัตถุเลเซอร์ใหม่ที่เคลื่อนที่ข้ามหน้าจอ
เพื่อให้สิ่งนี้ทำงาน เราต้องประสานชิ้นส่วนต่าง ๆ:
ส่วนสำคัญที่ต้องนำมาใช้:
- สร้าง วัตถุเลเซอร์ที่เกิดจากตำแหน่งของฮีโร่
- จัดการ การป้อนข้อมูลจากแป้นพิมพ์เพื่อกระตุ้นการสร้างเลเซอร์
- จัดการ การเคลื่อนที่และวงจรชีวิตของเลเซอร์
- นำเสนอ การแสดงผลภาพสำหรับโปรเจกไทล์เลเซอร์
การควบคุมอัตราการยิง
อัตราการยิงที่ไม่จำกัดจะทำให้เครื่องยนต์เกมทำงานหนักเกินไปและทำให้การเล่นเกมง่ายเกินไป ระบบอาวุธจริงก็เผชิญกับข้อจำกัดที่คล้ายกัน - แม้แต่ปืนเฟเซอร์ของ USS Enterprise ก็ต้องใช้เวลาชาร์จระหว่างการยิง
เราจะนำระบบคูลดาวน์มาใช้เพื่อป้องกันการยิงแบบรัว ๆ ในขณะที่ยังคงควบคุมได้อย่างตอบสนอง:
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 ในซีรีส์เกมอวกาศเพื่อเตือนตัวเองเกี่ยวกับคูลดาวน์
การสร้างระบบตรวจจับการชนกัน
คุณจะขยายโค้ดเกมอวกาศที่มีอยู่ของคุณเพื่อสร้างระบบตรวจจับการชนกัน เช่นเดียวกับระบบหลีกเลี่ยงการชนกันอัตโนมัติของสถานีอวกาศนานาชาติ เกมของคุณจะตรวจสอบตำแหน่งของวัตถุอย่างต่อเนื่องและตอบสนองต่อการตัดกัน
เริ่มต้นจากโค้ดบทเรียนก่อนหน้านี้ของคุณ คุณจะเพิ่มการตรวจจับการชนกันพร้อมกฎเฉพาะที่ควบคุมการมีปฏิสัมพันธ์ของวัตถุ
💡 เคล็ดลับ: สไปรต์เลเซอร์มีอยู่แล้วในโฟลเดอร์ทรัพย์สินของคุณและอ้างอิงในโค้ดของคุณ พร้อมสำหรับการนำไปใช้
กฎการชนกันที่ต้องนำมาใช้
กลไกเกมที่ต้องเพิ่ม:
- เลเซอร์ชนศัตรู: วัตถุศัตรูจะถูกทำลายเมื่อถูกโปรเจกไทล์เลเซอร์
- เลเซอร์ชนขอบหน้าจอ: เลเซอร์จะถูกลบเมื่อถึงขอบบนของหน้าจอ
- ศัตรูชนฮีโร่: วัตถุทั้งสองจะถูกทำลายเมื่อชนกัน
- ศัตรูถึงด้านล่าง: เงื่อนไขเกมโอเวอร์เมื่อศัตรูถึงด้านล่างของหน้าจอ
การตั้งค่าสภาพแวดล้อมการพัฒนา
ข่าวดี - เราได้ตั้งค่าพื้นฐานส่วนใหญ่ให้คุณแล้ว! ทรัพยากรเกมและโครงสร้างพื้นฐานทั้งหมดของคุณรออยู่ในโฟลเดอร์ your-work พร้อมสำหรับคุณที่จะเพิ่มฟีเจอร์การชนกันที่เจ๋ง ๆ
โครงสร้างโปรเจกต์
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| index.html
-| app.js
-| package.json
ทำความเข้าใจโครงสร้างไฟล์:
- มี ภาพสไปรต์ทั้งหมดที่จำเป็นสำหรับวัตถุในเกม
- รวม เอกสาร HTML หลักและไฟล์แอปพลิเคชัน JavaScript
- จัดเตรียม การกำหนดค่าของแพ็กเกจสำหรับเซิร์ฟเวอร์พัฒนาท้องถิ่น
การเริ่มเซิร์ฟเวอร์พัฒนา
ไปที่โฟลเดอร์โปรเจกต์ของคุณและเริ่มเซิร์ฟเวอร์ท้องถิ่น:
cd your-work
npm start
ลำดับคำสั่งนี้:
- เปลี่ยนไดเรกทอรีไปยังโฟลเดอร์โปรเจกต์ที่คุณทำงานอยู่
- เริ่มต้นเซิร์ฟเวอร์ HTTP ท้องถิ่นที่
http://localhost:5000 - ให้บริการไฟล์เกมของคุณสำหรับการทดสอบและพัฒนา
- เปิดใช้งานการพัฒนาแบบสดพร้อมการรีโหลดอัตโนมัติ
เปิดเบราว์เซอร์ของคุณและไปที่ http://localhost:5000 เพื่อดูสถานะเกมปัจจุบันของคุณพร้อมฮีโร่และศัตรูที่แสดงบนหน้าจอ
การนำไปใช้ทีละขั้นตอน
เช่นเดียวกับวิธีการที่ NASA ใช้ในการเขียนโปรแกรมยานอวกาศ Voyager เราจะนำการตรวจจับการชนกันมาใช้อย่างเป็นระบบ โดยสร้างแต่ละส่วนทีละขั้นตอน
1. เพิ่มขอบเขตการชนกันของรูปสี่เหลี่ยมผืนผ้า
ก่อนอื่น มาสอนวัตถุในเกมของเราให้บอกขอบเขตของมันกัน เพิ่มเมธอดนี้ในคลาส GameObject ของคุณ:
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width,
};
}
เมธอดนี้ทำให้สำเร็จ:
- สร้างวัตถุรูปสี่เหลี่ยมพร้อมพิกัดขอบเขตที่แม่นยำ
- คำนวณขอบล่างและขอบขวาโดยใช้ตำแหน่งบวกกับขนาด
- ส่งคืนวัตถุที่พร้อมสำหรับอัลกอริทึมการตรวจจับการชนกัน
- จัดเตรียมอินเทอร์เฟซมาตรฐานสำหรับวัตถุในเกมทั้งหมด
2. นำการตรวจจับการตัดกันมาใช้
ตอนนี้มาสร้างนักสืบการชนกันของเรา - ฟังก์ชันที่สามารถบอกได้ว่าเมื่อใดที่รูปสี่เหลี่ยมสองรูปทับซ้อนกัน:
function intersectRect(r1, r2) {
return !(
r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top
);
}
อัลกอริทึมนี้ทำงานโดย:
- ทดสอบเงื่อนไขการแยกสี่ข้อระหว่างรูปสี่เหลี่ยม
- ส่งคืน
falseหากเงื่อนไขการแยกใด ๆ เป็นจริง - ระบุการชนกันเมื่อไม่มีการแยก
- ใช้ตรรกะการปฏิเสธเพื่อการทดสอบการตัดกันที่มีประสิทธิภาพ
3. นำระบบการยิงเลเซอร์มาใช้
นี่คือจุดที่น่าตื่นเต้น! มาตั้งค่าระบบการยิงเลเซอร์กัน
ค่าคงที่ข้อความ
ก่อนอื่น มากำหนดประเภทข้อความบางประเภทเพื่อให้ส่วนต่าง ๆ ของเกมของเราสามารถสื่อสารกันได้:
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
ค่าคงที่เหล่านี้ให้:
- มาตรฐานชื่อเหตุการณ์ทั่วทั้งแอปพลิเคชัน
- เปิดใช้งานการสื่อสารที่สอดคล้องกันระหว่างระบบเกม
- ป้องกันข้อผิดพลาดในการพิมพ์ในการลงทะเบียนตัวจัดการเหตุการณ์
การจัดการการป้อนข้อมูลจากแป้นพิมพ์
เพิ่มการตรวจจับแป้นเว้นวรรคในตัวฟังเหตุการณ์แป้นพิมพ์ของคุณ:
} else if(evt.keyCode === 32) {
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
}
ตัวจัดการการป้อนข้อมูลนี้:
- ตรวจจับการกดแป้นเว้นวรรคโดยใช้ keyCode 32
- ส่งข้อความเหตุการณ์มาตรฐาน
- เปิดใช้งานตรรกะการยิงที่แยกออกจากกัน
การตั้งค่าตัวฟังเหตุการณ์
ลงทะเบียนพฤติกรรมการยิงในฟังก์ชัน initGame() ของคุณ:
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
if (hero.canFire()) {
hero.fire();
}
});
ตัวฟังเหตุการณ์นี้:
- ตอบสนองต่อเหตุการณ์แป้นเว้นวรรค
- ตรวจสอบสถานะคูลดาวน์การยิง
- กระตุ้นการสร้างเลเซอร์เมื่อได้รับอนุญาต
เพิ่มการจัดการการชนกันสำหรับการโต้ตอบระหว่างเลเซอร์และศัตรู:
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
});
ตัวจัดการการชนกันนี้:
- รับข้อมูลเหตุการณ์การชนกันพร้อมวัตถุทั้งสอง
- ทำเครื่องหมายวัตถุทั้งสองเพื่อการลบ
- รับรองการทำความสะอาดที่เหมาะสมหลังการชนกัน
4. สร้างคลาส Laser
นำโปรเจกไทล์เลเซอร์มาใช้ที่เคลื่อนที่ขึ้นด้านบนและจัดการวงจรชีวิตของมันเอง:
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. นำระบบตรวจจับการชนกันมาใช้
สร้างฟังก์ชันตรวจจับการชนกันที่ครอบคลุม:
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 ด้วยกลไกการยิงและการจำกัดอัตรา:
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 แม้ว่าเราจะพยายามให้การแปลมีความถูกต้อง แต่โปรดทราบว่าการแปลโดยอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ ขอแนะนำให้ใช้บริการแปลภาษามืออาชีพ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความผิดที่เกิดจากการใช้การแปลนี้