[DE] Game Development in nativem JavaScript

in deutsch •  7 years ago 

banner

Spiele in reinem JavaScript entwickeln scheint schwerer, als es eigentlich ist. Für ein einfaches Spiel benötigt es meist nicht mehr als die Game Loop, eine Menge Variablen und ein wenig Mathe. Im folgenden möchte ich Schritt für Schritt ein Einzelspieler Pong mit euch programmieren.


Was ist eine Game Loop?

Die "Game Loop" oder "Hauptschleife" ist der Teil eines Spiel, welcher immer und immer wieder ausgeführt wird. In dieser Schleife wird alles für das Spiel relevante wie das Zeichnen der Objekte oder das das Berechnen der Physik abgearbeitet. Bei der Implementierung der Game Loop muss darauf geachtet, werden, dass diese richtig erstellt wird. So ist es aufgrund von Timing-Problemen nicht gut, einfach eine while Schleife zu verwenden, da JavaScript single-threaded ist und somit alles andere neben der Schleife komplett blockiert wird. Stattdessen sollte die Funktion requestAnimationFrame verwendet werden. Mehr dazu hier.

Mathe?

Falls du zu den Leuten gehörst (und ich schließe mich da ein), welchen beim Wort "Mathe" schon ein Schauer über den Rücken läuft, kann ich dich beruhigen: die Mathematik, mit der wir im Folgenden konfrontiert werden, beschränkt sich auf die Grundrechenoperatoren.

Das Spiel

Das Grundgerüst

Als aller erstes müssen wir das Grundgerüst erstellen, in dem unser Spiel laufen wird. Erstellen wir also die Datei index.html:

  <!DOCTYPE html>
  <html lang="de">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Game Development in nativen JavaScript</title>

      <style>
        #game {
          background-color: #ccc;
          width: 480px;
          height: 320px;
          margin: auto auto;
          display: block;
        }
      </style>
    </head>
    <body>
      <canvas id="game" width="480" height="320"></canvas>
      <script src="game.js"></script>
    </body>
  </html>

Wie wir sehen gibt es inhaltlich nur drei relevante Dinge:

  1. das Styling für das Canvas
  2. das Canvas selbst
  3. das Script des Spiels

Neben der index.html erstellen wir noch die game.js, in welcher unsere Logik schlummern wird:

// get canvas and context
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');

function draw() {
  // draw red circle
  ctx.beginPath();
  ctx.arc(canvas.width / 2, canvas.height / 2, 10, 0, Math.PI * 2);
  ctx.fillStyle = 'red';
  ctx.fill();
  ctx.closePath();

  // start (endless) game loop
  requestAnimationFrame(draw);
}

// initialize game loop
requestAnimationFrame(draw);

An diesem Punkt der Entwicklung haben wir die ersten Schritte geschafft und werden mit dem ersten sichtbaren Resultat belohnt: ein roter Ball auf einem grauen Spielfeld.


game-dev_base-ball

Bewegung

Um den Ball in Bewegung zu setzen, erstellen wir vorerst ein paar Variablen und lagern den Code, um den Ball zu zeichnen, in seine eigene Funktion aus:

// get canvas and context
// ...

const sizes = { ball: 10 };
const colors = { ball: '#ff0000' };
let positions = {
  ball: {
    x: canvas.width / 2,
    y: canvas.height - (sizes.ball * 3),
  },
};
let velocity = {
  ball: { x: 2, y: -2 },
};

function drawCircle(); {
  // draw red circle
  ctx.beginPath();
  ctx.arc(positions.ball.x, positions.ball.y, sizes.ball, 0, Math.PI * 2);
  ctx.fillStyle = colors.ball;
  ctx.fill();
  ctx.closePath();
}

function draw() {
  drawCircle();

  // start (endless) game loop
  requestAnimationFrame(draw);
}

// ...

Das Ergebnis sollte ein roter Ball am unteren Ende des Spielfeldes sein.
Damit sich der Ball nun noch bewegt ergänzen wir die update Funktion:

// get canvas and context
// ...

function update() {
  position.ball.x += velocity.ball.x;
  position.ball.y += velocity.ball.y;
}

function draw() {
  drawCircle();
  update();
  // ...
}
// ...

Und nun bewegt sich der Ball. Aber was ist das?


game-dev_base-movement

Ich denke wir sollten unser Spielfeld nach jedem Zeichnen-Zyklus zurücksetzen. Dazu fügen wir einfach ctx.clearRect(0, 0, canvas.width, canvas.height); als erste Zeile in die draw Funktion ein.

game-dev_base-movement-fix

Kollisions-Erkennung

Jetzt wo sich der Ball bewegt, können wir uns der Kollisions-Erkennung widmen. Dazu prüfen wir einfach mit den uns zur Verfügung stehenden Variablen wo sich der Ball auf dem Spielfeld befindet und ändern je nach Standort die velocity Variable.

// get canvas and context
// ...

function update() {
  // check right & left wall
  if (
    positions.ball.x + velocity.ball.x > canvas.width - sizes.ball || // right wall
    positions.ball.x + velocity.ball.x < sizes.ball // left wall
  ) {
    velocity.ball.x = -velocity.ball.x; // inverse velocity on x-axis
  }

  // check top wall
  if (positions.ball.y < sizes.ball) {
    velocity.ball.y = -velocity.ball.y; // inverse velocity on y-axis
  }
  // ...
}

function draw() {
  drawCircle();
  update();
  // ...
}
// ...


game-dev_walls

Der Spieler

Nun fehlt uns noch der Spieler. Dazu erweitern wir unsere Variablen und erstellen uns eine neue Funktion, welche den Spieler zeichnet.

// get canvas and context
// ...

const sizes = {
  ball: 10,
  paddle: { width: 75, height: 10 },
};
const colors = {
  ball: '#ff0000',
  paddle: '#0000ff',
};
let positions = {
  ball: { x: canvas.width / 2, y: canvas.height - (sizes.ball * 3) },
  paddle: { x: (canvas.width - sizes.paddle.width) / 2, y: canvas.height - sizes.paddle.height },
};
let velocity = {
  ball: { x: 2, y: -2 },
  paddle: { x: 5 },
};

// ...

function drawPaddle() {
  ctx.beginPath();
  ctx.rect(positions.paddle.x, positions.paddle.y, sizes.paddle.width, sizes.paddle.height);
  ctx.fillStyle = colors.paddle;
  ctx.fill();
  ctx.closePath();
}

function draw() {
  drawCircle();
  drawPaddle();
  // ...
}
// ...

Und schon haben wir mit wenig Code unser Spieler Objekt erstellt:


game-dev_player

Spielerbewegung

Als nächstes implementieren wir die Bewegung des Spielers. Dazu horchen wir einfach auf die Events keydown und keyup, um zu identifizieren, ob Tasten gedrückt wurden und ändern dann wie bei dem Ball die Position des Spielers im Spielfeld.

// get canvas and context
// ...
let keyStates = {
  pressed: { left: false, right: false },
};
// ...

function updatePaddle() {
  if (keyStates.pressed.left && positions.paddle.x > 0) {
    positions.paddle.x -= velocity.paddle.x;
  }
  
  if (keyStates.pressed.right && positions.paddle.x + sizes.paddle.width < canvas.width) {
    positions.paddle.x += velocity.paddle.x;
  }
}

function update () {
  // ...
  updatePaddle();
  // ...
}
// ...
function keyDownHandler(event) {
  if (event.keyCode === 37) { // left key-code
    keyStates.pressed.left = true;
  } else if (event.keyCode === 39) { // right key-code
    keyStates.pressed.right = true;
  }
}

function keyUpHandler(event) {
  if (event.keyCode === 37) { // left key-code
    keyStates.pressed.left = false;
  } else if (event.keyCode === 39) { // right key-code
    keyStates.pressed.right = false;
  }
}

document.addEventListener('keydown', keyDownHandler, false);
document.addEventListener('keyup', keyUpHandler, false);
// ...

Mit ein wenig neuem Code können wir nun auch den Spieler bewegen:


game-dev_player-movement

Kollisions-Erkennung die zweite

Was nun noch fehlt ist die Überprüfung, ob der Ball unseren Spieler berührt. Dazu lagern wir den alten Kollisions-Erkennungs-Code aus der update Funktion aus und ergänzen ihn:

// ...
function updateBall() {
  // check right & left wall
  if (
    positions.ball.x + velocity.ball.x > canvas.width - sizes.ball || // right wall
    positions.ball.x + velocity.ball.x < sizes.ball // left wall
  ) {
    velocity.ball.x = -velocity.ball.x; // inverse velocity on x-axis
  }

  const collideWithPlayer = (
    positions.ball.y + velocity.ball.y > canvas.height - sizes.paddle.height - sizes.ball &&
    positions.ball.x + velocity.ball.x > positions.paddle.x && 
    positions.ball.x + velocity.ball.x < positions.paddle.x + sizes.paddle.width
  );

  // check top wall && player
  if (positions.ball.y < sizes.ball || collideWithPlayer) {
    velocity.ball.y = -velocity.ball.y; // inverse velocity on y-axis
  }
}
// ...
function update() {
  updateBall();
  // ...
}
// ...


game-dev_player-collision

Zusatz

Damit aus dem Hin und Her ein richtiges Spiel wird, ergänzen wir noch eine Punktzahl sowie einen Abbruch, wenn man den Ball nicht schafft zu erreichen:

// ...
let score = 0;
let lost = false;
// ...

function updateBall() {
  // ...
  if (positions.ball.y < sizes.ball || collideWithPlayer) {
    // if collision score up and increase speed
    if (collideWithPlayer) {
      score += 1;
      velocity.ball.x += Math.random();
      velocity.ball.y += Math.random();
      velocity.paddle.x = Math.abs(velocity.ball.x * 2.5); // only positive numbers
    }

    velocity.ball.y = -velocity.ball.y; // inverse velocity on y-axis
  } else if (positions.ball.y - sizes.ball > canvas.height && !lost) {
    lost = true;
    alert(`You win with ${score} points!`);
    window.location.reload();
  }
}
// ...
function drawScore() {
  ctx.font = "30px Arial";
  ctx.fillStyle = colors.text;
  ctx.fillText(`Score: ${score}`, 10, 30);
}
// ...
function draw() {
  // ...
  drawScore();
  // ...
}
// ...

Und schon haben wir ein simples Einzelspieler-Spiel, geschrieben in purem JavaSript:


game-dev_final

Der ganze Code der game.js ist in diesem Paste zu finden.


Du hast Fragen, Änderungsvorschläge oder Wünsche? Lass es mich in den Kommentaren wissen 😉
In dem Sinne, frohes Coden.



credits:@carlos-cabeza

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Sehr interessanter und ausführlicher Beitrag !
Freue mich wieder etwas von dir zu lesen :)

Hallo @drookyn, herzlich willkommen auf Steemit.

Wenn Du Fragen zu Steemit hast, oder Dich mit anderen „Steemians“ austauschen magst, schau einfach mal auf unserem Discord-Chat unter https://discord.gg/g6ktN45 vorbei. Mehr Informationen über den deutschsprachigen Discord-Chat findest Du in diesem Beitrag.

Wenn Du auf Deutsch schreibst, verwende immer #deutsch als einen der 5 Hashtags, um Deine Reichweite zu erhöhen.

Unter dem folgenden Link findest Du einige Anleitungen, die Dir den Einstieg in das Steem-Universum deutlich erleichtern werden: Deutschsprachige Tutorials für Steemit-Neulinge: Ein Überblick