You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/th/6-space-game/4-collision-detection/README.md

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 ในซีรีส์เกมอวกาศเพื่อเตือนตัวเองเกี่ยวกับคูลดาวน์

การสร้างระบบตรวจจับการชนกัน

คุณจะขยายโค้ดเกมอวกาศที่มีอยู่ของคุณเพื่อสร้างระบบตรวจจับการชนกัน เช่นเดียวกับระบบหลีกเลี่ยงการชนกันอัตโนมัติของสถานีอวกาศนานาชาติ เกมของคุณจะตรวจสอบตำแหน่งของวัตถุอย่างต่อเนื่องและตอบสนองต่อการตัดกัน

เริ่มต้นจากโค้ดบทเรียนก่อนหน้านี้ของคุณ คุณจะเพิ่มการตรวจจับการชนกันพร้อมกฎเฉพาะที่ควบคุมการมีปฏิสัมพันธ์ของวัตถุ

💡 เคล็ดลับ: สไปรต์เลเซอร์มีอยู่แล้วในโฟลเดอร์ทรัพย์สินของคุณและอ้างอิงในโค้ดของคุณ พร้อมสำหรับการนำไปใช้

กฎการชนกันที่ต้องนำมาใช้

กลไกเกมที่ต้องเพิ่ม:

  1. เลเซอร์ชนศัตรู: วัตถุศัตรูจะถูกทำลายเมื่อถูกโปรเจกไทล์เลเซอร์
  2. เลเซอร์ชนขอบหน้าจอ: เลเซอร์จะถูกลบเมื่อถึงขอบบนของหน้าจอ
  3. ศัตรูชนฮีโร่: วัตถุทั้งสองจะถูกทำลายเมื่อชนกัน
  4. ศัตรูถึงด้านล่าง: เงื่อนไขเกมโอเวอร์เมื่อศัตรูถึงด้านล่างของหน้าจอ

การตั้งค่าสภาพแวดล้อมการพัฒนา

ข่าวดี - เราได้ตั้งค่าพื้นฐานส่วนใหญ่ให้คุณแล้ว! ทรัพยากรเกมและโครงสร้างพื้นฐานทั้งหมดของคุณรออยู่ในโฟลเดอร์ 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 แม้ว่าเราจะพยายามให้การแปลมีความถูกต้อง แต่โปรดทราบว่าการแปลโดยอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ ขอแนะนำให้ใช้บริการแปลภาษามืออาชีพ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความผิดที่เกิดจากการใช้การแปลนี้