HTML5-Tutorium: Canvas: MiniPong 01: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Zeile 423: Zeile 423:
<dl>
<dl>
<dt>Überlappung (penetration = Eindringung)
<dt>Überlappung (penetration = Eindringung)
<dd>Der Ball dringt i.Allg. etwas in die Wand ein, bevor eine Kollision erkannt wird. Wenn dieses Eindringen durch die Kollisionsbehandlung nicht rückgängig gemacht wird, kann es passieren, dass der Ball in der Wand hängen bleibt, da er sich in allen folgenden Animationsschritten in der Wand befindet und daher in jedem Schritt seine Richtung ändert.   
<dd>Der Ball dringt {{iAllg}} etwas in die Wand ein, bevor eine Kollision erkannt wird. Wenn dieses Eindringen durch die Kollisionsbehandlung nicht rückgängig gemacht wird, kann es passieren, dass der Ball in der Wand hängen bleibt, da er sich in allen folgenden Animationsschritten in der Wand befindet und daher in jedem Schritt seine Richtung ändert.   
<dt>Tunneln (tunneling)
<dt>Tunneln (tunneling)
<dd>Wenn der Ball besonders schnell ist, kann er ein anderes Objekt eventuell durchtunneln. Dies passiert immer  
<dd>Wenn der Ball besonders schnell ist, kann er ein anderes Objekt eventuell durchtunneln. Dies passiert immer  

Version vom 10. November 2016, 18:36 Uhr

Dieser Artikel erfüllt die GlossarWiki-Qualitätsanforderungen nur teilweise:

Korrektheit: 3
(zu größeren Teilen überprüft)
Umfang: 3
(einige wichtige Fakten fehlen)
Quellenangaben: 5
(vollständig vorhanden)
Quellenarten: 5
(ausgezeichnet)
Konformität: 5
(ausgezeichnet)

HTML-Tutorium: MiniPong

MiniPong: | Teil 1 | Teil 2 | Teil 3 | Teil 4 | Teil 5

Musterlösung: MiniPong 1 (SVN-Repository)

Voraussetzung

Sie sollten die Inhalte des Tutoriums HTML5-Tutorium: Canvas: Hello World kennen.

Ziel: Animation des Balls

Im ersten Teil des Tutoriums wird beschrieben, wie man eine einfache Ball-Animation mit Hilfe eines Canvas-Elements erstellt.

Das Spiel besteht aus insgesamt fünf Modulen:

  • ball.js: Hier wird die JavaScript-Klasse Ball definiert.
  • collision.js: Hier wird die Funktion collision definiert, die überprüft, ob ein Ball mit einer „Wand“ des Canvas-Elements kollidiert. Falls dies der Fall ist, wird die Bewegungsrichtung des Balls geändert (er „prallt“ an der „Wand“ ab).
  • minipong.js: Die Datei enthält die eigentliche Spiellogik. Es wird ein Ball erzeugt. Eine Zeichenfunktion „draw“ ist dann dafür zuständig, die Position des Balles mehrmals pro Sekunde neu zu berechnen und den Canvas entsprechend zu aktualisieren.
  • init.js: Die Aufgabe der Init-Funktion ist es, die Größe des Canvas gemäß der Initialisierungswerte, die ihr übergeben werden, festzulegen, ein MiniPong-Objekt zur erzeugen und einen Timer zu starten, der die Draw-Methode des Spiels mit einer bestimmten Framerate aufruft.
  • main.js: Die Datei hat wie immer die Aufgabe, eine JSON-Datei mit den Initialwerten zu laden und anschließenden die Initialisierungsmethode des Spiels auszuführen. In der JSON-Datei werden die Größe des Canvas, die Framerate sowie die Eigenschaften des Balls festgelegt.

Neues Projekt anlegen

Legen Sie ein neues Projekt mit dem Namen „MiniPong01“ an. Fügen Sie die Ordner und Dateien, die Sie im Zip-Archiv WKempty_RequireJS.zip finden. ein. In diesem Zip-Archiv ist ein leere Projekt enthalten, das im Prinzip wie die App5 im Tutorium HTML5-Tutorium: JavaScript: Hello World 04 aufgebaut ist (Ein paar Datei- und Ordnernamen unterscheiden sich). Dieses Archiv enthält folgende Dateien:

  • web/css/main.css: Eine leere CSS-Datei.
  • web/json/init.json: Eine leere JSON-Datei, in die Initialisierungsinformationen der Anwendung eingefügt werden können.
  • web/js/lib/require: Die Bibliothek RequireJS samt zugehörigem JSON-Plugin.
  • web/js/app/module.js: Ein leeres RequireJS-Modul. Ein Modul mit dem Namen „module“ wird üblicherweise nicht benötigt. Die Datei kann allerdings als Template für die Erstellung anderer Module verwendet werden.
  • web/js/app/init.js: Ein RequireJS-Modul, das eine (bislang noch leere und damit funktionslose) Initialisierungsfunktion enthält. Diese Methode wird von main.js aufgerufen. Ihr werden üblicherweise das Window-Objekt des Browsers sowie ein JSON-Initialisierungsobjekt übergeben.
  • web/js/main.js: Eine JavaScript-Datei, die RequireJS initialisiert, die JSON-Datei init.json lädt und zu guter Letzt die Initialisierungsfunktion der eigentlichen Anwendung ausführt.
  • index.html: Eine HTML-Datei mit leeren Body-Element, die main.css, RequireJS und zu guter Letzt main.js lädt.

Die HTML-Datei

Die HTML-Datei „index.html“ ist schon fast vollständig. Es fehlt nur noch das Canvas-Element im Body-Element vor dem Script-Element (vgl. HTML5-Tutorium: Canvas: Hello World 02):

<canvas id="canvas"></canvas>

Die CSS-Datei

Die CSS-Datei „main.css“ des Projektes sieht folgendermaßen aus:

body
{ text-align:       center;
  padding:          10px;
  background-color: #AAA;
}

#canvas
{ border-color: #777;
  border-width: 2px;
  border-style: solid;

  background-color: #CCC;
}

Sie rückt das Canvas-Element in die obere Mitte der Bühne und legt die Hintergrundfarben des Browserfensters sowie des Canvas-Elements fest. Außerdem sorgt sie dafür, das ein Rand um das Canvas-Element gezeichnet wird.

Die JSON-Datei

Die JSON-Datei „init.json“ des Projektes sieht folgendermaßen aus:

 
{
  "canvas":
  {
    "width":  400,
    "height": 300
  },

  "physics":
  {
    "framesPerSecond": 50
  },

  "model":
  {
    "ball":
    {
      "radius":  8,
      "pos_min": {"x":  10, "y":  10},
      "pos_max": {"x": 390, "y": 290},
      "vel_min": {"x":  50, "y":  50},
      "vel_max": {"x": 200, "y": 200}
    }
  },

  "view":
  {
    "ball":
    {
      "color":       "#55AA55",
      "borderWidth": 1,
      "borderColor": "#000000"
    }
  }
}

Sie enthält vier Objekte zur Initialisierung der Anwendung:

  • canvas: Dieses Objekt legt diejenigen Eigenschaften des Canvas-Elements fest, die zur Laufzeit mit Hilfe von JavaScript dem Element zugeordnet werden. Das sind in diesem Fall nur die Breite und die Höhe. Die Hintergrundfarbe sowie der Rand wurden mit Hilfe von CSS definiert.
  • physics: Diese Objekt enthält Eigenschaften der Physics-Engine, die für das Spiel zum Einsatz kommt. In diesem Spiel ist das nur die Framerate, d. h. die Anzahl der Neuberechnung, die pro Sekunde vorgenommen werden sollen, um physikalische Veränderungen (Bewegung des Balls) zu simulieren.
  • model: Hier werden für jede Art von Objekten, die in dem Spiel vorkommt, physikalische Eigenschaften festgelegt, auf die die Physics-Engine zugreifen kann. Das es in unserem Spiel nur eine Objektart gibt, den Ball, enthält das model-Objekt auch nur die Beschreibung der Ballobjekte. Es legt fest, dass ein Ball stets einen festen Radius haben soll, sowie eine Position und eine Geschwindigkeit. Für Position und Geschwindigkeit sind Intervalle angegeben innerhalb derer diese Werte zufällig gewählt werden sollen. Das heißt, bei jedem Spielstart stratet der Ball an einer anderen Position und bewegt sich mit einer anderen Geschwindigkeit.
  • view: Hier werden für jede Art von Objekten, die in dem Spiel vorkommt und das auf der Bühne gezeichnet werden soll, grafische Eigenschaften festgelegt. Auch hier gibt es bislang nur das Ballobjekt. Von diesem Objekt werden die Farbe, die Breite des Randes sowie die Farbe des Randes festgelegt.

Das Ball-Modul

Das Ballmodul „ball.js“ definiert die JavaScript-Klasse „Ball“. Die Konstruktorfunktion „Ball“ erwartet als Eingabe sowohl die Modell-Daten als auch die View-Daten, die in der JSON-Datei für Ballobjekte festgelegt wurden.

Erstellen Sie zunächst eine Kopie der Datei „js/app/module.js“ unter dem Namen „js/app/ball.js“. Ersetzen Sie in der neu erstellten Datei zunächst die Return-Anweisung durch folgende Anweisung:

return Ball; // Returns the constructor defined above.

Anschließend ersetzen Sie die drei Zeilen

//var module =  ...;
function module()
{}

durch folgende Definition der Konstruktorfunktion „Ball“:

function Ball(p_init_model, p_init_view)
{
  // model
  var l_pos_min = p_init_model.pos_min,
      l_pos_max = p_init_model.pos_max,
      l_vel_min = p_init_model.vel_min,
      l_vel_max = p_init_model.vel_max;

  this.r = p_init_model.radius;

  this.x = (l_pos_min.x + Math.random()*(l_pos_max.x - l_pos_min.x)); 
  // x in [l_pos_min.x, l_pos_max.x[
  this.y = (l_pos_min.y + Math.random()*(l_pos_max.y - l_pos_min.y));  
  // y in [l_pos_min.y, l_pos_max.y[

  this.vx = (Math.random() < 0.5 ? 1 : -1)*                           
            (l_vel_min.x + Math.random()*(l_vel_max.x - l_vel_min.x));
  // vx in ]-l_vel_max.x, l_vel_max.y[ \ ]-l_vel_min.x, l_vel_min.x[
  this.vy = (Math.random() < 0.5 ? 1 : -1)*                           
            (l_vel_min.y + Math.random()*(l_vel_max.y - l_vel_min.y));  
  // vy in ]-l_vel_max.y, l_vel_max.y[ \]-l_vel_min.y, l_vel_min.y[

  // view
  this.color       = p_init_view.color;
  this.borderWidth = p_init_view.borderWidth;
  this.borderColor = p_init_view.borderColor;
}

Dieser Konstruktor initialisiert zunächst die Modell-Daten des Balles mit sinnvollen Werten:

  • r (Radius des Balls)
  • x (Aktuelle x-Position des Balls)
  • y (Aktuelle y-Position des Balls)
  • vx (Aktuelle Geschwindigkeit des Balls in x-Richtung in Pixeln/Sekunde)
  • vy (Aktuelle Geschwindigkeit des Balls in y-Richtung in Pixeln/Sekunde)

Den Radius entnimmt sie direkt dem Initialisierungsobjekt „p_init_model“, die aktuelle Position und die aktuelle Geschwindigkeit berechnet sie mit Hilfe des Zufallszahlengenerators „Math.random“ so, dass die Werte in den Intervallen liegen, die i p_init_model angegeben wurden. Der Aufruf „Math.random()“ liefert eine Float-Zahl, die größer oder gleich 0 ist und kleiner als 1. Bei der Geschwindigkeit wir mittels Math.random auch noch das Vorzeichen bestimmt. Der Methodenaufruf „(Math.random() < 0.5 ? 1 : -1)“ liefert in 50% der Fälle eine 1 als Ergebnis und in den restlichen 50% der Fälle eine -1. Im Falle der x-Geschwindigkeit bedeutet das, dass sich der Ball in 50% der Fälle nach links und in 50% der Fälle nach rechts bewegt. Im Fall der y-Geschwindigkeit wird auf dieselbe Weise zufällig ermittelt, ob sich der Ball zu Beginn nach unten oder nach oben bewegen soll.

Der Konstruktor initialisiert auch die View-Daten des Balles: color, borderWidth und borderColor. Die entsprechenden Werte übernimmt er einfach dem Initialisierungsobjekt „p_init_view“.

Die Klasse „Ball“ definiert für jedes Ballobjekt auch noch zwei Methoden. Wie üblich werden diese Methoden im Prototyp-Objekt „Ball.prototype“ des Konstruktors gespeichert, damit nicht für jedes Ballobjekt eine eigen Methode erzeugt wird.

  • move bewegt den Ball an seine neue Position; diese wird aus der aktuellen Position, dem Geschwindigkeitsvektor und der Zeitspanne, die seit dem letzen Aufruf des move-Befehls vergangen ist, berechnet.
  • draw visualisiert das (zweidimensionale) Ballobjekt auf dem 2D-Kontext einer Bühne (Canvas).

Fügen Sie folgende Definitionen in die Datei „ball.js“ direkt hinter die Konstruktorfunktion „Ball“ ein:

Ball.prototype.move =
  function(p_fps)
  { this.x += this.vx/p_fps;
    this.y += this.vy/p_fps;
  };

Ball.prototype.draw =
  function(p_context)
  { p_context.beginPath();
    p_context.arc(this.x, this.y, this.r, 0, 2*Math.PI);
    p_context.fillStyle   = this.color;
    if (this.borderWidth > 0)
    {
      p_context.lineWidth = this.borderWidth;
      p_context.strokeStyle = this.borderColor;
      p_context.stroke(); // Draw the border.
    }
    p_context.fill();     // Fill the inner area of the ball with its color.
  };

Die Methode „move“ erwartet als Eingabe die aktuelle Framerate. Wenn sich der Ball in einer Sekunde um vx Pixel in x-Richtung und um vy Pixel in y-Richtung bewegt und die Move-Methode p_fps mal pro Sekunde aufgerufen wird, dann bewegt sich der Ball bei jedem Aufruf um vx/p_fps Pixel in x-Richtung und um vy/p_fps Pixel in y-Richtung weiter.

Die Methode „move“ wird ebenfalls alle paar Millisekunden aufgerufen. Wie häufig das passiert ist allerdings unwesentlich. Ihre Aufgabe ist nur, dass sie jedes mal, wenn sie aufgerufen wird, den Ball an seiner aktuellen Postion auf der Bühne zeichnet und dabei natürlich die Farben und die Breite des Randes berücksichtigt, die dem Ballobjekt zugeordnet sind. Sie verwendet dazu Befehle des 2D-Kontextes eines Canvas-Elements. Daher muss ihr as 2D-Kontext-Objekt des Canvas-Elements als Argument übergeben werden.

Zunächst definiert die Methode im aktuellen 2D-Kontext ein neues Pfad-Objekt. Als Pfad definiert sie einen Kreisbogen, der bei 0 startet und bei 2*Math.PI (entspricht 360°) endet. Das heißt. bei dem Pfad handelt es sich um einen geschlossenen Kreis. Anschließend legt sie die Breite des Pfades, die Farbe des Pfades sowie die Füllfarbe des Gebietes, das der Pfad umschließt fest. Dazu greift sie auf die entsprechenden Werte des Ballobjektes zurück. Zu guter Letzt zeichnet sie den Pfad auf die Bühne (p_context.stroke()) und füllte den vom Pfad umschlossenen Innenraum mit Farbe (p_context.fill()).

Das Kollisions-Modul

In der Datei „collision.js“ wird für jedes bewegliche Objekt überprüft, ob es mit irgendeinem anderen (beweglichen oder unbeweglichen) Objekt kollidiert. Sollte dies der Fall sein, wird die Kollision behandelt, das heißt, es werden Eigenschaften des Objektes geändert. Es könnte beispielsweise explodieren.

In unserem Fall gibt es nur ein bewegliches Objekt, den Ball. Dieser kann nur mit unbeweglichen Objekte. kollidieren: Den „Wänden“ des Canvas-Elements. Als Reaktion auf eine derartige Kollision prallt der Ball ab, d. h. er ändert seine Richtung. Das ist bei Kollisionen mit senkrechten oder waagerechten Wänden ganz einfach: Es muss jeweils nur das Vorzeichen der $x$- bzw. der $y$-Geschwindigkeit geändert werden.

Dementsprechend wird im Kollisionsmodul nur eine Funktion namens „collision“ definiert und als Ergebnis zurückgegeben. Diese Funktion erhält als Input das Initialisierungsobjekt des Canvas, welches die Breite und die Höhe des Canvas enthält sowie das Ballobjekt, das mit den Wänden des Canvas kollidieren kann.

Erstellen Sie das Kollisions-Modul auf die gleiche Weise wie das Ball-Modul. Dieses Modul soll folgende Funktion definieren:

function collision(p_canvas, p_ball)
{
  // If the ball collides with the left or the right wall of the canvas
  // mirror its x-velocity.
  if (p_ball.x <= p_ball.r || p_ball.x >= p_canvas.width - p_ball.r)
  { p_ball.vx = -p_ball.vx; }

  // If the ball collides with the top or the bottom wall of the canvas
  // mirror its y-velocity.
  if (p_ball.y <= p_ball.r || p_ball.y >= p_canvas.height - p_ball.r)
  { p_ball.vy = -p_ball.vy; }
}

Das MiniPong-Modul

Das MiniPong-Modul ist für die Spiele-Logik zuständig. Das Spiel ist ziemlich einfach. Es wird ein Ball erstellt, der sich mit einer gewissen Geschwindigkeit über die Bühne bewegt.

Wie dieser Ball aussieht und welche Eigenschaften er hat, wird dem Konstruktor ebenso wie die Größe der Bühnen und die Framerate im JSON-Objekt „p_init“ übergeben. Die Bühne selbst wird im Parameter „p_context“ übermittelt.

Zunächst wird ein Ballobjekt erstellt:

var l_ball = new Ball(p_init.model.ball, p_init.view.ball);

Aus p_init werden das Modell-Initialisierungsobjekt „p_init.model.ball“ und das View-Initialisierungsobjekt „p_init.view.ball“ extrahiert und dem Ball-Konstruktor übergeben.

Damit hat man einen Ball, der sich über die Bühne bewegen kann. Zu diesem Zweck gibt es der Methode „draw“. Das ist die Kernfunktion (und das einzige öffentliche Attribut) eines MiniPong-Objekts.

Diese Methode führt bei jedem Aufruf folgende Aktionen durch:

  1. Kollisionserkennung und -behandlung: Kollidiert der Ball mit einer Wand?
  2. Bewegen des Balls an seine neue Position.
  3. Löschen aller Inhalte der Bühne, damit sie neu gezeichnet werden kann. (Kommentieren Sie diesen Befehl testhalber einmal aus, sobald Ihre Anwendung läuft).
  4. Zeichnen des Balls (an der zuvor berechneten Position) auf die Bühne.

Die Methode „draw“ wird diesmal nicht im Prototype-Objekt des Konstruktors gespeichert. Das ist auch nicht notwendig, da es pro Lauf einer Anwendung i. Allg. nur ein MiniPong-Objekt (oder testhalber mal zwei MiniPong-Objekte) gibt. Die Draw-Methode innerhalb der Konstruktor-Methode zu definieren, hat noch einen weiteren Vorteil: Man kann innerhalb des Objektes lokale, d. h. private Variablen definieren, auf die nur die Methode Zugriff hat. Von außen kann auf diese Variablen nicht zugegriffen werden. Methoden, die im Prototype-Objekt des Konstruktors definiert werden, greifen von außerhalb auf ein zugehöriges Objekt zu. Derartige Methoden haben keinen Zugriff auf private Variablen von Objekten.

Insgesamt sieht der MiniPong-Konstruktor folgendermaßen aus:

function MiniPong(p_init, p_context)
{
  var l_ball   = new Ball(p_init.model.ball, p_init.view.ball),
      l_canvas = p_init.canvas,
      l_fps    = p_init.physics.framesPerSecond;

  this.draw =
    function()
    {
      // model update
      l_ball.move(l_fps);

      // a posteriori collision detection and handling
      collision(l_canvas, l_ball);

      // view update
      p_context.clearRect(0, 0, l_canvas.width, l_canvas.height);  // clear
      l_ball.draw(p_context);
    };
}

Das zugehörige Modul wird abermals auf die gleiche Weise wie das Ball-Modul erstellt. Allerdings benötigt dieses Module sowohl den zuvor definierten Ball-Konstruktor, damit es einen (oder später auch mehrere) neue Bälle erzeugen kann. Außerdem benötigt es die Kollisionsfunktion, um die Richtung des Balles im Falle einer Kollision mit einer Wand anpassen zu können. Dies erfolgt, nachdem der Ball an eine neue Position bewegt wurde. Hier kommt also die sogenannte A-posteriori-Kollisionserkennung und -behandlung zu Einsatz. Diese ist einfach handzuhaben, führt aber häufig zu unerwünschten Tunneling- oder Penetrationseffekten.

Insgesamt muss das Modul also zunächst die beiden Module „app/ball“ und „app/collision“ laden und die geladenen Objekt als Funktionsargumente der Callback-Funktion übergeben.

Der Modul-Header sieht somit folgendermaßen aus:

define
(['app/ball', 'app/collision'],
 function(Ball, collision)
  ...

Das Init-Modul

Das Init-Modul stellt die Methode „init“ zur Verfügung, die von „main.js“ aufgerufen wird, um die Web-Anwendung zu starten. Im Prinzip macht diese Methode im Fall von MiniPong Folgendes:

  • Erzeugung eines MiniPong-Objekts „l_game“. Dafür wird das Modul „app/minipong“ benötigt.
  • Initialisierung des Canvas-Elements: Höhe und Breite gemäß den in der JSON-Datei festgelegten Initialwerten einstellen (vgl. HTML5-Tutorium: Canvas: Hello World 02).
  • Starten eines Timers, der mit der in der JSON-Datei festgelegten Framerate die Funktion „l_game.draw“ aufruft.

Für den letzten Schritt wird die Methode „window.setInterval“ verwende, die zwei Argumente erwartet: Eine Funktion, die regelmäßig aufgerufen werden soll, und die Anzahl der Millisekunden, die zwischen zwei Aufrufen vergehen soll. Die Anzahl der Millisekunden berechnet sich aus der Framerate: 1000/framesPerSecond. In der Datei „init.json“ wird beispielsweise der Wer 50 Frames pro Sekunde festgelegt. Das heißt, alle $1000/50 = 20$ Millisekunden wird ein neues Bild für die Canvas-Bühne berechnet. Wenn diese Berechnung länger als 20 Millisekunden dauert, fängt die Anwendung an zu ruckeln. Bei einem Ball, der sich über die Bühne bewegt, ist das noch kein Problem, bei größeren Web-Anwendungen kann dies aber zum Problem werden. Das soll aber hier noch nicht weiter vertieft werden.

Insgesamt sieht die Initialisierungsfunktion folgendermaßen aus:

function init(p_window, p_init)
{
  var l_canvas = p_window.document.getElementById("canvas"),
     l_game   = new MiniPong(p_init, l_canvas.getContext("2d"));

  l_canvas.width  = p_init.canvas.width;
  l_canvas.height = p_init.canvas.height;

  p_window.setInterval(l_game.draw, 1000/p_init.physics.framesPerSecond);
}

Dieses Modul benötigt den zuvor definierte MiniPong-Konstruktor, damit es ein neues Spiel erzeugen kann. Dazu muss es das Modul „app/minipong“ laden. Der Modul-Header sieht diesmal daher folgendermaßen aus:

define
( ['app/minipong'],
  function (MiniPong)
  ...

Das Main-Modul

Das Main-Modul liest wie üblich eine JSON-Initialisierungsdatei ein und startet die Web-Anwendung. Dieses Modul kann ohne Änderung aus dem leeren Projekt „WKempty_RequireJS“ übernommen werden.

requirejs.config
({
  baseUrl: 'js', // By default load any modules from directory js
  paths :
  {
    app:      'app',
    loadjson: 'lib/require/json',
    text:     'lib/require/text',
    json:     '../json'
  }
});

requirejs
( ['loadjson!json/init.json', 'app/init'],
  function(initJSON, init)
  {
    init(window, initJSON);
  }
);

Probleme der Kollisionserkennung

Die A-posteriori-Kollisionserkennung, die in MiniPong 1 bei der Erkennung von Kollisionen zwischen Ball und Wand angewandt wurde, ist nicht ganz unproblematisch, da zwei unerwünschte Effekte auftreten können:

Überlappung (penetration = Eindringung)
Der Ball dringt i. Allg. etwas in die Wand ein, bevor eine Kollision erkannt wird. Wenn dieses Eindringen durch die Kollisionsbehandlung nicht rückgängig gemacht wird, kann es passieren, dass der Ball in der Wand hängen bleibt, da er sich in allen folgenden Animationsschritten in der Wand befindet und daher in jedem Schritt seine Richtung ändert.
Tunneln (tunneling)
Wenn der Ball besonders schnell ist, kann er ein anderes Objekt eventuell durchtunneln. Dies passiert immer dann, wenn der Ball in einem Schritt eine so große Distanz zurücklegt, dass weder am Startpunkt noch am Endpunkt dieses Schritts eine Kollision erkannt wird, obwohl entlang des Weges eine Kollision stattgefunden hätte.

Beide Effekte können Sie beobachten, wenn Sie Ihren Minipong-1-Code etwas modifizieren.

Überlappung
Setzen Sie in der Datei init.json den minimalen und den maximalen $y$-Wert jeweils auf 300, d. h., platzieren Sie den Ball gleich zu Beginn in der Wand, und starten Sie die Anwendung. Nach dem Sie den Effekt bewundert haben, sollten Sie den Fehler auch wieder beheben. :-)
"pos_min": {"x":  10, "y": 300},
"pos_max": {"x": 390, "y": 300}
Tunneln
Da die Wände im Kollisionstest unendlich dick gewählt wurden, kann der Ball diese nicht durchdringen. Wenn Sie aber die Breite der Wände in der Funktion o_redraw (in der Datei main.js) auf Null reduzieren, haben Sie nicht lange Freude an Ihrem Ball:
  if (p_ball.x == p_ball.r || p_ball.x == p_canvas.width  - p_ball.r)
  { p_ball.vx = -p_ball.vx; }
  if (p_ball.y == p_ball.r || p_ball.y == p_canvas.height - p_ball.r)
  { p_ball.vy = -p_ball.vy; }

Erweiterung

Erweitern Sie die Anwendung so, dass sich zwanzig Bälle gleichzeitig über die Bühne bewegen. Kollisionen zwischen den Bällen brauchen Sie nicht zu behandeln. Beachten Sie aber das Programmierprinzip „Don't repeat yourself“ (DRY).

Musterlösung: MiniPong 1a (SVN-Repository)

Quellen

  1. Braun (2011): Herbert Braun; Webanimationen mit Canvas; in: c't Webdesign; Band: 2011; Seite(n): 44–48; Verlag: Heise Zeitschriften Verlag; Adresse: Hannover; 2011; Quellengüte: 5 (Artikel)
  2. Kowarschick (MMProg): Wolfgang Kowarschick; Vorlesung „Multimedia-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 3 (Vorlesung)
  3. Musterlösung MiniPong 01 (SVN)
  4. Musterlösung MiniPong 01a (SVN)

Fortsetzung des Tutoriums

Sie sollten nun Teil 2 des Tutoriums bearbeiten.