|
|
(13 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) |
Zeile 1: |
Zeile 1: |
| {{HTML5-Tutorium:Canvas:MiniPong:Menü}} | | {{HTML5-Tutorium:Canvas:MiniPong:Menü}} |
| '''Musterlösung''': <code>[http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_04/WebContent/index.html Minipong 4]</code> | | '''Musterlösung''': <code>[https://glossar.hs-augsburg.de/beispiel/tutorium/es5/minipong/WK_MiniPong05/web/index.html index.html]</code> |
| ([http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_04 SVN-Repository]) | | ([https://glossar.hs-augsburg.de/beispiel/tutorium/es5/minipong/WK_MiniPong05/ WK_MiniPong05 (SVN)]) |
| =Ziel: Simulation von Klassen in JavaScript=
| |
| Im vierten Teil des Tutoriums wird beschrieben, wie man [[Klasse]]n in JavaScript nachbilden kann.
| |
|
| |
|
| Vier Klassen werden implementiert: <code>Ball</code>, <code>Paddle</code>, <code>Collision</code> und <code>Main</code>.
| | ==Ziel: Das fertige Spiel „MiniPong“ verbessern== |
| Es wird jeweils ein Objekt pro Klasse erstellt.
| | Im fünften Teil des Tutoriums wird beschrieben, wie die funktionsfähige Version von MiniPong verbessert und erweitert werden kann. |
| | |
| [[Medium:MiniPong04Canvas.png|miniatur|ohne|709px|Das Datenmodell von MiniPong 04]]
| |
| | |
| =Anwendung „<code>MiniPongCanvas04</code>“= | |
| | |
| ==Neues Projekt anlegen==
| |
| | |
| Legen Sie ein neues „Statisches Web-Projekt“ mit dem Namen <code>MiniPongCanvas04</code> an.
| |
| | |
| Speichern Sie dieses Projekt wie üblich in Ihrem Repository.
| |
| | |
| ==Dateien erstellen==
| |
| Erstellen Sie zunächst die Datei <code>js/util.js</code> und füegen Sie folgenden Code ein:
| |
| | |
| <source lang="javascript">
| |
| /**
| |
| * @param {int} p_min
| |
| * @param {int} p_max
| |
| * @returns An integer number within the interval
| |
| * [<code>p_min</code>, <code>p_max</code>].
| |
| */
| |
| Math.randomIntMinMax =
| |
| function(p_min, p_max)
| |
| { return (p_min == p_max)
| |
| ? p_min
| |
| : p_min+Math.floor(Math.random()*(p_max-p_min+1));
| |
| };
| |
| </source>
| |
| | |
| Damit haben Sie die Klasse <code>Math</code> um statische Funktion <code>randomIntMinMax</code>
| |
| erweitert, die eine zufällige Integer-Zahl aus dem Intervall <code>[p_min, p_max]</code>
| |
| berechnet.
| |
| | |
| Kopieren Sie die Dateien <code>index.html</code>, <code>css/main.css</code> und <code>js/CONSTANT.js</code> von [[HTML5-Tutorium: Canvas: MiniPong 03|Teil 3]] des Tutoriums,
| |
| passen Sie den Projekttitel in der Datei <code>index.html</code> an.
| |
| | |
| Jede Klasse soll in einer eigenen Datei definiert werden, wie dies auch bei anderen Programmiersprachen wie z.B. Java üblich ist.
| |
| Erstellen Sie zu diesem Zweck zunächst drei leere Dateien <code>js/ball.js</code>, <code>js/paddle.js</code> sowie <code>js/collision.js</code>.
| |
| Sorgen Sie anschließend dafür, dass alle JavaScript-Dateien von der Datei <code>index.html</code> geladen werden,
| |
| indem Sie folgende Zeilen in den Header dieser Datei einfügen:
| |
| | |
| <source lang="javascript">
| |
| <script type="text/javascript" src="js/util.js" ></script>
| |
| <script type="text/javascript" src="js/CONSTANT.js" ></script>
| |
| <script type="text/javascript" src="js/ball.js" ></script>
| |
| <script type="text/javascript" src="js/paddle.js" ></script>
| |
| <script type="text/javascript" src="js/collision.js"></script>
| |
| <script type="text/javascript" src="js/main.js" ></script>
| |
| </source>
| |
| | |
| '''Anmerkung:''' Um den Kommunikations-Overhead zwischen Client (Browser) und Server möglichst gering zu halten,
| |
| sollte eine HTML-Seite möglichst wenige weitere Dokumente (Bilder, CSS-Dateien, JavaScript-Dateien etc.) nachladen.
| |
| Die Definition weiterer JavaSCript-Dateien stellt hier aber kein Problem dar, wenn – sobald die Anwendung fertiggestellt und ausgiebig getestet wurde –
| |
| alle JavaScript-Dateien komprimiert und in eine einzige Datei (<code>js/all.min.js</code>) eingefügt werden.
| |
| Natürlich darf in der Datei <code>index.html</code> anschließend auch nur noch diese eine JavaScript-Datei geladen werden.
| |
| | |
| ==Die Klasse <code>Ball</code>==
| |
| | |
| Die Definition der Klasse <code>Ball</code> wird in JavaScript durch zwei Objekte realisiert oder – besser gesagt – simuliert:
| |
| Eine Konstruktor-Funktion (genauer: ein Konstruktor-Funktion-Objekt) sowie das zugehörige <code>prototype</code>-Objekt.
| |
| | |
| Die Konstruktor-Funktion <code>Ball</code> erstellt neue Ball-Objekte und initialisiert diese,
| |
| indem sie im jedem neu erstellten Objekt (<code>this</code>) Attribute erzeugt und initialisiert.
| |
| (Ein Attribut wird automatisch erzeugt, sobald ihm ein Wert zugewiesen wird.)
| |
| | |
| ===Konstruktor===
| |
| <source lang="javascript">
| |
| /**
| |
| * Creates an instance of <code>Ball</code>.
| |
| *
| |
| * @constructor
| |
| * @this {Ball}
| |
| * @param {CanvasRenderingContext2D} p_context
| |
| * The 2d context of the canvas upon which the ball is to be drawed.
| |
| * @param {Number} p_r
| |
| * The radius of the ball.
| |
| * @param {Number} [p_x_start = BALL_X_START]
| |
| * The starting position of the ball in x-direction.
| |
| * @param {Number} [p_y_start = BALL_Y_START]
| |
| * The starting position of the ball in y-direction.
| |
| * @param {Array[2]} [p_vx_start = BALL_VX_START]
| |
| * The starting velocity of the ball in x-direction (min, max).
| |
| * @param {Array[2]} [p_vy_start = BALL_VY_START]
| |
| * The starting velocity of the ball in y-direction (min, max).
| |
| */
| |
| function Ball(p_context, p_r, p_x_start, p_y_start, p_vx_start, p_vy_start)
| |
| { this.r = p_r || BALL_RADIUS;
| |
| | |
| this.v_x_start = p_x_start || BALL_X_START;
| |
| this.v_y_start = p_y_start || BALL_Y_START;
| |
| this.v_vx_start = p_vx_start || BALL_VX_START;
| |
| this.v_vy_start = p_vy_start || BALL_VY_START;
| |
| | |
| this.v_context = p_context;
| |
|
| |
| this.reset();
| |
| };
| |
| </source>
| |
| | |
| Die <code>reset</code>-Methode nimmt weitere Initialisierungen vor.
| |
| Da diese Initialisierungen bei jedem Spielstart erneut ausgeführt werden müssen,
| |
| wurden sie in eine Methode ausgelagert.
| |
| | |
| Man beachte, dass der dem Konstruktor vorangestellte Kommentar [[JSDoc]]-konform ist.
| |
| Dies ermöglicht es, mit einem geeigneten JSDoc-Tool eine HTML-[[API]]-Dokumentation automatisch zu erstellen.
| |
| | |
| ===Methoden===
| |
| | |
| Der Konstruktor weist jedem neu erstellten Objekt ein weiteres Attribut automatisch zu: <code>prototype</code>.
| |
| Dieses Attribut enthält einen Verweis auf das <code>prototype</code>-Objekt des zugehörigen Konstruktors.
| |
| JavaScript sucht bei einem Methodenaufruf <code>obj.m()</code> die Definition der Methode <code>m</code>
| |
| zunächst im Objekt <code>obj</code> selbst. Sollte dort keine Definition vorhanden sein, so
| |
| sucht JavaScript als nächstes in <code>obj.prototype</code>, dann in
| |
| <code>obj.prototype.prototype</code> usw. Die Suche endet sobald entweder eine Definition gefunden oder
| |
| kein weiteres <code>prototype</code>-Objekt mehr existiert. Im letzteren Fall meldet JavaScript einen Fehler.
| |
| | |
| Das heißt, in JavaScript werden Methoden üblicherweise im Prototyp-Objekt des Konstruktors definiert.
| |
| | |
| '''Anmerkung:''' Im Folgenden werden wieder JSDoc-konforme Kommentare benutzt.
| |
| | |
| <source lang="javascript">
| |
| Ball.prototype =
| |
| { //////////////////////////////////////////////////////////////////////////////
| |
| // data
| |
| //////////////////////////////////////////////////////////////////////////////
| |
|
| |
| /** The radius of the ball. */
| |
| get r() { return this.v_r; },
| |
| set r(p_r) { this.v_r = p_r; },
| |
| | |
| /** The x-position of the ball. */
| |
| get x() { return this.v_x; },
| |
| set x(p_x) { this.v_x = p_x; },
| |
| | |
| /** The y-position of the ball. */
| |
| get y() { return this.v_y; },
| |
| set y(p_y) { this.v_y = p_y; },
| |
| | |
| /** The vx-velocity of the ball. */
| |
| get vx() { return this.v_vx; },
| |
| set vx(p_vx) { this.v_vx = p_vx; },
| |
| | |
| /** The vy-velocity of the ball. */
| |
| get vy() { return this.v_vy; },
| |
| set vy(p_vy) { this.v_vy = p_vy; },
| |
| | |
| //////////////////////////////////////////////////////////////////////////////
| |
| // logic
| |
| //////////////////////////////////////////////////////////////////////////////
| |
| | |
| /**
| |
| * Resets the ball:
| |
| * Moves the ball to its starting position
| |
| * and computes randomly a velocity vector.
| |
| */
| |
| reset:
| |
| function()
| |
| { this.x = this.v_x_start;
| |
| this.y = this.v_y_start;
| |
| this.vx = (Math.random()<0.5?1:-1)
| |
| *Math.randomIntMinMax(this.v_vx_start[0], this.v_vx_start[1]);
| |
| this.vy = Math.randomIntMinMax(this.v_vy_start[0], this.v_vy_start[1]);
| |
| },
| |
| | |
| /** Moves the ball in direction (vx,vy); the step size depends on FPS. */
| |
| move:
| |
| function()
| |
| { this.x += this.vx/FPS;
| |
| this.y += this.vy/FPS;
| |
| },
| |
| | |
| //////////////////////////////////////////////////////////////////////////////
| |
| // view
| |
| //////////////////////////////////////////////////////////////////////////////
| |
| | |
| /** Draws the ball at its current position onto a 2d context. */
| |
| draw:
| |
| function()
| |
| { this.v_context.beginPath();
| |
| this.v_context.arc(this.x, this.y, this.r, 0, 2*Math.PI, true);
| |
| this.v_context.lineWidth = BALL_BORDER_WIDTH;
| |
| this.v_context.lineStyle = BALL_BORDER_COLOR;
| |
| this.v_context.fillStyle = BALL_COLOR;
| |
| this.v_context.stroke();
| |
| this.v_context.fill();
| |
| },
| |
| | |
| //////////////////////////////////////////////////////////////////////////////
| |
| // end of prototype
| |
| //////////////////////////////////////////////////////////////////////////////
| |
| };
| |
| </source>
| |
| | |
| Man beachte, dass die [[Getter-Methode|Getter]]- und [[Setter-Methode]]n für die
| |
| Attribute <code>r</code>, <code>x</code>, <code>y</code>, <code>vx</code>, <code>vy</code>
| |
| EcmaScript-5-konform definiert wurden (EcmaScript 5 wird von HTML5-fähigen Brwosern unterstützt).
| |
| Dies hat den Vorteil, dass man auf diese Attribute nicht mit der Methoden-Aufruf-Syntax,
| |
| sondern mit der üblichen Attribut-Syntax zugreifen kann:
| |
| | |
| <source lang="JavaScript">
| |
| var my_ball = new Ball();
| |
| | |
| my_ball.r = 20; // Aufruf der Setter-Methode
| |
| console.log(my_ball.r); // Aufruf der Getter-Methode
| |
| </source>
| |
| | |
| In EcmaScript 3 (HTML 4 oder früher)
| |
| gibt es diese Art der Getter- und Setter-Methoden nicht. Hier muss man auf
| |
| den in Java üblichen Trick zurückgreifen und ersatzweise zwei Methoden definieren:
| |
| | |
| <source lang="JavaScript">
| |
| /** The radius of the ball. */
| |
| getR: function() { return this.v_r; },
| |
| setR: function(p_r) { this.v_r = p_r; },
| |
| ...
| |
| </source>
| |
| | |
| In diesem Fall erfolgen die Attribut-Zugriffe mittels Methodenaufrufen:
| |
| | |
| <source lang="JavaScript">
| |
| var my_ball = new Ball();
| |
| | |
| my_ball.setR(20); // Aufruf der Setter-Methode
| |
| console.log(my_ball.getR()); // Aufruf der Getter-Methode
| |
| </source>
| |
| | |
| Die Verwendung von echten Getter- und Setter-Methoden erhöht die
| |
| [[Programmierprinzipien#Stetigkeit.2C_Continuity|Stetigkeit]] des Codes,
| |
| da sich der Zugriff auf öffentliche Zustandsvariablen syntaktisch nicht vom
| |
| Zugriff auf echte Getter- und Setter-Methoden unterscheidet. Das heißt,
| |
| die Implementierung der Klasse kann jederzeit die Art der Attribut-Definition ändern,
| |
| ohne dass das Auswirkungen auf die Zugriff-Syntax von zugehörigen Objekten hätte.<ref>vgl. {{Quelle|Meyer (1997)}}</ref>
| |
| | |
| Beispielsweise könnten in der obigen Definition der Klasse <code>Ball</code>
| |
| die Getter- und Setter-Methoden der Attribute <code>x</code> und <code>y</code>
| |
| (ebenso wie alle anderen Getter- und Setter-Methoden) einfach aus dem Prototype-Objekt gelöscht werden.
| |
| Am Verhalten der Klasse <code>Ball</code> würde das nichts ändern, da die Methoden
| |
| <code>reset</code> und <code>move</code> nun direkt auf die Zustandsvariablen
| |
| <code>this.x</code> und <code>this.y</code> zugreifen würden. Der zugehörige
| |
| Code wäre sogar etwas effizienter.
| |
| | |
| Kommentieren Sie in der Musterlösung oder in Ihrer finalen Lösung
| |
| doch einfach mal ein paar Getter- und Setter-Methoden aus und starten Sie die Anwendung erneut.
| |
| Die Anwendung sollte immer noch fehlerfrei funktioneiren.
| |
|
| |
| Anschließend sollten Sie die Methoden wieder aktivieren und testweise einmal einen Logging-Befehl einfügen:
| |
| | |
| <source lang="javascript">
| |
| set x(p_x) { this.v_x = p_x; console.log("Setter 'x', param: " + p_x); },
| |
| </source>
| |
| | |
| Wenn Sie jetzt das die Anwendung ausführen, wird jede Änderung der x-Position des Balls
| |
| im Konsolfenster ausgegeben. Ohne echte Setter-Methode hätten Sie diesen Logging-Befehl
| |
| an jede Stelle, an der die Ballposition geändert wird, einfügen müssen. Oder Sie hätten
| |
| an jeder dieser Stellen <code>...ball.x = ...</code> durch <code>...ball.setX(...)</code>
| |
| ersetzen müssen.
| |
| | |
| ==Die Klasse <code>Paddle</code>==
| |
| | |
| Die Klasse <code>Paddle</code> wird analog zur Klasse <code>Ball</code> definiert.
| |
| Die benötigten Attribute und Methoden findet man im obigen Klassendiagramm.
| |
| | |
| Die Methoden-Definitionen von <code>reset</code>, <code>move</code> und <code>draw</code> werden
| |
| (wie bei der Klasse <code>Ball</code> aus) der Lösung des [[HTML5-Tutorium: Canvas: MiniPong 03|dritten Teils des Tutoriums]] übernommen.
| |
| Bei der Definition der Methode <code>reset</code> sollte analog zur Klasse <code>Ball</code>
| |
| der Zugriff auf globale Konstanten (<code>PADDLE_X</code> und <code>PADDLE_Y</code>)
| |
| durch den Zugriff auf lokale Zustandsvariablen (<code>this.v_x_start</code> und <code>this.v_y_start</code>)
| |
| ersetzt werden. Diese beiden Variablen sollten (wie in der Klasse <code>Ball</code>) von Konstruktor initialisiert werden.
| |
| | |
| Zusätzlich werden drei weitere Methoden benötigt:
| |
| | |
| <source lang="javascript">
| |
| /** Makes the paddle move left (if it is currently not moving). */
| |
| startLeft: function(){ if (this.vx == 0) this.vx = -this.v_vx_start; },
| |
| | |
| /** Makes the paddle move right (if it is currently not moving). */
| |
| startRight: function(){ if (this.vx == 0) this.vx = +this.v_vx_start; },
| |
| | |
| /** Makes the paddle stop moving. */
| |
| stop: function(){ this.vx = 0; },
| |
| </source>
| |
| | |
| Die Attribute werden – bis auf vier Ausnahmen – genauso definiert wie in der Klasse <code>Ball</code>:
| |
| Entweder gar nicht oder mittels echter Setter- und Getter-Methoden.
| |
| | |
| Vier Atribute wurden allerdings im Diagramm als nur lesbar (read only, <code>frozen</code>)
| |
| deklariert: <code>left</code>, <code>right</code>, <code>top</code> und <code>bottom</code>.
| |
| Diese Attribute dürfen also nicht geändert, sondern nur gelesen werden,
| |
| da die Werte aus anderen Attributen berechnet werden. Daher können die Werte auch nicht
| |
| in Zustandsvariablen gespeichert werden. Allerdings kann man Read-only-Attribute
| |
| mit Hilfe echter Getter-Methoden recht elegant realisieren:
| |
| | |
| <source lang="javascript">
| |
| /** The left side of the paddle (read only). */
| |
| get left() { return this.x; },
| |
| | |
| /** The right side of the paddle (read only). */
| |
| get right() { return this.x + this.width; },
| |
| | |
| /** The top side of the paddle (read only). */
| |
| get top() { return this.y; },
| |
| | |
| /** The bottom side of the paddle (read only). */
| |
| get bottom() { return this.y + this.height; },
| |
| </source>
| |
| | |
| Auf diese Attribute kann ebenfalls mit der üblichen Attribut-Syntax zugegriffen werden:
| |
| | |
| <source lang="javascript">
| |
| var my_paddle = new Paddle();
| |
| | |
| console.log(my_paddle.left); // Aufruf der Getter-Methode
| |
| </source>
| |
| | |
| Da keine Setter-Methoden existieren, können diese Attribute nicht geändert werden.
| |
| Falls man seinen JavaScript-Code mittels <code>"use strict";</code> als „strikt“ dekalriert hat,
| |
| resultiert folgende Anweisung in einer Fehlermeldung:
| |
| | |
| <source lang="javascript">
| |
| my_paddle.left = 30; // Aufruf der nicht vorhandenen Setter-Methode
| |
| </source>
| |
| | |
| ==Die Klasse <code>Collision</code>==
| |
| | |
| Ein Objekt der Klass <code>Collision</code> ist für das Erkennen und Behandlen von Kollisionen zuständig.
| |
| | |
| Dem Konstruktor müssen alle möglich Kollisionspartner übergeben werden: Wände, Ball und Schläger.
| |
| | |
| <source lang="javascript">
| |
| /**
| |
| * Creates an instance of <code>Collision</code>.
| |
| *
| |
| * The main method <code>handleCollision</code> detects collisions
| |
| * between the ball and a wall, between the paddle and a wall, and
| |
| * between the ball and the paddle.
| |
| *
| |
| * @constructor
| |
| * @this {Collision}
| |
| * @param {Object} p_walls The walls of the game: An object with
| |
| * four integer attributes: <code>left</code>,
| |
| * <code>right</code>, <code>top</code>, and
| |
| * <code>bottom</code>.
| |
| * @param {Ball} p_ball The ball of the game.
| |
| * @param {Paddle} p_paddle The paddle of the game.
| |
| */
| |
| function Collision(p_walls, p_ball, p_paddle)
| |
| { this.v_walls = p_walls;
| |
| this.v_ball = p_ball;
| |
| this.v_paddle = p_paddle;
| |
| };
| |
| </source>
| |
| | |
| In der Kollisionsklasse wird nur eine öffentliche Methode definiert: <code>handleCollision</code>.
| |
| Diese Methode hat zwei Aufgabe: Kollisionen erkennen und erkannte Kollisionen zu behandeln.
| |
| Dabei sind zwei Arten von Behandlung möglich:
| |
| # Die Kollisionspartner ändern ihr Verhalten (Richtungswechsel des Balls, Stopp des Schläger).
| |
| # Das Spiel reagiert auf die Kollisionen (Erhöhung der Punktezahl, Spielende)
| |
| | |
| Die erste Art der Behandlung nimmt <code>handleCollision</code> selbst vor,
| |
| da ihr die Kollisionspartner bekannt sind. Die zweite Art der Kollisionsbehandlung
| |
| kann diese Methode dagegen nicht vornehmen. Daher informiert sie den Aufrufer
| |
| mittels eines Rückgabewerts (<code>EVENT_SCORE</code>: Erhöhung des Punktekontots, <code>EVENT_EXIT</code>: Spielende)
| |
| über das eingetretene Ereignis. Es ist dann die Aufgabe des Aufrufers, entsprechende Maßnahmen
| |
| vorzunehmen.
| |
| | |
| <source lang="javascript">
| |
| Collision.prototype =
| |
| { //////////////////////////////////////////////////////////////////////////////
| |
| // Methods (Logic)
| |
| //////////////////////////////////////////////////////////////////////////////
| |
| | |
| /**
| |
| * Detects collisions between the ball and a wall,
| |
| * between the paddle and the wall, and between the ball and the paddle.
| |
| * If a collision is detected, the collision is handled.
| |
| * <p>
| |
| * This method is an update method returning an event
| |
| * name in som cases. Better event handling should be
| |
| * implemented.
| |
| * </p>
| |
| * @return {String} <code>EVENT_EXIT</code> if the ball hits the bottom wall,
| |
| * <code>EVENT_SCORE</code> if the ball hits the paddle.
| |
| * <code>NULL</code> otherwise
| |
| */
| |
| handleCollision:
| |
| function()
| |
| { this.m_collision_ball_wall();
| |
| this.m_collision_paddle_wall();
| |
| | |
| if (this.m_ball_exit())
| |
| return EVENT_EXIT;
| |
| | |
| if (this.m_collision_ball_paddle())
| |
| return EVENT_SCORE;
| |
| | |
| return null;
| |
| },
| |
| | |
| //////////////////////////////////////////////////////////////////////////////
| |
| // Private Methods (Logic)
| |
| //////////////////////////////////////////////////////////////////////////////
| |
| | |
| // Tests whether the ball hits the bottom wall.
| |
| m_ball_exit:
| |
| function()
| |
| { return (this.v_ball.y >= this.v_walls.bottom + this.v_ball.r);
| |
| },
| |
| | |
| // Tests whether the ball hits another wall.
| |
| m_collision_ball_wall:
| |
| function()
| |
| { if ( (this.v_ball.x <= this.v_walls.left + this.v_ball.r)
| |
| || (this.v_ball.x >= this.v_walls.right - this.v_ball.r)
| |
| )
| |
| this.v_ball.vx = -this.v_ball.vx;
| |
| | |
| if (this.v_ball.y <= this.v_walls.top + this.v_ball.r)
| |
| this.v_ball.vy = -this.v_ball.vy;
| |
| },
| |
| | |
| // Tests whether the paddle hits the bottom wall and, if so, stops it.
| |
| m_collision_paddle_wall:
| |
| function()
| |
| { if ( (this.v_paddle.left <= this.v_walls.left && this.v_paddle.vx < 0)
| |
| || (this.v_paddle.right >= this.v_walls.right && this.v_paddle.vx > 0)
| |
| )
| |
| this.v_paddle.stop();
| |
| },
| |
| | |
| // Tests whether the paddle hits the paddle.
| |
| m_collision_ball_paddle:
| |
| function()
| |
| { if ( this.v_ball.y + this.v_ball.r >= this.v_paddle.top
| |
| && this.v_ball.y + this.v_ball.r <= this.v_paddle.bottom
| |
| && this.v_ball.x + 0.5*this.v_ball.r >= this.v_paddle.left
| |
| && this.v_ball.x - 0.5*this.v_ball.r <= this.v_paddle.right
| |
| )
| |
| { // Resolve penetration.
| |
| if (this.v_ball.vy > 0) // The ball is moving from top to bottom.
| |
| this.v_ball.y = this.v_paddle.y - this.v_ball.r;
| |
| else // The ball is moving from bottom to top.
| |
| this.v_ball.y = this.v_paddle.y + this.v_paddle.height + this.v_ball.r;
| |
| | |
| this.v_ball.vy = -this.v_ball.vy;
| |
| this.v_ball.vx += this.v_paddle.friction*this.v_paddle.vx;
| |
| return true;
| |
| };
| |
| return false;
| |
| },
| |
| | |
| };
| |
| </source>
| |
| | |
| Man beachten, dass in der obigen Definition vier private Methoden definiert wurden,
| |
| die für die jeweils unterschiedlichen Kollisionsszenarien zuständig sind:
| |
| * Ball kollidiert mit unterer Wand (⇒ Spielende)
| |
| * Ball kollidiert mit einer anderen Wand (⇒ Richtungsänderung)
| |
| * Schläger kollidiert mit einer Wand (⇒ Stopp der Schlägerbewegung)
| |
| * Ball kollidiert mit Schläger (⇒ Richtungsänderung und Punktgewinn)
| |
| | |
| Diese Aufteilung wäre hier nicht notwendig gewesen, führt aber zu verständlicherem Code
| |
| und legt den Grundstein für eine weitergehende Modularisierung der Kollisionsklasse.
| |
| | |
| ==Die Datei <code>main.js</code>==
| |
| | |
| Hier könnte zunächst die Datei <code>main.js</code> von [[HTML5-Tutorium: Canvas: MiniPong 03|Teil 3 des Tutoriums]] übernommen und leicht angepasst werden:
| |
| | |
| Die Variablen[[Definition vs. Deklaration|definition]] von <code>g_ball</code> und <code>g_paddle</code> werden durch Variablen[[Definition vs. Deklaration|deklarationen]]
| |
| ersetzt:
| |
| | |
| <source lang="javascript">
| |
| // ball, paddle, collision
| |
| var g_ball;
| |
| var g_paddle;
| |
| var g_collision;
| |
| </source>
| |
| | |
| Die eigentlich Definition der drei Objekte erfolgt nun in der Funktion <code>f_init</code>
| |
| '''nachdem''' das Objekt <code>g_context</code> ermittelt wurde:
| |
| | |
| <source lang="javascript">
| |
| // Initialize all game objects.
| |
| g_ball = new Ball(g_context);
| |
| g_paddle = new Paddle(g_context);
| |
| g_collision = new Collision
| |
| ({left: 0,
| |
| right: CANVAS_WIDTH,
| |
| top: 0,
| |
| bottom: CANVAS_HEIGHT
| |
| },
| |
| g_ball,
| |
| g_paddle
| |
| );
| |
| </source>
| |
| | |
| '''Anmerkung:''' Die untere Wand wurde außerhalb des Spielfeldrandes gelegt,
| |
| damit der Ball ganz von der Bühne verschwunden ist, bevor das Spielende angezeigt wird.
| |
| | |
| Die einzigen weiteren notwendigen Änderungen sind:
| |
| | |
| Die Aufrufe
| |
| <source lang="javascript">
| |
| g_ball.draw(g_context);
| |
| g_paddle.draw(g_context);
| |
| </source>
| |
| werden durch
| |
| <source lang="javascript">
| |
| g_ball.draw();
| |
| g_paddle.draw();
| |
| </source>
| |
| ersetzt, da jedes grafische Objekt den 2D-Kontext, auf den es gezeichnet werden soll, kennt (er wurde dem jeweiligen Objekt bei der Konstruktion übergeben).
| |
| | |
| Die Redraw-Methode wird deutlich einfacher, da deren Aufgabe im Wesentlichen vom Kollisionsobjekt übernommen wurden:
| |
| | |
| <source lang="javascript">
| |
| function o_redraw()
| |
| { // collision detection
| |
| var l_exit = g_collision.handleCollision();
| |
| | |
| if (l_exit == EVENT_EXIT)
| |
| { o_stop_game();
| |
| return;
| |
| };
| |
|
| |
| if (l_exit == EVENT_SCORE)
| |
| { // Raise the score and display it.
| |
| g_score++;
| |
| f_info("Punkte: " + g_score);
| |
| };
| |
|
| |
| // Move the ball and paddle.
| |
| g_ball.move();
| |
| g_paddle.move();
| |
| | |
| // Redraw the canvas.
| |
| f_draw();
| |
| }
| |
| </source>
| |
| | |
| Die Implementierungen der Start- und Stopp-Methoden des Paddles können ebenfalls vereinfacht werden,
| |
| da dafür im Paddle-Objekt spezielle Methoden zur Verfügung stehen:
| |
| | |
| <source lang="javascript">
| |
| // An event observer:
| |
| // It is called whenever a "start paddle moving event" is signalled.
| |
| function o_start_paddle_moving(p_event)
| |
| { if (p_event.keyCode == KEY_LEFT)
| |
| g_paddle.startLeft();
| |
| else if (p_event.keyCode == KEY_RIGHT)
| |
| g_paddle.startRight();
| |
| }
| |
| | |
| // An event observer:
| |
| // It is called whenever a "stop paddle moving event" is signalled.
| |
| function o_stop_paddle_moving(p_event)
| |
| { g_paddle.stop(); }
| |
| </source>
| |
| | |
| ==Eine echte <code>Main</code>-Klasse (<code>main.js</code> reloaded)==
| |
| | |
| Die JavaScript-Datei <code>main.js</code> enthält noch viele globale Werte: globale Variablen (<code>g_...</code>),
| |
| globale Funktionen (<code>f_...</code>) und globale Observer-Methoden (<code>o_...</code>).
| |
| | |
| Aus Sicht des „[[Programmierprinzipien#Modularit.C3.A4t.2C_Modularity.2C_Teile_und_herrsche.2C_Divide_et_impera|Prinzips der Modularität]]“
| |
| ist die Verwendung von globalen Größen extrem schlecht. Auf globale Größen kann jedes Modul ungehindert zugreifen. Daher
| |
| kann eine globale Größe nie geändert werden, ohne vorher sämliche Module zu überprüfen, ob und in welcher Weise sie
| |
| auf diese Größe zugreifen. Dies ist eine der Lehren, die zahlreiche [[Fortran]]-Programmierer aus der früher üblichen Programmierpraxis,
| |
| sehr viele Daten in einem globalen Datenbereich abzulegen, schmerzhaft ziehen mussten.
| |
| | |
| Das heißt, alle globalen Größen der Datei <code>main.js</code> sollten in einem <code>Main</code>-Objekt gekapselt werden.
| |
| | |
| ===Konstruktor===
| |
| | |
| Der Konstruktor übernimmt im Wesentlichen die Aufgaben der ehemaligen Init-Funktion <code>f_init</code>.
| |
| Anmerkung: Hier wurde bewusst auf [[JSDoc]]-Kommentare verzichtet, das <code>Main</code> eine
| |
| „private“ Klasse ist, die nicht in anderen Projekten wiederverwendet wird. Daher ist es nicht sonderlich sinnvoll,
| |
| die API dieser Klasse automatisch zu generieren.
| |
| | |
| <source lang="javascript">
| |
| // Create the main object, after the HTML page has been loaded.
| |
| window.onload = function(){ new Main(); };
| |
| | |
| // The singleton class Main.
| |
| function Main()
| |
| { // Ensure that Main is a singleton class.
| |
| if (Main.c_element != null)
| |
| return Main.c_element;
| |
| Main.c_element = this;
| |
| | |
| var l_canvas = document.getElementById("d_canvas");
| |
| | |
| // Initialize the canvas.
| |
| l_canvas.width = CANVAS_WIDTH;
| |
| l_canvas.height = CANVAS_HEIGHT;
| |
| this.v_context = l_canvas.getContext("2d");
| |
| | |
| // Initialize all game objects.
| |
| this.v_ball = new Ball(this.v_context);
| |
| this.v_paddle = new Paddle(this.v_context);
| |
| this.v_collision = new Collision
| |
| ({left: 0,
| |
| right: CANVAS_WIDTH,
| |
| top: 0,
| |
| bottom: CANVAS_HEIGHT
| |
| },
| |
| this.v_ball,
| |
| this.v_paddle
| |
| );
| |
|
| |
| var self = this; // Needed to overcome dynamic binding problems.
| |
| document.getElementById("d_start_stop").onmousedown =
| |
| function() { self.o_start_stop_game(); };
| |
|
| |
| // Reset the game (i.e. change its state to "waiting for game to be started").
| |
| this.v_state = STATE_STOPPED;
| |
| this.m_init_stopped();
| |
| }
| |
| </source>
| |
| | |
| Im obigen Datenmodell wurde die Klasse <code>Main</code> als <code>Singleton</code> deklariert. Das heißt, von dieser
| |
| Klasse darf es zu jedem Zeitpunkt maximal ein Objekt geben. Die erste <code>if</code>-Anweisung im Konstruktor
| |
| sorgt dafür, dass damit tatsächlich nur ein <code>Main</code>-Objekt erzeugt werden kann. Das erste
| |
| <code>Main</code>-Objekt wird im Konstruktor-Objekt gespeichert. Sofern tatsächlich versucht werden
| |
| würde, ein zweites <code>Main</code>-Objekt mit diesem Konstruktor zu erzeugen, wird einfach
| |
| das schon existente <code>Main</code>-Objekt als Ergebnis zurückgegeben.
| |
| | |
| Eine sehr subtile Änderung ist allerdings hinsichtlich des Start/Stop-Buttons notwendig.
| |
| In JavaScript hängt der Wert der Variablen <code>this</code> nicht vom Definitionsort ab, sondern vom Aufrufer der Methode.
| |
| Die Variable <code>this</code> ist also – im Gegensatz zu allen anderne JavaScript-Variablen –
| |
| nicht [[Bindung|lexikalisch]] sondern [[Bindung|dynamisch]] gebunden.
| |
| | |
| Das Problem ist nun, dass die Methode <code>o_start_stop_game</code> nicht jetzt, sondern erst später aufgerufen wird und zwar
| |
| vom im HTML-Dokument enthaltenen Button-Objekt (<code>document.getElementById("d_start_stop")</code>)
| |
| sobald der Benutzer auf diesen Button klickt. Damit zeigt die Variable <code>this</code> auf dieses Button-Objekt und nicht
| |
| auf das <code>Main</code>-Objekt. Da aber die Methode <code>o_start_stop_game</code> mittels <code>this</code> auf
| |
| Attribute des <code>Main</code>-Objekts zugreifen will, muss man einen schmutzigen, aber in JavaScript-Code
| |
| häufig anzutreffenden Trick anwenden. Man muss <code>this</code> '''außerhalb''' der Funktion, die das Button-Objekt
| |
| später aufruft, in einer lexikalisch gebundenen Variablen, d.h. in irgendeiner anderen Variablen, ablegen.
| |
| Überlicherweise heißt diese Variable <code>self</code>.
| |
| | |
| <source lang="javascript">
| |
| var self = this; // Needed to overcome dynamic binding problems.
| |
| </source>
| |
| Innerhalb der Funktion, die das Button-Objekt später aufruft, wird nun <code>self</code> anstelle von <code>this</code>
| |
| verwendet.
| |
| | |
| <source lang="javascript">
| |
| document.getElementById("d_start_stop").onmousedown =
| |
| function() { self.o_start_stop_game(); };
| |
| </source>
| |
| | |
| Da <code>self</code> lexkalisch gebunden ist, tritt das zuvor beschriebene Problem nicht mehr auf.
| |
| Wenn nun innerhalb der Methode <code>o_start_stop_game</code> auf die Variable <code>this</code>
| |
| zugegrifen wird, so ist darin wie gewünscht das <code>Main</code>-Objekt enthalten.
| |
| | |
| '''Warnung:''' Dieses JavaScript-Verhalten kann gerade einem Programmieranfäger stundenlange Fehlersuche bescheren.
| |
| | |
| ===Methoden===
| |
|
| |
| Die ehemals öffentlichen Funktionen der JavaScript-Datei <code>main.js</code>
| |
| werden als Methoden des <code>Main</code>-Objekts realisiert.
| |
| | |
| <source lang="javascript">
| |
| Main.prototype =
| |
| { //////////////////////////////////////////////////////////////////////////////
| |
| // logic
| |
| //////////////////////////////////////////////////////////////////////////////
| |
| | |
| // Has to be called when the game is to be started.
| |
| m_init_started:
| |
| function ()
| |
| { // Reset the score.
| |
| this.v_score = 0;
| |
| | |
| // Set the button "d_start_stop" to be a stop button (view).
| |
| document.getElementById("d_start_stop").value = "Stopp";
| |
| | |
| var self = this; // Needed to overcome dynamic binding problems.
| |
| | |
| // React to key down and key up events.
| |
| // The following code does not work due to dynamic binding of "this" :-(((
| |
| // document.onkeydown = this.o_start_paddle_moving;
| |
| // document.onkeyup = this.o_stop_paddle_moving;
| |
| document.onkeydown =
| |
| function(p_event){ self.o_start_paddle_moving(p_event); };
| |
| document.onkeyup =
| |
| function(p_event){ self.o_stop_paddle_moving(p_event); };
| |
| | |
| // Start the timer for redrawing the canvas every 1000/FPS seconds.
| |
| this.v_timer =
| |
| window.setInterval(function(){ self.o_redraw(); }, 1000/FPS);
| |
| | |
| // Show the current score (zero points)
| |
| this.m_info("Punkte: " + this.v_score);
| |
| },
| |
| | |
| // Has to be called when the game is to be stopped.
| |
| m_init_stopped:
| |
| function ()
| |
| { // Stop the canvas redrawing timer.
| |
| window.clearInterval(this.v_timer);
| |
| | |
| // Stop reacting to key down and key up events.
| |
| document.onkeydown = null;
| |
| document.onkeyup = null;
| |
| | |
| // Set the button "d_start_stop" to be a start button (view).
| |
| document.getElementById("d_start_stop").value = "Start";
| |
| | |
| // Put the ball and the paddle on their starting positions
| |
| // and then redraw the canvas.
| |
| this.v_ball.reset();
| |
| this.v_paddle.reset();
| |
| this.m_draw();
| |
| },
| |
|
| |
| // An event observer:
| |
| // It is called presses the start/stop button.
| |
| o_start_stop_game:
| |
| function()
| |
| { // Change the current state (this is a little automaton!).
| |
| this.v_state = NEXT_STATE[this.v_state];
| |
| | |
| // Initialize the new state.
| |
| this["m_init_" + this.v_state]();
| |
| /* // Alternatively:
| |
| * if (this.v_state == STATE_STARTED)
| |
| * this.m_init_started();
| |
| * else if (this.v_state == STATE_STOPPED)
| |
| * this.m_init_stopped();
| |
| */
| |
| },
| |
| | |
| // An event observer:
| |
| // It is called whenever a "start paddle moving event" is signalled.
| |
| o_start_paddle_moving:
| |
| function(p_event)
| |
| { // console.log(this);
| |
| if (p_event.keyCode == KEY_LEFT)
| |
| this.v_paddle.startLeft();
| |
| else if (p_event.keyCode == KEY_RIGHT)
| |
| this.v_paddle.startRight();
| |
| },
| |
| | |
| // An event observer:
| |
| // It is called whenever a "stop paddle moving event" is signalled.
| |
| o_stop_paddle_moving:
| |
| function(p_event)
| |
| { this.v_paddle.stop();
| |
| },
| |
| | |
| //////////////////////////////////////////////////////////////////////////////
| |
| // view
| |
| //////////////////////////////////////////////////////////////////////////////
| |
| | |
| // Clears the canvas and redraws all sprites (ball and paddle).
| |
| m_draw:
| |
| function()
| |
| { this.v_context.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
| |
|
| |
| this.v_ball.draw();
| |
| this.v_paddle.draw();
| |
| },
| |
| | |
| // Displays information on the HTML page.
| |
| m_info:
| |
| function (p_info)
| |
| { document.getElementById("d_info").firstChild.nodeValue = p_info; },
| |
| | |
| // An event observer:
| |
| // It is called every 1000/FPS seconds, but only when the game has been started.
| |
| o_redraw:
| |
| function()
| |
| { // collision detection
| |
| var l_exit = this.v_collision.handleCollision();
| |
| | |
| if (l_exit == EVENT_EXIT)
| |
| { this.o_start_stop_game();
| |
| return;
| |
| };
| |
|
| |
| if (l_exit == EVENT_SCORE)
| |
| { // Raise the score and display it.
| |
| this.v_score++;
| |
| this.m_info("Punkte: " + this.v_score);
| |
| };
| |
|
| |
| // Move the ball and paddle.
| |
| this.v_ball.move();
| |
| this.v_paddle.move();
| |
| | |
| // Redraw the canvas.
| |
| this.m_draw();
| |
| },
| |
| | |
| //////////////////////////////////////////////////////////////////////////////
| |
| // end of prototype
| |
| //////////////////////////////////////////////////////////////////////////////
| |
| };
| |
| </source>
| |
| | |
| '''Beachte:''' Auch in den Methodendefinitionen musste der
| |
| „<code>this</code>/<code>self</code>-Hack“ mehrfach angewandt werden.
| |
| Man überlege sich in allen Fällen, warum dies notwendig ist.
| |
| | |
| Um das Problem nachzuvollziehen, sollten Sie die Trace-Anweisung
| |
| <code>console.log(this);</code> in der Methode <code>o_start_paddle_moving</code>
| |
| (in Ihrer Anwendung oder in der Musterlösung) aktivieren.
| |
| | |
| Starten Sie nun die Anwednung im Firefox und beobachten Sie die Text-Ausgabe im Firebug-Konsolen-Fester,
| |
| wenn Sie Ihren Schläger mittels Tastatur-Steuerung bewegen.
| |
| | |
| Aktivieren Sie anschließend im Rumpf der Methode <code>m_init_started</code>
| |
| die ersten beiden der folgenden Zeilen und kommentieren Sie die nachfolgenden vier Zeilen aus:
| |
| | |
| <source lang="javascript">
| |
| // document.onkeydown = this.o_start_paddle_moving;
| |
| // document.onkeyup = this.o_stop_paddle_moving;
| |
| document.onkeydown =
| |
| function(p_event){ self.o_start_paddle_moving(p_event); };
| |
| document.onkeyup =
| |
| function(p_event){ self.o_stop_paddle_moving(p_event); };
| |
| </source>
| |
| | |
| Aus obigem Code wird also:
| |
| <source lang="javascript">
| |
| document.onkeydown = this.o_start_paddle_moving;
| |
| document.onkeyup = this.o_stop_paddle_moving;
| |
| // document.onkeydown =
| |
| // function(p_event){ self.o_start_paddle_moving(p_event); };
| |
| // document.onkeyup =
| |
| // function(p_event){ self.o_stop_paddle_moving(p_event); };
| |
| </source>
| |
| | |
| Starten Sie nun Ihre Anwendung erneut
| |
| und versuchen Sie den Schläger mittels Tastensteuerung zu bewegen.
| |
| | |
| Sie werden feststellen, dass die Methode <code>o_start_paddle_moving</code>
| |
| nun nicht mehr auf das <code>Main</code>-Objekt zugreift,
| |
| sondern auf das <code>index.html</code>-Objekt (welches JavaScript in der globalen Variablen <code>document</code> zur Verfügung stellt).
| |
| Die in diesem Objekt enthaltenen Methoden <code>onkeydown</code> und <code>onkeyup</code> werden von JavaScript
| |
| aufgerufen, sobald der Benutzer irgendeine Taste auf dem Keyboard drückt bzw. loslässt.
| |
| Da im <code>index.html</code>-Objekt das Objekt <code>v_paddle</code> nicht existiert, reagiert <code>MiniPong</code>
| |
| nun bei jedem Tastendruck mit einer Fehlermeldung.
| |
| | |
| In der ursprünglichen Version von <code>main.js</code> bestand dieses Problem noch nicht,
| |
| da alle Funktionen (wie z.B. <code>o_start_paddle_moving</code>) und Variablen global definiert wurde.
| |
| Im globalen Kontext bezieht sich <code>this</code> auch auf das globale Objekt.
| |
| | |
| =Erweiterung=
| |
| | |
| Erweitern Sie die Anwendung wiederum so, dass Sie zwei Schläger unabhängig voneinander
| |
| links und rechts am Bühnenrand bewegen können, d.h., dass zwei Spieler gegeneinander spielen können.
| |
| Jeder Spieler hat seinen eigenen Score. Der erste Spieler, der 7 Punkte erreicht, gewinnt.
| |
| (Vergleiche [[HTML5-Tutorium: Canvas: MiniPong 03#Erweiterung]])
| |
| | |
| {{TBD}}
| |
| [[Medium:MiniPong04aCanvas.png|miniatur|ohne|945px|Das Objektmodell von MiniPong 04a]]
| |
| | |
| '''Musterlösung''': <code>[http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_04a/WebContent/index.html Minipong 4a]</code>
| |
| ([http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_04a SVN-Repository])
| |
| | |
| =Verbesserungsmöglichkeiten=
| |
| | |
| Die Code-Qualität von <code>MiniPongCanvas04</code> hat sich gegenüber
| |
| <code>[[HTML5-Tutorium: Canvas: MiniPong 03#Weitere_Verbesserungsm.C3.B6glichkeiten|MiniPongCanvas03]]</code>
| |
| vor allem hinsichtlich der Modularität leicht gebessert. Es besteht allerdings weiterer Verbesserungsbedarf.
| |
| | |
| {{Codequalität
| |
| | application = MiniPongCanvas04
| |
| | readability = 4
| |
| | writability = 5
| |
| | continuity = 4
| |
| | customizability = 4
| |
| | dry = 3
| |
| | demeter = 5
| |
| | verifiability = 2
| |
| | interfaces = 5
| |
| | contract = 5
| |
| | liskov = 5
| |
| | modularity = 3
| |
| }}
| |
| | |
| ==Das Prinzip der „[[Programmierprinzipien#Verst.C3.A4ndlichkeit.2C_Comprehesibility.2C_Lesbarkeit.2C_Readability|Verständlichkeit/Lesbarkeit]]“==
| |
| | |
| {{TBD}}
| |
| | |
| ==Das Prinzip der „[[Programmierprinzipien#Schreibbarkeit.2C_Writability|Schreibbarkeit]]“==
| |
| | |
| {{TBD}}
| |
| | |
| ==Das Prinzip der „[[Programmierprinzipien#Stetigkeit.2C_Continuity|Stetigkeit]]“==
| |
| | |
| {{TBD}}
| |
| | |
| ==Das Prinzip der „[[Programmierprinzipien#Konfigurierbarkeit.2C_Customizability|Konfigurierbarkeit]]“==
| |
| | |
| {{TBD}}
| |
| | |
| ==Das Prinzip „[[Don't repeat yourself]]“==
| |
| | |
| {{TBD}}
| |
| | |
| ==Das „[[Programmierprinzipien#Gesetz_von_Demeter.5B5.5D.2C_Law_of_Demeter.2C_LoD|Gesetz von Demeter]]“==
| |
| | |
| {{TBD}}
| |
| | |
| ==Das Prinzip der „[[Programmierprinzipien#.C3.9Cberpr.C3.BCfbarkeit.2C_Verifiability|Überprüfbarkeit]]“==
| |
| | |
| {{TBD}}
| |
| | |
| ==Das Prinzip „[[Programmierprinzipien#Benutze_Integrit.C3.A4tsbedingungen.2C_Make_Use_of_Integrity_Constraints.2C_Design_by_Contract.5B2.5D|Design by Contract]]“==
| |
| | |
| {{TBD}}
| |
| | |
| ==Das „[[Programmierprinzipien#Liskovsches_Substitutionsprinzip.5B3.5D.2C_LSP.2C_Ersetzbarkeitsprinzip.2C_Liskov_substitution_principle.5B4.5D|Liskovsche Substitutionsprinzip]]“==
| |
| | |
| {{TBD}}
| |
| | |
| ==Das Prinzip der „[[Programmierprinzipien#Modularit.C3.A4t.2C_Modularity.2C_Teile_und_herrsche.2C_Divide_et_impera|Modularität]]“==
| |
|
| |
|
| {{TBD}} | | {{TBD}} |
|
| |
|
| =Quellen= | | ===Klassenmodell=== |
| <references/>
| | [[Datei:MiniPong05 ClassModel Overview.png|gerahmt|rechts|Klassendiagramm]] |
| <ol start = "2">
| |
| <li>{{Quelle|Braun, H. (2011): Webanimationen mit Canvas}}</li>
| |
| <li>{{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</li>
| |
| </ol>
| |
|
| |
|
| =Fortsetzung des Tutoriums=
| |
|
| |
|
| Sie sollten nun das [[HTML-Tutorium: SVG: Hello World|Hello-World-SVG-Tutorium]] bearbeiten, sofern Sie dies noch nicht gemacht haben.
| | ==Quellen== |
| Anderenfalls können Sie gleich mit dem [[HTML-Tutorium: SVG: MiniPong|Minipong-SVG-Tutorium]] forfahren.
| | # {{Quelle|Kowarschick, W.: Multimedia-Programmierung}} |
| [[Kategorie: HTML5-Tutorium: Canvas: MiniPong]][[Kategorie: HTML5-Beispiel]][[Kategorie:Kapitel:Multimedia-Programmierung:Beispiele]] | | [[Kategorie: HTML5-Tutorium: Canvas: MiniPong]][[Kategorie: HTML5-Beispiel]][[Kategorie:Kapitel:Multimedia-Programmierung:Beispiele]] |