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/3-moving-elements-around
leestott 425e0f6fbe
🌐 Update translations via Co-op Translator
3 weeks ago
..
README.md 🌐 Update translations via Co-op Translator 3 weeks ago
assignment.md 🌐 Update translations via Co-op Translator 4 weeks ago

README.md

สร้างเกมอวกาศ ตอนที่ 3: เพิ่มการเคลื่อนไหว

แบบทดสอบก่อนเรียน

แบบทดสอบก่อนเรียน

เกมจะสนุกขึ้นเมื่อมีเอเลี่ยนเคลื่อนไหวบนหน้าจอ! ในเกมนี้ เราจะใช้การเคลื่อนไหวสองประเภท:

  • การเคลื่อนไหวด้วยคีย์บอร์ด/เมาส์: เมื่อผู้ใช้โต้ตอบกับคีย์บอร์ดหรือเมาส์เพื่อเคลื่อนย้ายวัตถุบนหน้าจอ
  • การเคลื่อนไหวที่เกิดจากเกม: เมื่อเกมเคลื่อนย้ายวัตถุในช่วงเวลาที่กำหนด

แล้วเราจะเคลื่อนย้ายสิ่งต่าง ๆ บนหน้าจอได้อย่างไร? ทุกอย่างเกี่ยวกับพิกัดคาร์ทีเซียน: เราเปลี่ยนตำแหน่ง (x, y) ของวัตถุแล้ววาดหน้าจอใหม่

โดยทั่วไป คุณต้องทำตามขั้นตอนต่อไปนี้เพื่อให้เกิด การเคลื่อนไหว บนหน้าจอ:

  1. ตั้งค่าตำแหน่งใหม่ สำหรับวัตถุ; สิ่งนี้จำเป็นเพื่อให้รู้สึกว่าวัตถุได้เคลื่อนที่
  2. ล้างหน้าจอ หน้าจอต้องถูกล้างระหว่างการวาดใหม่ เราสามารถล้างได้โดยการวาดสี่เหลี่ยมที่เติมด้วยสีพื้นหลัง
  3. วาดวัตถุใหม่ ที่ตำแหน่งใหม่ การทำเช่นนี้จะทำให้เราสามารถเคลื่อนย้ายวัตถุจากตำแหน่งหนึ่งไปยังอีกตำแหน่งหนึ่งได้สำเร็จ

นี่คือตัวอย่างโค้ด:

//set the hero's location
hero.x += 5;
// clear the rectangle that hosts the hero
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);

คุณคิดว่าเหตุใดการวาดฮีโร่ของคุณหลายเฟรมต่อวินาทีอาจส่งผลต่อประสิทธิภาพ? อ่านเพิ่มเติมเกี่ยวกับ ทางเลือกสำหรับรูปแบบนี้

จัดการเหตุการณ์คีย์บอร์ด

คุณจัดการเหตุการณ์โดยการเชื่อมต่อเหตุการณ์เฉพาะกับโค้ด เหตุการณ์คีย์บอร์ดจะถูกเรียกใช้บนหน้าต่างทั้งหมด ในขณะที่เหตุการณ์เมาส์ เช่น click สามารถเชื่อมต่อกับการคลิกองค์ประกอบเฉพาะ เราจะใช้เหตุการณ์คีย์บอร์ดตลอดโครงการนี้

ในการจัดการเหตุการณ์ คุณต้องใช้เมธอด addEventListener() ของหน้าต่างและให้พารามิเตอร์สองตัวเป็นอินพุต พารามิเตอร์แรกคือชื่อของเหตุการณ์ เช่น keyup พารามิเตอร์ที่สองคือฟังก์ชันที่จะถูกเรียกใช้เมื่อเหตุการณ์เกิดขึ้น

นี่คือตัวอย่าง:

window.addEventListener('keyup', (evt) => {
  // `evt.key` = string representation of the key
  if (evt.key === 'ArrowUp') {
    // do something
  }
})

สำหรับเหตุการณ์คีย์ มีคุณสมบัติสองอย่างในเหตุการณ์ที่คุณสามารถใช้เพื่อดูว่าคีย์ใดถูกกด:

  • key เป็นตัวแทนข้อความของคีย์ที่ถูกกด เช่น ArrowUp
  • keyCode เป็นตัวแทนตัวเลข เช่น 37 ซึ่งสอดคล้องกับ ArrowLeft

การจัดการเหตุการณ์คีย์มีประโยชน์นอกเหนือจากการพัฒนาเกม คุณคิดว่าเทคนิคนี้สามารถนำไปใช้ในด้านอื่นได้อย่างไร?

คีย์พิเศษ: ข้อควรระวัง

มีคีย์ พิเศษ บางตัวที่ส่งผลต่อหน้าต่าง ซึ่งหมายความว่าหากคุณกำลังฟังเหตุการณ์ keyup และคุณใช้คีย์พิเศษเหล่านี้เพื่อเคลื่อนย้ายฮีโร่ของคุณ มันจะทำให้เกิดการเลื่อนแนวนอนด้วย ด้วยเหตุนี้ คุณอาจต้องการ ปิด พฤติกรรมเริ่มต้นของเบราว์เซอร์ในขณะที่คุณสร้างเกมของคุณ คุณต้องใช้โค้ดแบบนี้:

let onKeyDown = function (e) {
  console.log(e.keyCode);
  switch (e.keyCode) {
    case 37:
    case 39:
    case 38:
    case 40: // Arrow keys
    case 32:
      e.preventDefault();
      break; // Space
    default:
      break; // do not block other keys
  }
};

window.addEventListener('keydown', onKeyDown);

โค้ดด้านบนจะทำให้คีย์ลูกศรและคีย์ Space มีพฤติกรรม เริ่มต้น ถูกปิด การปิดพฤติกรรมเกิดขึ้นเมื่อเราเรียก e.preventDefault()

การเคลื่อนไหวที่เกิดจากเกม

เราสามารถทำให้สิ่งต่าง ๆ เคลื่อนไหวได้เองโดยใช้ตัวจับเวลา เช่น ฟังก์ชัน setTimeout() หรือ setInterval() ที่อัปเดตตำแหน่งของวัตถุในแต่ละช่วงเวลา นี่คือตัวอย่างโค้ด:

let id = setInterval(() => {
  //move the enemy on the y axis
  enemy.y += 10;
})

วงลูปของเกม

วงลูปของเกมเป็นแนวคิดที่เป็นฟังก์ชันที่ถูกเรียกใช้ในช่วงเวลาปกติ มันถูกเรียกว่าวงลูปของเกมเพราะทุกสิ่งที่ควรมองเห็นได้สำหรับผู้ใช้จะถูกวาดในวงลูป วงลูปของเกมใช้วัตถุเกมทั้งหมดที่เป็นส่วนหนึ่งของเกม วาดทั้งหมด ยกเว้นในกรณีที่วัตถุไม่ควรเป็นส่วนหนึ่งของเกมอีกต่อไป ตัวอย่างเช่น หากวัตถุเป็นศัตรูที่ถูกเลเซอร์ยิงและระเบิด มันจะไม่เป็นส่วนหนึ่งของวงลูปเกมอีกต่อไป (คุณจะได้เรียนรู้เพิ่มเติมในบทเรียนถัดไป)

นี่คือตัวอย่างวงลูปของเกมในโค้ด:

let gameLoopId = setInterval(() =>
  function gameLoop() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "black";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    drawHero();
    drawEnemies();
    drawStaticObjects();
}, 200);

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

ดำเนินการสร้างเกมอวกาศต่อ

คุณจะนำโค้ดที่มีอยู่และขยายมันต่อไป คุณสามารถเริ่มต้นด้วยโค้ดที่คุณทำเสร็จในส่วนที่ I หรือใช้โค้ดใน Part II- starter

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

ขั้นตอนที่แนะนำ

ค้นหาไฟล์ที่ถูกสร้างไว้ให้คุณในโฟลเดอร์ your-work มันควรมีดังนี้:

-| assets
  -| enemyShip.png
  -| player.png
-| index.html
-| app.js
-| package.json

เริ่มโครงการของคุณในโฟลเดอร์ your_work โดยพิมพ์:

cd your-work
npm start

โค้ดด้านบนจะเริ่ม HTTP Server ที่ที่อยู่ http://localhost:5000 เปิดเบราว์เซอร์และใส่ที่อยู่นั้น ตอนนี้มันควรแสดงฮีโร่และศัตรูทั้งหมด; แต่ยังไม่มีอะไรเคลื่อนไหว!

เพิ่มโค้ด

  1. เพิ่มวัตถุเฉพาะ สำหรับ hero และ enemy และ game object ซึ่งควรมีคุณสมบัติ x และ y (จำส่วน Inheritance or composition )

    คำแนะนำ game object ควรเป็นวัตถุที่มี x และ y และความสามารถในการวาดตัวเองลงบนแคนวาส

    คำแนะนำ: เริ่มต้นด้วยการเพิ่มคลาส GameObject ใหม่พร้อมตัวสร้างที่กำหนดไว้ดังนี้ และวาดมันลงบนแคนวาส:

    
    class GameObject {
      constructor(x, y) {
        this.x = x;
        this.y = y;
        this.dead = false;
        this.type = "";
        this.width = 0;
        this.height = 0;
        this.img = undefined;
      }
    
      draw(ctx) {
        ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
      }
    }
    

    จากนั้น ขยาย GameObject นี้เพื่อสร้าง Hero และ Enemy

    class Hero extends GameObject {
      constructor(x, y) {
        ...it needs an x, y, type, and speed
      }
    }
    
    class Enemy extends GameObject {
      constructor(x, y) {
        super(x, y);
        (this.width = 98), (this.height = 50);
        this.type = "Enemy";
        let id = setInterval(() => {
          if (this.y < canvas.height - this.height) {
            this.y += 5;
          } else {
            console.log('Stopped at', this.y)
            clearInterval(id);
          }
        }, 300)
      }
    }
    
  2. เพิ่มตัวจัดการเหตุการณ์คีย์ เพื่อจัดการการนำทางด้วยคีย์ (เคลื่อนย้ายฮีโร่ขึ้น/ลง ซ้าย/ขวา)

    จำไว้ ว่ามันเป็นระบบคาร์ทีเซียน มุมบนซ้ายคือ 0,0 และอย่าลืมเพิ่มโค้ดเพื่อหยุด พฤติกรรมเริ่มต้น

    คำแนะนำ: สร้างฟังก์ชัน onKeyDown ของคุณและเชื่อมต่อมันกับหน้าต่าง:

     let onKeyDown = function (e) {
           console.log(e.keyCode);
             ...add the code from the lesson above to stop default behavior
           }
     };
    
     window.addEventListener("keydown", onKeyDown);
    

    ตรวจสอบคอนโซลของเบราว์เซอร์ ณ จุดนี้ และดูการกดคีย์ที่ถูกบันทึก

  3. นำไปใช้ Pub sub pattern สิ่งนี้จะช่วยให้โค้ดของคุณสะอาดขึ้นเมื่อคุณทำตามส่วนที่เหลือ

    ในการทำส่วนสุดท้ายนี้ คุณสามารถ:

    1. เพิ่มตัวฟังเหตุการณ์ บนหน้าต่าง:

       window.addEventListener("keyup", (evt) => {
         if (evt.key === "ArrowUp") {
           eventEmitter.emit(Messages.KEY_EVENT_UP);
         } else if (evt.key === "ArrowDown") {
           eventEmitter.emit(Messages.KEY_EVENT_DOWN);
         } else if (evt.key === "ArrowLeft") {
           eventEmitter.emit(Messages.KEY_EVENT_LEFT);
         } else if (evt.key === "ArrowRight") {
           eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
         }
       });
      
    2. สร้างคลาส EventEmitter เพื่อเผยแพร่และสมัครรับข้อความ:

      class EventEmitter {
        constructor() {
          this.listeners = {};
        }
      
        on(message, listener) {
          if (!this.listeners[message]) {
            this.listeners[message] = [];
          }
          this.listeners[message].push(listener);
        }
      
        emit(message, payload = null) {
          if (this.listeners[message]) {
            this.listeners[message].forEach((l) => l(message, payload));
          }
        }
      }
      
    3. เพิ่มค่าคงที่ และตั้งค่า EventEmitter:

      const Messages = {
        KEY_EVENT_UP: "KEY_EVENT_UP",
        KEY_EVENT_DOWN: "KEY_EVENT_DOWN",
        KEY_EVENT_LEFT: "KEY_EVENT_LEFT",
        KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT",
      };
      
      let heroImg, 
          enemyImg, 
          laserImg,
          canvas, ctx, 
          gameObjects = [], 
          hero, 
          eventEmitter = new EventEmitter();
      
    4. เริ่มต้นเกม

    function initGame() {
      gameObjects = [];
      createEnemies();
      createHero();
    
      eventEmitter.on(Messages.KEY_EVENT_UP, () => {
        hero.y -=5 ;
      })
    
      eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
        hero.y += 5;
      });
    
      eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
        hero.x -= 5;
      });
    
      eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
        hero.x += 5;
      });
    }
    
  4. ตั้งค่าวงลูปของเกม

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

    window.onload = async () => {
      canvas = document.getElementById("canvas");
      ctx = canvas.getContext("2d");
      heroImg = await loadTexture("assets/player.png");
      enemyImg = await loadTexture("assets/enemyShip.png");
      laserImg = await loadTexture("assets/laserRed.png");
    
      initGame();
      let gameLoopId = setInterval(() => {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "black";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        drawGameObjects(ctx);
      }, 100)
    
    };
    
  5. เพิ่มโค้ด เพื่อเคลื่อนย้ายศัตรูในช่วงเวลาที่กำหนด

    ปรับปรุงฟังก์ชัน createEnemies() เพื่อสร้างศัตรูและเพิ่มพวกมันลงในคลาส gameObjects ใหม่:

    function createEnemies() {
      const MONSTER_TOTAL = 5;
      const MONSTER_WIDTH = MONSTER_TOTAL * 98;
      const START_X = (canvas.width - MONSTER_WIDTH) / 2;
      const STOP_X = START_X + MONSTER_WIDTH;
    
      for (let x = START_X; x < STOP_X; x += 98) {
        for (let y = 0; y < 50 * 5; y += 50) {
          const enemy = new Enemy(x, y);
          enemy.img = enemyImg;
          gameObjects.push(enemy);
        }
      }
    }
    

    และเพิ่มฟังก์ชัน createHero() เพื่อทำกระบวนการคล้ายกันสำหรับฮีโร่

    function createHero() {
      hero = new Hero(
        canvas.width / 2 - 45,
        canvas.height - canvas.height / 4
      );
      hero.img = heroImg;
      gameObjects.push(hero);
    }
    

    และสุดท้าย เพิ่มฟังก์ชัน drawGameObjects() เพื่อเริ่มการวาด:

    function drawGameObjects(ctx) {
      gameObjects.forEach(go => go.draw(ctx));
    }
    

    ศัตรูของคุณควรเริ่มเคลื่อนที่เข้าหายานอวกาศฮีโร่ของคุณ!


🚀 ความท้าทาย

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

แบบทดสอบหลังเรียน

แบบทดสอบหลังเรียน

ทบทวนและศึกษาด้วยตนเอง

แม้ว่าเราจะเขียนเกมของเราโดยไม่ใช้เฟรมเวิร์ก แต่มีเฟรมเวิร์ก Canvas ที่ใช้ JavaScript สำหรับการพัฒนาเกมมากมาย ใช้เวลาสักครู่เพื่อ อ่านเกี่ยวกับสิ่งเหล่านี้

งานที่ได้รับมอบหมาย

แสดงความคิดเห็นในโค้ดของคุณ


ข้อจำกัดความรับผิดชอบ:
เอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI Co-op Translator แม้ว่าเราจะพยายามอย่างเต็มที่เพื่อให้การแปลมีความถูกต้อง แต่โปรดทราบว่าการแปลอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่แม่นยำ เอกสารต้นฉบับในภาษาต้นทางควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ แนะนำให้ใช้บริการแปลภาษามนุษย์ที่เป็นมืออาชีพ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความที่ผิดพลาดซึ่งเกิดจากการใช้การแปลนี้