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/de/6-space-game/3-moving-elements-around
Lee Stott 2daab5271b
Update Quiz Link
3 weeks ago
..
README.md Update Quiz Link 3 weeks ago
assignment.md 🌐 Update translations via Co-op Translator 4 weeks ago

README.md

Baue ein Weltraumspiel Teil 3: Bewegung hinzufügen

Quiz vor der Lektion

Quiz vor der Lektion

Spiele machen erst dann richtig Spaß, wenn Aliens über den Bildschirm laufen! In diesem Spiel werden wir zwei Arten von Bewegungen nutzen:

  • Tastatur-/Mausbewegung: wenn der Benutzer mit der Tastatur oder Maus interagiert, um ein Objekt auf dem Bildschirm zu bewegen.
  • Spielinduzierte Bewegung: wenn das Spiel ein Objekt in bestimmten Zeitintervallen bewegt.

Wie bewegt man also Dinge auf einem Bildschirm? Es dreht sich alles um kartesische Koordinaten: Wir ändern die Position (x, y) des Objekts und zeichnen dann den Bildschirm neu.

Typischerweise sind folgende Schritte notwendig, um Bewegung auf einem Bildschirm zu realisieren:

  1. Setze eine neue Position für ein Objekt; dies ist notwendig, damit das Objekt als bewegt wahrgenommen wird.
  2. Leere den Bildschirm, der Bildschirm muss zwischen den Zeichnungen geleert werden. Dies kann durch das Zeichnen eines Rechtecks mit einer Hintergrundfarbe erfolgen.
  3. Zeichne das Objekt neu an der neuen Position. Dadurch wird das Objekt schließlich von einer Position zur anderen bewegt.

So könnte das im Code aussehen:

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

Kannst du dir vorstellen, warum das Neuzeichnen deines Helden viele Male pro Sekunde zu Leistungseinbußen führen könnte? Lies mehr über Alternativen zu diesem Muster.

Tastaturereignisse behandeln

Ereignisse werden behandelt, indem spezifische Ereignisse mit Code verknüpft werden. Tastaturereignisse werden für das gesamte Fenster ausgelöst, während Mausereignisse wie ein click mit einem bestimmten Element verbunden werden können. In diesem Projekt werden wir Tastaturereignisse verwenden.

Um ein Ereignis zu behandeln, musst du die Methode addEventListener() des Fensters verwenden und ihr zwei Eingabeparameter übergeben. Der erste Parameter ist der Name des Ereignisses, z. B. keyup. Der zweite Parameter ist die Funktion, die als Ergebnis des Ereignisses aufgerufen werden soll.

Hier ist ein Beispiel:

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

Für Tastaturereignisse gibt es zwei Eigenschaften des Ereignisses, die du verwenden kannst, um zu sehen, welche Taste gedrückt wurde:

  • key, dies ist eine String-Darstellung der gedrückten Taste, z. B. ArrowUp.
  • keyCode, dies ist eine numerische Darstellung, z. B. 37, was ArrowLeft entspricht.

Die Manipulation von Tastaturereignissen ist auch außerhalb der Spieleentwicklung nützlich. Welche anderen Anwendungen kannst du dir für diese Technik vorstellen?

Besondere Tasten: ein Hinweis

Es gibt einige besondere Tasten, die das Fenster beeinflussen. Das bedeutet, dass, wenn du ein keyup-Ereignis hörst und diese besonderen Tasten benutzt, um deinen Helden zu bewegen, auch ein horizontales Scrollen ausgelöst wird. Aus diesem Grund möchtest du möglicherweise dieses eingebaute Browserverhalten deaktivieren, während du dein Spiel entwickelst. Dafür benötigst du Code wie diesen:

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

Der obige Code stellt sicher, dass die Pfeiltasten und die Leertaste ihr Standardverhalten deaktivieren. Der Mechanismus zur Deaktivierung erfolgt, wenn wir e.preventDefault() aufrufen.

Spielinduzierte Bewegung

Wir können Dinge von selbst bewegen, indem wir Timer wie die Funktionen setTimeout() oder setInterval() verwenden, die die Position des Objekts bei jedem Tick oder Zeitintervall aktualisieren. So könnte das aussehen:

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

Die Spielschleife

Die Spielschleife ist ein Konzept, das im Wesentlichen eine Funktion ist, die in regelmäßigen Abständen aufgerufen wird. Sie wird als Spielschleife bezeichnet, da alles, was für den Benutzer sichtbar sein soll, in der Schleife gezeichnet wird. Die Spielschleife verwendet alle Spielobjekte, die Teil des Spiels sind, und zeichnet sie, es sei denn, sie sollten aus irgendeinem Grund nicht mehr Teil des Spiels sein. Zum Beispiel, wenn ein Objekt ein Feind ist, der von einem Laser getroffen wird und explodiert, ist es nicht mehr Teil der aktuellen Spielschleife (du wirst mehr darüber in den nächsten Lektionen lernen).

So könnte eine Spielschleife typischerweise im Code aussehen:

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

Die obige Schleife wird alle 200 Millisekunden aufgerufen, um die Leinwand neu zu zeichnen. Du kannst das Intervall wählen, das für dein Spiel am sinnvollsten ist.

Fortsetzung des Weltraumspiels

Du wirst den bestehenden Code erweitern. Entweder beginnst du mit dem Code, den du in Teil I abgeschlossen hast, oder du verwendest den Code aus Teil II - Starter.

  • Bewegung des Helden: Du wirst Code hinzufügen, um sicherzustellen, dass du den Helden mit den Pfeiltasten bewegen kannst.
  • Bewegung der Feinde: Du wirst auch Code hinzufügen, um sicherzustellen, dass sich die Feinde mit einer bestimmten Geschwindigkeit von oben nach unten bewegen.

Empfohlene Schritte

Finde die Dateien, die für dich im Unterordner your-work erstellt wurden. Sie sollten Folgendes enthalten:

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

Starte dein Projekt im Ordner your_work, indem du Folgendes eingibst:

cd your-work
npm start

Das obige startet einen HTTP-Server unter der Adresse http://localhost:5000. Öffne einen Browser und gib diese Adresse ein. Im Moment sollten der Held und alle Feinde angezeigt werden; noch bewegt sich nichts!

Code hinzufügen

  1. Füge dedizierte Objekte für hero, enemy und game object hinzu, die x- und y-Eigenschaften haben. (Erinnere dich an den Abschnitt über Vererbung oder Komposition).

    TIPP: game object sollte das Objekt sein, das x und y sowie die Fähigkeit hat, sich selbst auf eine Leinwand zu zeichnen.

    Tipp: Beginne damit, eine neue GameObject-Klasse mit ihrem Konstruktor wie unten dargestellt hinzuzufügen, und zeichne sie dann auf die Leinwand:

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

    Erweitere nun dieses GameObject, um den Helden und den Feind zu erstellen.

    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. Füge Ereignis-Handler für Tasten hinzu, um die Navigation (Bewegung des Helden nach oben/unten, links/rechts) zu steuern.

    DENKE DARAN: Es handelt sich um ein kartesisches System, oben links ist 0,0. Denke auch daran, Code hinzuzufügen, um das Standardverhalten zu deaktivieren.

    Tipp: Erstelle deine onKeyDown-Funktion und verknüpfe sie mit dem Fenster:

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

    Überprüfe zu diesem Zeitpunkt die Konsole deines Browsers und beobachte, wie die Tastenanschläge protokolliert werden.

  3. Implementiere das Pub-Sub-Muster, um deinen Code sauber zu halten, während du die verbleibenden Teile umsetzt.

    Um diesen letzten Teil umzusetzen, kannst du:

    1. Einen Ereignis-Listener für das Fenster hinzufügen:

       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. Eine EventEmitter-Klasse erstellen, um Nachrichten zu veröffentlichen und zu abonnieren:

      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. Konstanten hinzufügen und den EventEmitter einrichten:

      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. Das Spiel initialisieren

    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. Richte die Spielschleife ein

    Refaktoriere die window.onload-Funktion, um das Spiel zu initialisieren und eine Spielschleife in einem geeigneten Intervall einzurichten. Du wirst auch einen Laserstrahl hinzufügen:

    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. Füge Code hinzu, um Feinde in bestimmten Intervallen zu bewegen.

    Refaktoriere die Funktion createEnemies(), um die Feinde zu erstellen und sie in die neue GameObjects-Klasse zu schieben:

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

    und füge eine Funktion createHero() hinzu, um einen ähnlichen Prozess für den Helden durchzuführen.

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

    und schließlich füge eine Funktion drawGameObjects() hinzu, um das Zeichnen zu starten:

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

    Deine Feinde sollten beginnen, auf dein Raumschiff zuzugehen!


🚀 Herausforderung

Wie du sehen kannst, kann dein Code zu einem "Spaghetti-Code" werden, wenn du Funktionen, Variablen und Klassen hinzufügst. Wie kannst du deinen Code besser organisieren, damit er lesbarer wird? Skizziere ein System, um deinen Code zu organisieren, auch wenn er weiterhin in einer Datei bleibt.

Quiz nach der Lektion

Quiz nach der Lektion

Überprüfung & Selbststudium

Während wir unser Spiel ohne Frameworks schreiben, gibt es viele JavaScript-basierte Canvas-Frameworks für die Spieleentwicklung. Nimm dir Zeit, um darüber zu lesen.

Aufgabe

Kommentiere deinen Code

Haftungsausschluss:
Dieses Dokument wurde mit dem KI-Übersetzungsdienst Co-op Translator übersetzt. Obwohl wir uns um Genauigkeit bemühen, weisen wir darauf hin, dass automatisierte Übersetzungen Fehler oder Ungenauigkeiten enthalten können. Das Originaldokument in seiner ursprünglichen Sprache sollte als maßgebliche Quelle betrachtet werden. Für kritische Informationen wird eine professionelle menschliche Übersetzung empfohlen. Wir übernehmen keine Haftung für Missverständnisse oder Fehlinterpretationen, die sich aus der Nutzung dieser Übersetzung ergeben.