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 voor de les

Quiz voor de les

Spellen worden pas echt leuk wanneer er aliens rondrennen op het scherm! In dit spel maken we gebruik van twee soorten bewegingen:

  • Toetsenbord/Muis beweging: wanneer de gebruiker interactie heeft met het toetsenbord of de muis om een object op het scherm te verplaatsen.
  • Spel-geïnduceerde beweging: wanneer het spel een object beweegt met een bepaalde tijdsinterval.

Hoe verplaatsen 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 heb je 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 te ervaren.
  2. Maak het scherm leeg, het scherm moet tussen de tekeningen worden leeggemaakt. Dit kan door een rechthoek te tekenen die we vullen met een achtergrondkleur.
  3. Teken het object opnieuw op de nieuwe locatie. Door dit te doen, verplaatsen we het object uiteindelijk van de ene locatie naar de andere.

Hier is hoe dat eruit kan zien in code:

//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 een reden bedenken waarom het herhaaldelijk tekenen van je held meerdere frames per seconde prestatiekosten kan veroorzaken? 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 gedurende dit project.

Om een gebeurtenis af te handelen, moet je de addEventListener()-methode van het venster gebruiken en deze voorzien van twee invoerparameters. 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 toetsenbordgebeurtenissen 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.

Manipulatie van toetsenbordgebeurtenissen is nuttig buiten gameontwikkeling. 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. Dat betekent dat als je luistert naar een keyup-gebeurtenis en je deze speciale toetsen gebruikt om je held te verplaatsen, het ook horizontaal scrollen zal uitvoeren. Om die reden wil je mogelijk dit ingebouwde browsergedrag uitschakelen terwijl je je spel ontwikkelt. Je hebt 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 pijltjestoetsen en de spatiebalk hun standaard gedrag uitschakelen. Het uitschakel-mechanisme treedt in werking wanneer we e.preventDefault() aanroepen.

Spel-geïnduceerde beweging

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

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

De spel-lus

De spel-lus is een concept dat in wezen een functie is die op regelmatige intervallen wordt aangeroepen. Het wordt de spel-lus genoemd omdat alles wat zichtbaar moet zijn voor de gebruiker in de lus wordt getekend. De spel-lus maakt gebruik van alle spelobjecten die deel uitmaken van het spel en tekent ze allemaal, tenzij ze om een bepaalde reden niet meer 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 spel-lus (je leert hier meer over in latere lessen).

Hier is hoe een spel-lus 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 lus wordt elke 200 milliseconden aangeroepen om het canvas opnieuw te tekenen. Je kunt het beste interval kiezen dat logisch is voor jouw spel.

Verder met het Ruimtespel

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.

  • Beweeg de held: je voegt code toe om ervoor te zorgen dat je de held kunt bewegen met de pijltjestoetsen.
  • Beweeg vijanden: je moet ook code toevoegen om ervoor te zorgen dat de vijanden van boven naar beneden bewegen met een bepaalde snelheid.

Aanbevolen stappen

Vind de bestanden die voor je zijn aangemaakt in de submap your-work. Het zou het volgende moeten bevatten:

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

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

cd your-work
npm start

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

Code toevoegen

  1. Voeg toegewijde objecten toe voor hero, enemy en game object, ze moeten x- en y-eigenschappen hebben. (Herinner je het gedeelte over Erfenis 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 zijn constructor zoals hieronder aangegeven, en teken het 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 handlers voor toetsenbordgebeurtenissen toe om navigatie met toetsen te verwerken (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 toetsaanslagen die worden geregistreerd.

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

    Om dit laatste deel te doen, kun je:

    1. Voeg een event listener toe 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. Maak een EventEmitter-klasse 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. Voeg constanten toe en stel de EventEmitter in:

      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. Initialiseer het spel

    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. Stel de spel-lus in

    Refactor de window.onload-functie om het spel te initialiseren en een spel-lus op een goed interval in te stellen. 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 op een bepaald interval te 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 vergelijkbaar 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 beginnen te benaderen!


🚀 Uitdaging

Zoals je kunt zien, kan je code veranderen in 'spaghetti-code' wanneer je functies, variabelen en klassen begint toe te voegen. Hoe kun je je code beter organiseren zodat deze beter leesbaar is? 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

Review & Zelfstudie

Hoewel we ons spel schrijven zonder frameworks, zijn er veel op JavaScript gebaseerde canvas-frameworks voor gameontwikkeling. Neem de tijd om hierover te lezen.

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, willen we u erop wijzen dat geautomatiseerde vertalingen fouten of onnauwkeurigheden kunnen bevatten. Het originele document in de oorspronkelijke taal moet worden beschouwd als de gezaghebbende bron. Voor kritieke informatie wordt professionele menselijke vertaling aanbevolen. Wij zijn niet aansprakelijk voor misverstanden of verkeerde interpretaties die voortvloeien uit het gebruik van deze vertaling.