|
|
(67 dazwischenliegende Versionen von 2 Benutzern 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)]) |
|
| |
|
| {{In Bearbeitung}}
| | ==Ziel: Das fertige Spiel „MiniPong“ verbessern== |
| =Ziel: Simulation von Klassen in JavaScript= | | Im fünften Teil des Tutoriums wird beschrieben, wie die funktionsfähige Version von MiniPong verbessert und erweitert werden kann. |
| Im vierten Teil des Tutoriums wird beschrieben, wie man [[Klasse]]n in JavaScript nachbilden kann. | |
| | |
| Vier Klassen werden implementiert: <code>Main</code>, <code>Ball</code>, <code>Paddle</code> und <code>Collision</code>.
| |
| Es wird jeweils ein Objekt pro Klasse erstellt.
| |
| | |
| [[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==
| |
| | |
| 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.
| |
| | |
| ===<code>index.html</code>===
| |
| | |
| 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>
| |
| und fügen Sie anschließend folgende drei Zeilen in die Datei <code>index.html</code> ein (vor der Zeile, in der <code>js/main.js</code> geladen wird):
| |
| | |
| <source lang="javascript">
| |
| <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>
| |
| </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.
| |
| | |
| ===<code>ball.js</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 neu erstellten Objekt (<code>this</code>) Attribute erstellt und initialisiert.
| |
| | |
| ====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]
| |
| * The starting position of the ball in x-direction.
| |
| * @param {number} [p_y_start = BALL_Y]
| |
| * The starting position of the ball in y-direction.
| |
| * @param {number} [p_vx_start_min = BALL_VX_MIN]
| |
| * The minimal starting velocity of the ball in x-direction.
| |
| * @param {number} [p_vx_start_max = BALL_VX_MAX]
| |
| * The maximal starting velocity of the ball in x-direction.
| |
| * @param {number} [p_vy_start_min = BALL_VY_MIN]
| |
| * The minimal starting velocity of the ball in y-direction.
| |
| * @param {number} [p_vy_start_max = BALL_VY_MAX}
| |
| * The maximal starting velocity of the ball in y-direction.
| |
| */
| |
| function Ball(p_context,
| |
| p_r, p_x_start, p_y_start,
| |
| p_vx_start_min, p_vx_start_max,
| |
| p_vy_start_min, p_vy_start_max
| |
| )
| |
| {this.r = p_r || BALL_RADIUS;
| |
| | |
| this.v_x_start = p_x_start || BALL_X;
| |
| this.v_y_start = p_y_start || BALL_Y;
| |
| this.v_vx_start_min = p_vx_start_min || BALL_VX_MIN;
| |
| this.v_vx_start_max = p_vx_start_max || BALL_VX_MAX;
| |
| this.v_vy_start_min = p_vy_start_min || BALL_VY_MIN;
| |
| this.v_vy_start_max = p_vy_start_max || BALL_VY_MAX;
| |
| | |
| this.v_vx_start_delta = this.v_vx_start_max - this.v_vx_start_min;
| |
| this.v_vy_start_delta = this.v_vy_start_max - this.v_vy_start_min;
| |
| | |
| 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.
| |
| | |
| Beachten Sie bitte, 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: Auch hier 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 starting velocity vector.
| |
| */
| |
| reset:
| |
| function()
| |
| { this.x = this.v_x_start;
| |
| this.y = this.v_y_start;
| |
| this.vx = (Math.random()<0.5?1:-1)
| |
| * (this.v_vx_start_min+Math.random()*this.v_vx_start_delta);
| |
| this.vy = (this.v_vy_start_min+Math.random()*this.v_vy_start_delta);
| |
| },
| |
| | |
| /** 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>
| |
| | |
| Beachten Sie, 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 (HTML5) definiert wurden. Das hat den Vorteil, dass man mit
| |
| der üblichen Attribut-Syntax auf diese Attribute 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>
| |
| (und 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. Anschließend
| |
| sollten Sie die Methoden wieder aktivieren und testweise einmal einen Log-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 mussen. Oder Sie hätten
| |
| an jeder dieser Stellen <code>...ball.x = ...</code> durch <code>...ball.setX(...)</code>
| |
| ersetzen müssen.
| |
| | |
| ===<code>paddle.js</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>
| |
| auf 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. Dies 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 genauso definiert wie in der Klasse <code>Ball</code>:
| |
| Entweder gar nicht oder mittels echten 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. Dies kann mit Hilfe echter Getter-Methoden
| |
| recht elegant realisiert werden:
| |
| | |
| <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>
| |
| | |
| ===<code>main.js</code>===
| |
| | |
| {{TBD}}
| |
| | |
| =Weitere Verbesserungsmöglichkeiten=
| |
| | |
| {{Codequalität
| |
| | application = MiniPongCanvas04
| |
| | readability = 4
| |
| | writability = 6
| |
| | continuity = 4
| |
| | customizability = 4
| |
| | dry = 3
| |
| | demeter = 5
| |
| | verifiability = 2
| |
| | interfaces = 6
| |
| | contract = 6
| |
| | liskov = 6
| |
| | 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]] |