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/nl/6-space-game/3-moving-elements-around/README.md

14 KiB

Bouw een Ruimtespel Deel 3: Beweging Toevoegen

Quiz Voorafgaand aan de Les

Quiz voorafgaand aan de les

Games worden pas echt leuk als er aliens over het scherm bewegen! In dit spel maken we gebruik van twee soorten bewegingen:

  • Toetsenbord/Muisbeweging: wanneer de gebruiker een object op het scherm beweegt door interactie met het toetsenbord of de muis.
  • Spelgeïnduceerde beweging: wanneer het spel een object met een bepaald tijdsinterval beweegt.

Maar hoe bewegen we dingen op een scherm? Het draait allemaal om cartesiaanse coördinaten: we veranderen de locatie (x, y) van het object en tekenen vervolgens het scherm opnieuw.

Meestal zijn de volgende stappen nodig om beweging op een scherm te realiseren:

  1. Stel een nieuwe locatie in voor een object; dit is nodig om het object als verplaatst waar te nemen.
  2. Maak het scherm leeg, het scherm moet tussen de tekeningen door worden gewist. Dit kan door een rechthoek te tekenen die we vullen met een achtergrondkleur.
  3. Teken het object opnieuw op de nieuwe locatie. Hiermee bereiken we uiteindelijk dat het object van de ene naar de andere locatie beweegt.

Hier is hoe dat er in code uit kan zien:

//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);

Kun je bedenken waarom het herhaaldelijk opnieuw tekenen van je held met veel frames per seconde prestatiekosten met zich mee kan brengen? Lees meer over alternatieven voor dit patroon.

Toetsenbordgebeurtenissen Afhandelen

Je handelt gebeurtenissen af door specifieke gebeurtenissen aan code te koppelen. Toetsenbordgebeurtenissen worden geactiveerd op het hele venster, terwijl muisgebeurtenissen zoals een klik kunnen worden gekoppeld aan het klikken op een specifiek element. We zullen toetsenbordgebeurtenissen gebruiken in dit project.

Om een gebeurtenis af te handelen, gebruik je de addEventListener()-methode van het venster en geef je twee invoerparameters op. De eerste parameter is de naam van de gebeurtenis, bijvoorbeeld keyup. De tweede parameter is de functie die moet worden aangeroepen als gevolg van de gebeurtenis.

Hier is een voorbeeld:

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

Voor toetsgebeurtenissen zijn er twee eigenschappen op de gebeurtenis die je kunt gebruiken om te zien welke toets is ingedrukt:

  • key, dit is een stringrepresentatie van de ingedrukte toets, bijvoorbeeld ArrowUp.
  • keyCode, dit is een numerieke representatie, bijvoorbeeld 37, wat overeenkomt met ArrowLeft.

Het manipuleren van toetsgebeurtenissen is ook nuttig buiten game-ontwikkeling. Aan welke andere toepassingen kun je denken voor deze techniek?

Speciale toetsen: een kanttekening

Er zijn enkele speciale toetsen die invloed hebben op het venster. Dit betekent dat als je luistert naar een keyup-gebeurtenis en je deze speciale toetsen gebruikt om je held te bewegen, dit ook horizontaal scrollen veroorzaakt. Daarom wil je mogelijk dit ingebouwde browsergedrag uitschakelen terwijl je je spel ontwikkelt. Hiervoor heb je code zoals deze nodig:

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);

De bovenstaande code zorgt ervoor dat de pijltjestoetsen en de spatiebalk hun standaard gedrag uitschakelen. Het uitschakel-mechanisme vindt plaats wanneer we e.preventDefault() aanroepen.

Spelgeïnduceerde Beweging

We kunnen objecten automatisch laten bewegen door timers te gebruiken, zoals de setTimeout()- of setInterval()-functie, die de locatie van het object bij elke tik of tijdsinterval bijwerken. Hier is hoe dat eruit kan zien:

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

De Spelloop

De spelloop is een concept dat in wezen een functie is die op regelmatige intervallen wordt aangeroepen. Het wordt de spelloop genoemd omdat alles wat zichtbaar moet zijn voor de gebruiker in de loop wordt getekend. De spelloop maakt gebruik van alle spelobjecten die deel uitmaken van het spel en tekent ze, tenzij ze om een bepaalde reden niet langer deel uitmaken van het spel. Bijvoorbeeld, als een object een vijand is die door een laser is geraakt en explodeert, maakt het geen deel meer uit van de huidige spelloop (je leert hier meer over in latere lessen).

Hier is hoe een spelloop er typisch uitziet, uitgedrukt in code:

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);

De bovenstaande loop wordt elke 200 milliseconden aangeroepen om het canvas opnieuw te tekenen. Je kunt het beste interval kiezen dat logisch is voor jouw spel.

Het Ruimtespel Voortzetten

Je neemt de bestaande code en breidt deze uit. Begin met de code die je hebt voltooid tijdens deel I of gebruik de code in Deel II - starter.

  • De held bewegen: je voegt code toe om ervoor te zorgen dat je de held kunt bewegen met de pijltjestoetsen.
  • Vijanden bewegen: je voegt ook code toe om ervoor te zorgen dat de vijanden van boven naar beneden bewegen met een bepaald tempo.

Aanbevolen Stappen

Zoek de bestanden die voor je zijn aangemaakt in de map your-work. Deze map zou het volgende moeten bevatten:

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

Start je project in de map your_work door het volgende in te typen:

cd your-work
npm start

Hiermee start je een HTTP-server op adres http://localhost:5000. Open een browser en voer dat adres in. Op dit moment zou het de held en alle vijanden moeten weergeven; er beweegt nog niets - nog niet!

Code Toevoegen

  1. Voeg toegewijde objecten toe voor hero, enemy en game object. Deze moeten x- en y-eigenschappen hebben. (Onthoud het gedeelte over Overerving of compositie).

    TIP: game object moet degene zijn met x en y en de mogelijkheid om zichzelf op een canvas te tekenen.

    tip: begin met het toevoegen van een nieuwe GameObject-klasse met de constructor zoals hieronder, en teken deze vervolgens op het canvas:

    
    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);
      }
    }
    

    Breid nu dit GameObject uit om de Hero en Enemy te maken.

    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. Voeg toetsgebeurtenis-handlers toe om toetsnavigatie af te handelen (beweeg de held omhoog/omlaag, links/rechts).

    ONTHOUD: het is een cartesiaans systeem, linksboven is 0,0. Vergeet ook niet code toe te voegen om standaardgedrag te stoppen.

    tip: maak je onKeyDown-functie en koppel deze aan het venster:

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

    Controleer op dit punt je browserconsole en bekijk de geregistreerde toetsaanslagen.

  3. Implementeer het Pub-sub patroon, dit houdt je code overzichtelijk terwijl je de resterende delen volgt.

    Om dit laatste deel te doen, kun je:

    1. Een gebeurtenislistener toevoegen aan het venster:

       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. Een EventEmitter-klasse maken om berichten te publiceren en te abonneren:

      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. Constanten toevoegen en de EventEmitter instellen:

      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. Het spel initialiseren

    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. De spelloop instellen

    Refactor de window.onload-functie om het spel te initialiseren en een spelloop in te stellen op een goed interval. Je voegt ook een laserstraal toe:

    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. Voeg code toe om vijanden met een bepaald interval te laten bewegen.

    Refactor de createEnemies()-functie om de vijanden te maken en ze in de nieuwe gameObjects-klasse te plaatsen:

    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);
        }
      }
    }
    

    en voeg een createHero()-functie toe om een soortgelijk proces voor de held uit te voeren.

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

    en voeg ten slotte een drawGameObjects()-functie toe om het tekenen te starten:

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

    Je vijanden zouden nu je heldenruimteschip moeten naderen!


🚀 Uitdaging

Zoals je kunt zien, kan je code veranderen in 'spaghetticode' wanneer je functies, variabelen en klassen toevoegt. Hoe kun je je code beter organiseren zodat deze leesbaarder wordt? Schets een systeem om je code te organiseren, zelfs als het nog steeds in één bestand staat.

Quiz Na de Les

Quiz na de les

Herziening & Zelfstudie

Hoewel we ons spel schrijven zonder frameworks, zijn er veel op JavaScript gebaseerde canvas-frameworks voor game-ontwikkeling. Neem de tijd om wat te lezen over deze.

Opdracht

Geef commentaar op je code


Disclaimer:
Dit document is vertaald met behulp van de AI-vertalingsservice Co-op Translator. Hoewel we streven naar nauwkeurigheid, dient u zich ervan bewust te zijn dat geautomatiseerde vertalingen fouten of onnauwkeurigheden kunnen bevatten. Het originele document in zijn oorspronkelijke taal moet worden beschouwd als de gezaghebbende bron. Voor cruciale informatie wordt professionele menselijke vertaling aanbevolen. Wij zijn niet aansprakelijk voor misverstanden of verkeerde interpretaties die voortvloeien uit het gebruik van deze vertaling.