Kleiner JavaScript Kurs

Objekte (Pong - Spiel)

JavaScript lässt sich sowohl prozedural, als auch objektorientiert programmieren. Beim Programmieren eines Pong - Spiels (Tennis / Ping-Pong) werden wir uns einige einfache Grundlagen der Objektorientierten Programmierung ansehen.
Als "Spielfläche verwenden wir ein Canvas - Element (mit Rahmen und Hintergrundfarbe). Die Steuerung erfolgt zunächst mit den Pfeiltasten der Tastatur (wir können später noch Buttons oder eine Touch - Steuerung auf der HTML - Seite hinzufügen). Ggf. benötigen wir auch noch einen Button zum Starten des Spiels.

<body>
    <canvas id="myCanvas" width="600" height="400"  
          style="border: 2px solid #000000; background-color: #000000;">
    </canvas><br>
</body>

Im Script bereiten wir zunächst das Canvas zum Zeichnen vor:

<script>
  var canvas = document.getElementById("myCanvas");
  var ctx = canvas.getContext("2d");
  var spielfeld={breite: canvas.width, hoehe: canvas.height, farbe: "#000000"};
</script>

Unsere erste Objektvariable ist das Spielfeld. In geschweiften Klammern können wir die Eigenschaften des Spielfelds, z.B. die Breite, Höhe, Farbe, usw. festlegen. Die spätere Verwendung dieser Eigenschaften funktioniert ähnlich wie bei Arrays. Statt eines numerischen Index werden hier aber Textbezeichnungen verwendet. Das funktioniert, wie bei Arrays mit eckigen Klammern, oder besser mit einem "Punkt-Trenner", also Objektvariable.Eigenschaft.

alert(spielfeld["hoehe"]);
alert(spielfeld.breite);

Für unser Pong - Spiel benötigen wir noch weitere Objekte, u.a. einen Ball, 2 Schläger und optional einen Punktezähler und ein "Netz".
Definieren wir zunächst den Ball; das Ball - Objekt hat die Eigenschaften x und y für den Kreismittelpunkt, radius (Radius des Balls) und farbe (Füllfarbe des Balls). Außerdem bekommt der Ball noch eine Methode "zeichnen()" die den Ball auf das Canvas zeichnet.

var ball={x: spielfeld.breite/2, y: spielfeld.hoehe/2, radius: 10, farbe: "#ff0000",
       zeichnen: function (){
                  ctx.beginPath();
                  ctx.fillStyle = this.farbe;
                  ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
                  ctx.fill();
                  ctx.closePath();
                  ctx.stroke();
}};
ball.zeichnen();

Da wir die Eigenschaften des Ball - Objekts durch Zuweisung ändern können, ist es jetzt möglich den Ball an eine beliebige Stelle zu zeichnen.

ball.x=20;
ball.y=30;
ball.zeichnen();

Für die beiden Schläger (für den Spieler und der PC) erstellen wir zunächst ebenfalls ein Objekt als Muster Klasse. Die Klassendefinition sieht etwas anders aus, als beim Ball - Objekt. Der Klassenname ("Schlaeger") beginnt, im Gegensatz zu Variablen- oder Funktions - Namen, mit einem Großbuchstaben.

function Schlaeger(){this.x=10; this.y=spielfeld.hoehe/2-25; 
                   this.breite=10; this.hoehe=50;
                   this.farbe="#ffffff";
                   this.zeichnen=function(){
                      ctx.fillStyle = this.farbe;
                      ctx.fillRect(this.x, this.y, this.breite, this.hoehe);
                   };
}

Die beiden Schläger "spieler" und "pc" werden dann als Objekte vom Typ Schläger erstellt. Beim PC - Schläger muss dann noch die x-Position geändert werden.

var spieler=new Schlaeger();
spieler.zeichnen();
var pc=new Schlaeger();
pc.x=spielfeld.breite-10-pc.breite;
pc.zeichnen();

Weitere Spielelemente (Punktezähler, Netz, ...) können ebenfalls als Objekte erstelltt werden.

var punkte={spielerX: spielfeld.breite/4, pcX: spielfeld.breite/4*3, y: spielfeld.hoehe/5,
          spielerPunkte: 0, pcPunkte: 0,
          anzeigen: function(){
            ctx.font = "40px Georgia";
            ctx.fillText(this.spielerPunkte, this.spielerX, this.y);
            ctx.fillText(this.pcPunkte, this.pcX, this.y);
            }
         }

Jetzt fehlt nur noch die Bewegung im Spiel. Wie bereits bei anderen Spielen werden wir in einer Schleife
  • den Canvas löschen
  • den Ball an der aktuellen Position anzeigen
  • die Schläger zeichnen
  • die nächste Position berechnen und dabei Kollisionen mit den Schlägern und den Rändern berücksichtigen
  • Punkte zählen und anzeigen
Beginnen wir mit der Bewegung / Neuberechnung der Position des Balls. Die Startgeschwindigkeit / -richtung ermitteln wir mit dem Zufallsgenerator. Außerdem habe ich ein Array var richtung=[1, -1] definiert, damit die Richtung auch negativ sein kann.

var richtung=[1, -1];
var ball={x: spielfeld.breite/2, y: spielfeld.hoehe/2, radius: 10, farbe: "#ff0000",
        richtungX: (Math.floor(Math.random()*2)+3)*richtung[Math.floor(Math.random()*2)], 
        richtungY: (Math.floor(Math.random()*2)+3)*richtung[Math.floor(Math.random()*2)],
       zeichnen: function (){
                  ctx.beginPath();
                  ctx.fillStyle = this.farbe;
                  ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
                  ctx.fill();
                  ctx.closePath();
                  ctx.stroke();
                    },
        bewegen: function (){
                  this.x+=this.richtungX;
                  this.y+=this.richtungY;
                  if(this.y-this.radius<=0 || this.y+this.radius>=spielfeld.hoehe){
                    this.richtungY=this.richtungY*(-1);
                    this.y+=this.richtungY;
                    }
                  }
        }

Auch das Abprallen des Balles an der oberen (y=0) und unteren (y=spielfeld.hoehe) Seite des Spielfelds ist in der Methode ball.bewegen() geregelt.

Die GameLoop sieht jetzt so aus:

function gameLoop(){
    ctx.fillStyle = "#000000";
    ctx.fillRect(0, 0, spielfeld.breite, spielfeld.hoehe);
    ball.zeichnen();
    spieler.zeichnen();
    pc.zeichnen();
    ball.bewegen();
    }
var intervalID = window.setInterval(gameLoop, 25);

Der Schläger "spieler" wird mit den Pfeiltasten nach oben und unten gesteuert. Die Funktion wird duch einen "keydown" - Event ausgelöst:

document.addEventListener('keydown', spielerSteuerung);
function spielerSteuerung(event){
  const tasteOben=38;
  const tasteUnten=40;
  const tasteESC=27;
  var tastenDruck=event.keyCode;
  if(tastenDruck==tasteOben){
    spieler.y-=4;
  }
  if(tastenDruck==tasteUnten){
    spieler.y+=4;
  }
  if(tastenDruck==tasteESC){
    window.clearInterval(intervalID);
  }
}

Die Steuerung des PC-Schlägers übernimmt der Computer. Die Steuerung darf nicht zu gut sein (Geschwindigkeit beschränken, ...) sonst haben wir keine Gewinnchance.

function pcSteuerung(){
    if(ball.y>pc.y-pc.hoehe/2){
      pc.y+=4;
    }
    if(ball.y<pc.y+pc.hoehe/2){
      pc.y-=4;
    }
}

Die Funktion pcSteuerung() muss noch in die gameLoop() eingefügt werden.

Als nächste wollen wir die Kollision des Balles mit einem Schläger programmieren. Damit die Formeln übersichtlicher werden, definieren wir einige Hilfsvariablen für den Ball und die Schläger. Dabei betrachten wir nur den Schläger, in dessen Hälfte der Ball gerade ist.

function schlaegerCollision(){
if(ball.x>spielfeld.breite/2){
  schlaeger=pc;}
else{schlaeger=spieler;}
ball.unten=ball.y+ball.radius;
ball.oben=ball.y-ball.radius;
ball.rechts=ball.x+ball.radius;
ball.links=ball.x-ball.radius;
schlaeger.oben=schlaeger.y;
schlaeger.unten=schlaeger.y+schlaeger.hoehe;
schlaeger.links=schlaeger.x;
schlaeger.rechts=schlaeger.x+schlaeger.breite;
if(ball.unten>=schlaeger.oben &&
   ball.oben<=schlaeger.unten &&
   ball.rechts>=schlaeger.links &&
   ball.links<=schlaeger.rechts){
  ball.richtungX=ball.richtungX*(-1);
  ball.x+=ball.richtungX;
  ball.y+=ball.richtungY;
}
}

Auch die Funktion schlaegerCollision() muss noch in die gameLoop().

Funktion punkteZaehlen() und neustart()
Wenn der Ball das Spielfeld links oder rechts verlässt, gibt es einen Punkt für den Gegner. Außerdem muss der Ball wieder ins Spiel.

function punkteZaehlen(){
    if(ball.x<=0){
      punkte.pcPunkte+=1;
      neustart();
    }
    if(ball.x>=spielfeld.breite){
      punkte.spielerPunkte+=1;
      neustart();
    }
}
  function neustart(){
    ball.x=spielfeld.breite/2;
    ball.y=spielfeld.hoehe/2;
    ball.richtungX=(Math.floor(Math.random()*2)+3)*richtung[Math.floor(Math.random()*2)]; 
    ball.richtungY=(Math.floor(Math.random()*2)+3)*richtung[Math.floor(Math.random()*2)];
}

Weiterentwicklung des Spiels z.B.
  • Verschönerung der html - Seite, z.B. Überschrift, Anleitung, Hintergrundfarbe, ...
  • das Spiel wird beim Klick auf das Spielfeld gestartet
  • der Ball wird immer schneller
  • das Spiel wird beendet, sobald ein Spieler 10 Punkte hat
  • Touch - Steuerung für Smartphone und Tablet


So könnte das Pong - Spiel aussehen: Pong - Spiel


Hier die HTML-Seite mit dem kompletten Programm


<!DOCTYPE html>
<html lang="de">
    <head>
        <meta charset="utf-8">
                        
    </head>
                        
    <body>
      <canvas id="myCanvas" width="600" height="400"  
            style="border: 2px solid #000000; background-color: #000000;">
      </canvas><br>
	</body>
	<script>
      var canvas = document.getElementById("myCanvas");
      var ctx = canvas.getContext("2d");
      var spielfeld={breite: canvas.width, hoehe: canvas.height, farbe: "#000000"};
      //alert(spielfeld["hoehe"]);
	  //alert(spielfeld.breite);
      var richtung=[1, -1];
      var ball={x: spielfeld.breite/2, y: spielfeld.hoehe/2, radius: 10, farbe: "#ff0000",
                richtungX: (Math.floor(Math.random()*2)+3)*richtung[Math.floor(Math.random()*2)], 
                richtungY: (Math.floor(Math.random()*2)+3)*richtung[Math.floor(Math.random()*2)],
               zeichnen: function (){
                          ctx.beginPath();
                          ctx.fillStyle = this.farbe;
                          ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
                          ctx.fill();
                          ctx.closePath();
                          ctx.stroke();
      						},
                bewegen: function (){
                          this.x+=this.richtungX;
                  		  this.y+=this.richtungY;
                  		  if(this.y<=0 || this.y>=spielfeld.hoehe){
                            this.richtungY=this.richtungY*(-1);
                            this.y+=this.richtungY;
                          }
      						}
               }

      function Schlaeger(){this.x=10; this.y=spielfeld.hoehe/2-25; 
                           this.breite=10; this.hoehe=50;
                       	   this.farbe="#ffffff";
                           this.zeichnen=function(){
                              ctx.fillStyle = this.farbe;
                              ctx.fillRect(this.x, this.y, this.breite, this.hoehe);
                           };
      }
      var spieler=new Schlaeger();
      var pc=new Schlaeger();
      pc.x=spielfeld.breite-10-pc.breite;
      
      var punkte={spielerX: spielfeld.breite/4, pcX: spielfeld.breite/4*3, y: spielfeld.hoehe/5,
                  spielerPunkte: 0, pcPunkte: 0,
                  anzeigen: function(){
                    ctx.font = "40px Georgia";
                    ctx.fillText(this.spielerPunkte, this.spielerX, this.y);
                    ctx.fillText(this.pcPunkte, this.pcX, this.y);
                  }}
	
      function gameLoop(){
        ctx.fillStyle = "#000000";
        ctx.fillRect(0, 0, spielfeld.breite, spielfeld.hoehe);
        ball.zeichnen();
        spieler.zeichnen();
        pc.zeichnen();
        punkte.anzeigen();
        ball.bewegen();
        pcSteuerung();
        schlaegerCollision();
        punkteZaehlen();
      }
      var intervalID = window.setInterval(gameLoop, 25);
      document.addEventListener('keydown', spielerSteuerung);
      function spielerSteuerung(event){
          const tasteOben=38;
          const tasteUnten=40;
          const tasteESC=27;
          var tastenDruck=event.keyCode;
          if(tastenDruck==tasteOben){
            spieler.y-=4;
          }
          if(tastenDruck==tasteUnten){
            spieler.y+=4;
          }
          if(tastenDruck==tasteESC){
            window.clearInterval(intervalID);
          }
      }
      function pcSteuerung(){
        if(ball.richtungX>0){
          if(ball.y>pc.y-pc.hoehe/2){
            pc.y+=4;
          }
          if(ball.y<pc.y+pc.hoehe/2){
            pc.y-=4;
          }
        }
      }
      function schlaegerCollision(){
        if(ball.x>spielfeld.breite/2){
          schlaeger=pc;}
        else{schlaeger=spieler;}
        ball.unten=ball.y+ball.radius;
        ball.oben=ball.y-ball.radius;
        ball.rechts=ball.x+ball.radius;
        ball.links=ball.x-ball.radius;
        schlaeger.oben=schlaeger.y;
        schlaeger.unten=schlaeger.y+schlaeger.hoehe;
        schlaeger.links=schlaeger.x;
        schlaeger.rechts=schlaeger.x+schlaeger.breite;
        if(ball.unten>=schlaeger.oben &&
		   ball.oben<=schlaeger.unten &&
		   ball.rechts>=schlaeger.links &&
		   ball.links<=schlaeger.rechts){
          ball.richtungX=ball.richtungX*(-1);
          ball.x+=ball.richtungX;
          ball.y+=ball.richtungY;
        }
      }
      function punkteZaehlen(){
        if(ball.x<=0){
          punkte.pcPunkte+=1;
          neustart();
        }
        if(ball.x>=spielfeld.breite){
          punkte.spielerPunkte+=1;
          neustart();
        }
      }
      function neustart(){
        ball.x=spielfeld.breite/2;
        ball.y=spielfeld.hoehe/2;
        ball.richtungX=(Math.floor(Math.random()*2)+3)*richtung[Math.floor(Math.random()*2)]; 
        ball.richtungY=(Math.floor(Math.random()*2)+3)*richtung[Math.floor(Math.random()*2)];
      }
    </script>
</html>