MMProg: Praktikum: WiSe 2018/19: Ball03: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Keine Bearbeitungszusammenfassung
 
(64 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
{{MMProg:Praktikum:WiSe 2018/19:Menü}}
{{MMProg:Praktikum:WiSe 2018/19:Menü}}


'''Musterlösung''': [https://glossar.hs-augsburg.de/beispiel/tutorium/2018/ball/WK_Ball03/web/ Web-Auftritt] ([https://gitlab.multimedia.hs-augsburg.de:8888/kowa/WK_Ball03.git Git-Repository] '''noch nicht online''')  
'''Musterlösung''': [https://glossar.hs-augsburg.de/beispiel/tutorium/2018/ball/WK_Ball03/web/ Web-Auftritt] ([https://gitlab.multimedia.hs-augsburg.de/kowa/WK_Ball03.git Git-Repository])  
==Ziel==
==Ziel==


Zeile 12: Zeile 12:


==Vorbereitung==
==Vorbereitung==
Importieren Sie das leere Git-Projekt [https://gitlab.multimedia.hs-augsburg.de:8888/kowa/Ball03.git Ball03] in WebStorm.
Importieren Sie das leere Git-Projekt [https://gitlab.multimedia.hs-augsburg.de/kowa/Ball03.git Ball03] in WebStorm.
Laden Sie anschließend mittels <code>npm i</code> alle benötigten Node.js-Module in das Projekt.
Laden Sie anschließend mittels <code>npm i</code> alle benötigten Node.js-Module in das Projekt.


Zeile 20: Zeile 20:
<source lang="javascript">
<source lang="javascript">
git remote remove origin
git remote remove origin
git remote add origin https://gitlab.multimedia.hs-augsburg.de:8888/BENUTZER/Ball03.git
git remote add origin https://gitlab.multimedia.hs-augsburg.de/BENUTZER/Ball03.git
</source>
</source>


Zeile 27: Zeile 27:
In Ihrem Projekt finden Sie drei Web-Anwendungen: <code>src/index01.html</code>, <code>src/index02.html</code> und <code>src/index03.html</code>.
In Ihrem Projekt finden Sie drei Web-Anwendungen: <code>src/index01.html</code>, <code>src/index02.html</code> und <code>src/index03.html</code>.
Allerdings existiert derzeit nur der JavaScript-Ordner <code>src/js/app01</code> für die Web-App <code>index01.html</code>.
Allerdings existiert derzeit nur der JavaScript-Ordner <code>src/js/app01</code> für die Web-App <code>index01.html</code>.
Die fehlenden JavaScript-Ordner <code>src/js/app02</code> und <code>src/js/app03</code> werden im Laufe des Tutoriums erstelltt und gefüllt.
Die fehlenden JavaScript-Ordner <code>src/js/app02</code> und <code>src/js/app03</code> werden erst zu einem späteren Zeitpunt erstellt.


Im Ordner <code>src/js/app01</code> befinden sich derzeit zwei Dateien: <code>app.js</code>
Im Ordner <code>src/js/app01</code> befinden sich derzeit zwei Dateien: <code>app.js</code>
Zeile 42: Zeile 42:


Anmerkung: Die Datei <code>webpack.config.js</code> wurde speziell für diese Struktur entwickelt.
Anmerkung: Die Datei <code>webpack.config.js</code> wurde speziell für diese Struktur entwickelt.
Die Datei <code>webpack.config.js</code> von Teil 02 ist nicht kompatibel mit der
Die im Teil 2 enthaltene Datei gleichen Namens ist damit nicht kompatibel.  
webpack-Konfigurationsdatei von Teil 3.  


===Aufgabe 1: Analyse von <code>app01</code>===
===Aufgabe 0: Analyse von <code>app01</code>===
Es gibt in <code>WK_Ball03</code> eine <code>app01</code> mit zwei Modulen <code>app</code> und <code>game</code>, die in zwei Dateien  <code>app.js</code> und <code>game.js</code> implementiert wurden.
Es gibt in <code>WK_Ball03</code> eine <code>app01</code> mit zwei Modulen <code>app</code> und <code>game</code>, die in zwei Dateien  <code>app.js</code> und <code>game.js</code> implementiert wurden.


Zeile 54: Zeile 53:
* Dieser Ball wird durch einen einfarbigen Kreis mit Rand visualisiert.
* Dieser Ball wird durch einen einfarbigen Kreis mit Rand visualisiert.
* Der Ball bewegt sich gemäß seinen Initialparametern schräg über die Bühne.
* Der Ball bewegt sich gemäß seinen Initialparametern schräg über die Bühne.
* Bei einer Kollission mit dem Bühnenrand ändert er seine Bewegungsrichtung.
* Bei einer Kollision mit dem Bühnenrand ändert er seine Bewegungsrichtung.


Die gesamte beschriebene Spiellogik ist in einer Datei enthalten: <code>game.js</code>. So eine Datei bezeichne ich als '''Moloch'''.
Die gesamte beschriebene Spiellogik ist in einer Datei enthalten: <code>game.js</code>. So eine Datei bezeichne ich als '''Moloch'''.
Zeile 65: Zeile 64:


* <code>index01.html</code>: Eine HTML-Seite zum Starten der eigentlichen Web-Anwendung, sobald sie vom Browser geladen wird.
* <code>index01.html</code>: Eine HTML-Seite zum Starten der eigentlichen Web-Anwendung, sobald sie vom Browser geladen wird.
* <code>head.css</code>: Eine CSS-Datei, die das Layout der HTML-Datei festlegt (insbesondere die Hintergrundfarbe), solange die App geladen wird. Diese DAtei wird direkt von <code>index01.html</code> eingebunden. Mittels webpack wird der Inhalt dieser Datei komprimiert und direkt in die zugehörige HTML-Datei injiziert.
* <code>head.css</code>: Eine CSS-Datei, die das Layout der HTML-Datei festlegt (insbesondere die Hintergrundfarbe), solange die App geladen wird. Diese Datei wird mittels eines <code>link</code>-Elements direkt von <code>index01.html</code> eingebunden. Mittels webpack wird der Inhalt dieser Datei komprimiert und in die zugehörige HTML-Datei injiziert, indem das <code>link</code>-Element durch ein <code>script</code>-Element ersetzt wird.
* <code>app.css</code>: Eine CSS-Datei, die das Layout der Web-App festlegt. Diese Datei wird von <code>app.js</code> importiert. Mittels webpack wird dafür gesorgt, dass (die transformierte Version von) der Inhalt der CSS-Datei in <code>index01.html</code> von <code>app.js</code> injiziert wird, sobald diese Datei vollständig geladen wurde.  
* <code>app.css</code>: Eine CSS-Datei, die das Layout der Web-App festlegt. Diese Datei wird von <code>app.js</code> importiert. Mittels webpack wird dafür gesorgt, dass der JavaScript-Code von  <code>app.js</code> eine komprimierte Version vom Inhalt der CSS-Datei in <code>index01.html</code> injiziert wird, sobald die Web-App vollständig geladen wurde.  
* <code>GameLoop</code>: Eine Game-Loop-Klasse, die Ihnen im Rahmen des Praktikums zur Verfügung gestellt wird. Diese Modul benutzt weitere Module der WK-Bibliothek: <code>Automaton</code> und <code>EventDispatcher</code>.  
* <code>GameLoop</code>: Eine Game-Loop-Klasse, die Ihnen im Rahmen des Praktikums zur Verfügung gestellt wird. Dieses Modul benutzt weitere Module der WK-Bibliothek: <code>Automaton</code> und <code>EventDispatcher</code>.  
* <code>wait</code>: Eine asynchrone Funktion, die in der WK-Biliothek bereitsteht, um eine gewisse Zeitspanne zu warten, bevor eine Aktion durchgeführt wird. Hier wird sie verwendet, um den Start der App ein paar Millisekunden zu verzögern, nachdem die App vollständig geladen und sichbar gemacht wurde.  
* <code>wait</code>: Eine asynchrone Funktion, die in der WK-Biliothek bereitsteht, um eine gewisse Zeitspanne zu warten, bevor eine Aktion durchgeführt wird. Hier wird sie verwendet, um den Start der App ein paar Millisekunden zu verzögern, nachdem die App vollständig geladen und sichbar gemacht wurde.  
* PixiJS: Eine sehr mächtige 2D-Grafik-Bibliothek, die sehr modular aufgebaut ist. Aus dieser Biliothek werden zwei Klassen verwendet (auch diese verwenden – wie die <code>GameLoop</code>– zahlreiche weitere Module, um ihre Aufgaben zu erfüllen; es würde allerdings das Diagramm vollkommen unlesbar machen, alle diese Module hier aufzuführen):
* PixiJS: Eine sehr mächtige 2D-Grafik-Bibliothek, die sehr modular aufgebaut ist. Aus dieser Biliothek werden zwei Klassen verwendet (auch diese verwenden – wie die <code>GameLoop</code> – zahlreiche weitere Module, um ihre Aufgaben zu erfüllen; es würde allerdings das Diagramm vollkommen unlesbar machen, alle diese Module hier aufzuführen):
** [http://pixijs.download/dev/docs/PIXI.Application.html <code>PIXI.Application</code>]: Die PixiJS-Root-Klasse. Das zugehörige Objekt enthält alle Elemente, um graphische Elemente effizient auf einem HTML-Canvas-Element darzustellen.
** [http://pixijs.download/dev/docs/PIXI.Application.html <code>PIXI.Application</code>]: Die PixiJS-Root-Klasse. Das zugehörige Objekt enthält alle Elemente, um graphische Elemente effizient auf einem HTML-Canvas-Element darzustellen.
** [http://pixijs.download/dev/docs/PIXI.Graphics.html <code>PIXI.Graphics</code>]: Eine PixiJS-Klasse zum Zeichnen einfacher geometrischer Formen wie Kreise, Rechtecke, Linien etc.
** [http://pixijs.download/dev/docs/PIXI.Graphics.html <code>PIXI.Graphics</code>]: Eine PixiJS-Klasse zum Zeichnen einfacher geometrischer Formen wie Kreisen, Rechtecken, Linien etc.
* <code>app</code>: Das Hauptmodul. Es stellt mit Hilfe der zuvor genannten Module die Spielumgebung zur Verfügung: Eine PixiJS-Bühne, eine Game Loop sowie eine CSS-Datei. Sobald die Umgebung erstellt und initialisiert wurde, startet diese Modul leicht verzögert die Update- und die Render-Funktion des eigentlichen Spielmoduls <code>game</code>.
* <code>app</code>: Das Hauptmodul. Es stellt mit Hilfe der zuvor genannten Module die Spielumgebung zur Verfügung: Eine PixiJS-Bühne, eine Game Loop sowie eine CSS-Datei. Sobald die Umgebung erstellt und initialisiert wurde, startet diese Modul leicht verzögert die Update- und die Render-Funktion des eigentlichen Spielmoduls <code>game</code>.
* <code>game</code>: Das immer noch molochartige Spielmodul, das im Rahmen des Praktikums in diverse Einzelmodule aufgeteilt werden wird.
* <code>game</code>: Das immer noch molochartige Spielmodul, das im Rahmen des Praktikums in diverse Einzelmodule aufgeteilt werden wird.


===Aufgabe 1: Modularisierung des Modells===
===Aufgabe 1: Modularisierung ders Game-Moduls===
(Musterlösung: [https://gitlab.multimedia.hs-augsburg.de/kowa/WK_Ball03.git Gitlab: <code>WK_Ball03</code>, <code>app01</code>];
unter <code>app01a</code> finden Sie eine Variante, in der das Update- und das Render-Modul nicht als Funktionsmodule, sondern als Klassenmodule realisiert wurden) 
 
Ein wichtiges [[Programmierprinzipien#Modularit.C3.A4t.2C_Modularity.2C_Teile_und_herrsche.2C_Divide_et_impera|Programmierprinzip]] besagt, dass jedes Modul nur eine Aufgabe erledigen soll. Bislang ist das Game-Modul in dieser Hinsicht noch ziemlich schlecht, da es zahlreiche Aufgabe erfüllt. Dies sollen Sie ändern.
Ein wichtiges [[Programmierprinzipien#Modularit.C3.A4t.2C_Modularity.2C_Teile_und_herrsche.2C_Divide_et_impera|Programmierprinzip]] besagt, dass jedes Modul nur eine Aufgabe erledigen soll. Bislang ist das Game-Modul in dieser Hinsicht noch ziemlich schlecht, da es zahlreiche Aufgabe erfüllt. Dies sollen Sie ändern.


[[Datei:WK Ball03 ClassModel01 2018.png|mini|600px|gerahmt|ohne|geplantes Klassendiagramm von <code>app01</code>]]
[[Datei:WK Ball03 ClassModel01 2018.png|mini|600px|gerahmt|ohne|geplantes Klassendiagramm von <code>app01</code>]]


Als Ersatz für das Game-Modul sollen insgesamt sechs Komponenten erstellt werden „Bühne (Model)“, „Ball (Model)“, „Ball (View)“, „Kollissionserkennung und -behandlung (Model)“,  „Update-Funktion (Model)“, „Render-Funktion (View)“. Oben sehen Sie das zugehörige Klassendiagramm, bestehend aus insgesamt sieben Modulen:
Als Ersatz für das Game-Modul sollen insgesamt sechs Komponenten erstellt werden „Bühne (Model)“, „Ball (Model)“, „Ball (View)“, „Kollisionserkennung und -behandlung (Model)“,  „Update-Funktion (Model)“ und „Render-Funktion (View)“. Oben sehen Sie das zugehörige Klassendiagramm, bestehend aus insgesamt sieben Modulen:


* Modul <code>app</code>: Zuständig für die Initialisierung der Spielumgebung und das anschließende Starten des Spiels. Dieses Modul besteht aus einer Folge von Anweisungen, die direkt zum Start der Web-App ausgeführt werden.
* Modul <code>app</code>: Zuständig für die Initialisierung der Spielumgebung und das anschließende Starten des Spiels. Dieses Modul besteht aus einer Folge von Anweisungen, die direkt beim Start der Web-App ausgeführt werden.
* Klasse <code>ModelStage</code>: Jedes Objekt dieser Klasse repräsentiert das Modell einer Spielbühne. Üblicherweise gibt es nur eine Spielbühne. Es gibt aber auch Spielsituationen mit mehreren Spielbühnen ({{zB}} wenn neben der Hauptbühne, die nur einen Ausschnitt der Spielwelt zeigt, eine Minimap existiert, die einen Überblick auf die Spielwelt gewährt). Diese Klasse kann in vielen Anwendungen wiederverwendet werden. Im Laufe der Zeit werden allerdings sicher noch weitere Attribute und Methoden ergänzt werden.
* Klasse <code>ModelStage</code>: Jedes Objekt dieser Klasse repräsentiert das Modell einer Spielbühne. Üblicherweise gibt es nur eine Spielbühne. Es gibt aber auch Spielsituationen mit mehreren Spielbühnen ({{zB}} wenn neben der Hauptbühne, die nur einen Ausschnitt der Spielwelt zeigt, eine Minimap existiert, die einen Überblick auf die Spielwelt gewährt). Diese Klasse kann in vielen Anwendungen wiederverwendet werden. Im Laufe der Zeit werden allerdings sicher noch weitere Attribute und Methoden ergänzt werden.
* Klasse <code>ModelCircle</code>: Eine ebenfalls sehr gut wiederverwendbare Klasse, die für kreisförmige Objekte aller Art in einem Spiel zum Einsatz kommen kann. Normalerweise gibt es zahlreiche kreisförmige Objekte in einem Spiel. Auch hier wird es im Laufe der Zeit sicher diverse Erweiterungen geben.
* Klasse <code>ModelCircle</code>: Eine ebenfalls sehr gut wiederverwendbare Klasse, die für kreisförmige Objekte aller Art in einem Spiel zum Einsatz kommen kann. Normalerweise gibt es zahlreiche kreisförmige Objekte in einem Spiel. Auch hier wird es im Laufe der Zeit sicher diverse Erweiterungen geben.
* Funktion <code>collisionCircleStage</code>: Ein Modul, das eine Funktion zur Kollissionserkennung und -behandlung  von (beweglichen) Kreisobjekten mit (unbeweglichen) Rändern der Bühne zur Verfügung stellt. Auch dieses Modul kann sehr gut wiederverwendet werden. Allerdings gibt es (wie bei allen Fragen der Kollissionserkennung und -behandlung) noch zahlreiches Verbesserungspotential.  
* Funktion <code>collisionCircleStage</code>: Ein Modul, das eine Funktion zur Kollisionserkennung und -behandlung  von (beweglichen) Kreisobjekten mit (unbeweglichen) Rändern der Bühne zur Verfügung stellt. Auch dieses Modul kann sehr gut wiederverwendet werden. Allerdings gibt es (wie bei allen Fragen der Kollisionserkennung und -behandlung) noch zahlreiches Verbesserungspotential.  
* Funktionssammlung <code>update</code>: Dieses Modul enthält zwei Funktionen: <code>initUpdater</code> und <code>update</code>. Die erste Funktion dient dazu, die Update-Funktion zu initialisieren, {{dh}}, ihr die Objekte bekannt zu geben, die sie regelmäßig aktualisieren soll. Die zweite Funktion wird der Game Loop als Callback-Funktion übergeben, um diese Aktualisierungen regelmäßig ({{zB}} genau sechzig mal pro Sekunde) durchzuführen. Außerdem ist die  Update-Funktion dafür zusäntdig, die Kollissionserkennung und -behandlung zu initiieren. Sie selbst führt diese Aufgabe allerdings nicht durch, sondern überträgt diese Aufgabe an geeignete Hilfsmodule (in diesem Fall das Modul <code>collisionCircleStage</code>). Dieses Modul kann meist nicht problemlos wiederverwendet werden, da jede Web-App ihre ganz eigene Objekt-Welt verwaltet.
* Funktionssammlung <code>update</code>: Dieses Modul enthält zwei Funktionen: <code>initUpdater</code> und <code>update</code>. Die erste Funktion dient dazu, die Update-Funktion zu initialisieren, {{dh}}, ihr die Objekte bekannt zu geben, die sie regelmäßig aktualisieren soll. Die zweite Funktion wird der Game Loop als Callback-Funktion übergeben, um diese Aktualisierungen regelmäßig ({{zB}} genau sechzig mal pro Sekunde) durchzuführen. Außerdem ist die  Update-Funktion dafür zuständig, die Kollisionserkennung und -behandlung zu initiieren. Sie selbst führt diese Aufgabe allerdings nicht durch, sondern überträgt sie an geeignete Hilfsmodule (in diesem Fall das Modul <code>collisionCircleStage</code>). Das Update-Modul kann meist nicht problemlos wiederverwendet werden, da jede Web-App ihre ganz eigene Objektwelt verwaltet.
* Klasse <code>ViewCircle</code>: Eine sehr gut wiederverwendbare Klasse, die die kreisförmige Objekte visualisieren soll. In Aufgabe 1 wird der Ball mit Hilfe von PixiJS-Graphics-Befehlen gezeichnet (weshalb die Klasse vielleicht besser <code>ViewCircleGraphics</code> heißen sollte). In einer späteren Aufgabe sollen sie die Visualisierung mit Hilfe von Bildern unter Zuhilfenahme der PixiJS-Klasse <code>Sprite</code> realisieren. (Diese Klasse sollte daher eventuell  <code>ViewCircleSprite</code> genannt werden.)
* Klasse <code>ViewCircle</code>: Eine sehr gut wiederverwendbare Klasse, die die kreisförmigen Objekte visualisieren soll. In Aufgabe 1 wird der Ball mit Hilfe von PixiJS-Graphics-Befehlen gezeichnet (weshalb die Klasse vielleicht besser <code>ViewCircleGraphics</code> heißen sollte). In einer späteren Aufgabe sollen sie die Visualisierung mit Hilfe von Bildern unter Zuhilfenahme der PixiJS-Klasse <code>Sprite</code> realisieren. (Diese Klasse sollte daher eventuell  <code>ViewCircleSprite</code> genannt werden.)
* Funktionssammlung <code>render</code>: Dieses Modul enthält zwei Funktionen: <code>initRenderer</code> und <code>render</code>. Die erste Funktion dient dazu, die Render-Funktion zu initialisieren, {{dh}}, ihr die Objekte bekannt zu geben, deren Visualisierung sie regelmäßig aktualisieren soll. Die zweite Funktion wird der Game Loop als Callback-Funktion übergeben, um diese Aktualisierungen regelmäßig (möglichst sechzig mal pro Sekunde) durchzuführen. Im Gegensatz zur Update-Funktion ist diese Funktion sehr gut wiederverwendbar. Ihre einzige Aufgabe ist es, für alle View-Objekte, die in einen Array enthalten sind (das ihr bei Initialisierung übergeben wurde), jeweils die Update-Funktion aufzurufen, die jedes View-Objekt bereitstellen muss.
* Funktionssammlung <code>render</code>: Dieses Modul enthält zwei Funktionen: <code>initRenderer</code> und <code>render</code>. Die erste Funktion dient dazu, die Render-Funktion zu initialisieren, {{dh}}, ihr die Objekte bekannt zu geben, deren Visualisierung sie regelmäßig aktualisieren soll. Die zweite Funktion wird der Game Loop als Callback-Funktion übergeben, um diese Aktualisierungen regelmäßig (möglichst sechzig mal pro Sekunde) durchzuführen. Im Gegensatz zur Update-Funktion ist diese Funktion sehr gut wiederverwendbar. Ihre einzige Aufgabe ist es, für alle View-Objekte, die in einen Array enthalten sind (das ihr bei Initialisierung übergeben wurde), jeweils die Update-Funktion aufzurufen, die jedes View-Objekt bereitstellen muss.


Im Ordner <code>js/app/game01</code> finden Sie für jedes dieser drei Module eine Datei. Im Ordner <code>js</code> finden Sie außerdem das Hauptmodul
====Erstellen der Modul-Struktur====
<code>app01.js</code>. Der notwendige Code, um die Spielumgebung bereitzustellen ist darin bereits denthalten. Ihre Aufgabe
Legen Sie zunächst zwei Ordner an:
ist es, die Funktionen und Klassen, die in den anderen Dateien enthalten sind, zu implementieren. Welche Elemente die entsprechenden Module enthalten sollen, können Sie dem nachfolgenden Diagramm entnehmen.
* <code>src/js/app01/model</code>
* <code>src/js/app01/view</code>


====Modularisierung der Model-Komponenten====
Erzeugen Sie dann innerhalb des Ordners <code>src/js/app01/</code> für jedes Modul eine leere EcmaScript-Datei:
*<code>app.js</code> (diese Datei gibt es schon, sie wird im Folgenden allerdings modifiziert.
* <code>model/ModelStage.js</code>
* <code>model/ModelCircle.js</code>
* <code>model/collisionCircleStage.js</code>
* <code>model/update.js</code>
* <code>view/ViewCircle.js</code>
* <code>view/render.js</code>


Die Idee, jedes Modul in einer eigenen Datei zu platzieren, geht auf Java zurück. Im Gegensatz zu anderen Sprachen erzwingt Java, dass jede Klasse in eine eigene Datei geschrieben werden muss (vgl. [https://stackoverflow.com/questions/1318712/why-is-each-public-class-in-a-separate-file Why is each public class in a separate file?]).
Außerdem gibt es in Java nur eine Art von Modulen: [[Klasse (OOP)|Klasse]]n. Beide Restriktionen gelten für JavaScript nicht. Sie sollten sich aber angewöhnen, zumindest die Regel „ein Modul === eine Datei mit demselben Namen plus der Endung <code>.js</code>“ zu beachten.


Fügen Sie in jede Datei einen geeigneten Rahmencode ein. In ein Modul, das eine Klasse enthält (siehe obiges Diagramm), fügen Sie bitte folgenden Code ein,
[[Datei:WK Ball03 ClassModel01.png|gerahmt|ohne| Klassendiagramm 1 mit Funktionen, Attributen und Methoden]]
'''wobei Sie jeweils <code>CLASSNAME</code> durch den Klassenname ersetzen müssen'''.


Gehen Sie folgendermaßen vor:
<source lang="javascript">
class CLASSNAME {
  constructor() {
  }
}


* Kopieren Sie den Inhalt der Datei <code>game00.js</code> in die Datei <code>game01/game.js</code>, '''ohne die darin enthaltenen Import-Befehle zu überschreiben.''' (Wenn Sie jetzt <code>index01.html</code> im Browser öffnen, sollte der Ball über die Bühne flitzen – zumindest, wenn Sie nicht vergessen haben, <code>grunt watch</code> zu aktivieren :-) )
export default CLASSNAME;
* Lagern Sie den Code der Kollissionserkennung und -behandlung aus der Funktion <code>modelUpdates</code> in die Funktion <code>collision</code> in der Datei <code>model/collision.js</code> aus. (''Achtung'': Der Code zur Berechnung der neuen Position und neuen Geschwindigkeit des Balls darf nicht in diese Datei ausgelagert werden.) Beachten Sie, dass die Funktion  <code>collision</code> die Inputparameter <code>p_immovable</code> und <code>p_movable</code> besitzt. Die im kopierten Code enthaltenen Variablennamen <code>v_ball</code> und <code>v_stage</code> müssen entsprechend umbenannt werden.
</source>
* Rufen Sie in der Funktion  <code>modelUpdates</code> die neu erstellte (und von <code>game.js</code> auch schon importierte) Funktion <code>collision</code> geeignet auf. Bei einem Test der Web-Anwendung sollte sich der Ball wieder wie gewohnt über die Bühne bewegen.
oder auch (je nachdem, welche Klammerstruktur sie bevorzugen; bei der folgenden Struktor müssen Sie den Code allerdings <code>Strg-Shift-v</code> <code>Paste without Formating</code> einfügen, damit sie erhalten bleibt)
*Erstellen Sie nun die Klasse <code>Stage</code>. Gemäß dem obigen Diagramm hat sie sechs Attribute: <code>width</code>, <code>height</code>, <code>lft</code>, <code>rgt</code>, <code>top</code> und <code>btm</code>. Jedes dieser Attribute kann entweder als Wert geseichert oder mit Hilfe von Getter- und Setter-Methoden berechnet werden. Die Entscheidung darüber, wie man vorgehen sollte, hängt davon ab, wie oft man lesend auf die Attribute zugreift und wie oft sie sich verändern. Bei einer Bühne ändern sie sich (nachdem die Bühne einmal initialisiert wurde) meist gar nicht oder nur selten (wenn {{zB}} ein Eventhandler nach dem Skalieren des Browserfensters die Bühnengröße automatisch anpasst). Gelesen werden die Attribute dagegen oft (bei jeder Kollissionserkennung und -behandlung eines beweglichen Objektes mit der Bühne). Daher bietet es sich hier an, alle Attribute in (internen) Objektvariablen zu speichern. Erstellen Sie also in der Klasse folgenden Konstruktor:
<source lang="javascript">
<source lang="javascript">
constructor()
class CLASSNAME
{
{ constructor()
   this.v_lft = 0;
  {  
  this.v_rgt = 0;
   }
  this.v_top = 0;
}
  this.v_btm = 0;
 
export default CLASSNAME;
</source>


  this.v_width  = 0;
Für Funktionsmodule, die mehr als eine Funktion exportieren (hier sind das die Module <code>update</code> und <code>render</code>; siehe oben), sollten Sie folgenden Code einfügen (wobei Sie die Funktionsnamen natürlich geeignet ersetzen müssen):
  this.v_height = 0;
 
<source lang="javascript">
function FUNKTIONSNAME_1()
{
}
 
function FUNKTIONSNAME_2()
{
}
}
export { FUNKTIONSNAME_1, FUNKTIONSNAME_2 };
</source>
</source>


* Dieser Konstruktor legt für jedes der gewünschten Attribute ein passendes objektinternes Attribut an und initialisiert es mit dem Wert <code>0</code>. Da es in ECMAScript bislang noch keine privaten Klassenmitglieder gibt, kann man bei jedem Bühnenobjekt, das man später anlegt, auch auf diese Attribute zugreifen. Das sollten Sie aber tunlichst lassen. Die Namen wurde extra so gewählt, dass man erkennt, dass es sich eigentlich um private Elemente des Objekts handelt. Um lesend auf die Attribute zugreifen zu können, müssen Sie als nächstes für jedes Attribut eine passende [[Getter]]-Methode hinter dem Konstruktor einfügen:
Beachten Sie, dass Sie später diese Funktionen ({{zB}} in der Datei <code>app.js</code>) mittels
<source lang="javascript">
import { FUNKTIONSNAME_1, FUNKTIONSNAME_2 } from './ORDNER/MODULNAME.js';
</source>
importieren können.
 
Für Funktionsmodule, die nur eine Funktion exportieren (hier ist das das Modul <code>collisionCircleStage</code>), sollten Sie folgenden Code einfügen (wobei Sie den Funktionsnamen natürlich ebenfalls geeignet ersetzen müssen):


<source lang="javascript">
<source lang="javascript">
get width() { return this.v_width; }
function FUNKTIONSNAME()
get height() { return this.v_height; }
{  
}


get lft() { return this.v_lft; }
export default FUNKTIONSNAME;
get rgt() { return this.v_rgt; }
get top() { return this.v_top; }
get btm() { return this.v_btm; }
</source>
</source>


* Nun können die Attribute des Bühnenobjektes noch nicht aktualisiert werden. Wenn die Werte nur bei Spielstart initialisiert werden, könnte man diese Aufgabe dem Konstruktor übertragen. Allerdings kann sich untergewissen Umständen die Bühnengröße im Laufe der Zeit ändern. Daher ist es sinvoll, auch geeignete Setter-Funktionen für die sechs Attribute zu definieren. Und hier zeigt sich der Vorteil, den der Einsatz von Setter-Funktionen mit sich bringt: Die Werte der Attribute hängen voneinander ab. Zum Beispiel ist die Bühnenbreite gleich dem Abstand zwischen rechtem und linken Rand. Bei der Änderung eines Attributwertes kann man nun automatisch davon abhängige Attributwerte ebenfalls modifizieren, sodass die sogenannten Integritätsbedingungen „Breite gleich Abstand zwischen linken und rechtem Rand“ sowie „Höche gleich Abstand zwischen unterem und oberend Rand“ immer erfüllt sind:
Da hier die Funktion als Default-Objekt exportiert wird, können Sie diese Funktion später mittels


<source lang="javascript">
<source lang="javascript">
set width (p_w) { this.v_width  = p_w; this.v_rgt = this.v_lft + p_w; }
import FUNKTIONSNAME from './ORDNER/MODULNAME.js';
set height(p_h) { this.v_height = p_h; this.v_btm = this.v_top + p_h; }
</source>
importieren. Hier können bzw. müssen Sie beim Importieren also auf die geschweiften Klammern verzichten.
Sollten Sie die geschweiften Klammern unbedingt verwenden wollen,
müssten Sie die Exportanweisung anlog zum Modul mit mehreren Funktionsobjekten definieren.
 
Starten Sie nun <code>npm run watch</code> und überprüfen Sie , ob die Web-App <code>app01</code>
ausführbar ist. Das sollte auf jeden Fall funktionieren, da Sie ja an der App ({{dh}} an den Dateien
<code>app.js</code> und <code>game.js</code> bislang gar nichts geändert haben.
 
Importieren Sie nun die fünf Module, auf die das Modul <code>app</code> im obigen Diagramm verweist,
in die Datei <code>app.js</code>. Beachten Sie, dass die Syntax der Importanweisungen von den Exportanweisungen abhängen,
die im jeweiligen Modul verwendet wurde (siehe Anmerkungen zum Funktionscode zuvor).
 
Wenn Sie keine syntaktischen Fehler gemacht haben, sollte webpack die App immer noch fehlerfrei übersetzen und ausführen können.
WebStorm warnt Sie allerdings bei den Import-Anweisungen, dass Sie Objekte improtieren, die Sie gar nicht verwenden.
Das stimmt derzeit ja auch, also ist alles in Ordnung.
 
====Implementierung der Komponenten====
 
=====<code>ModelStage</code>=====
[[Datei:WK Ball03 ClassModel01 ModelStage 2018.png|mini|600px|gerahmt|ohne| Attribute und Methoden der Klasse <code>ModelStage</code>]]
 
Implementieren Sie zunächst die Klasse <code>StageModel</code>. Im UML-Klassendiagramm sehen Sie, dass jedes Objekt dieser Klasse vier Attribute hat:
<code>left</code>, <code>right</code>, <code>top</code>, <code>bottom</code>.
Es sollen jeweils Zahlen darin gespeichert werden. Allerdings ist JavaScript untypisiert, so dass Sie diese
Bedingungen höchstens in [[JSDoc]]-Kommentaren formulieren können. Erschwerend kommt hinzu,
das man in EcmaScript-Klassen derzeit Attribute nur mit Hilfe von [[Getter]]- und [[Setter]]-Methoden definieren kann.
Direkt kann man sie zurzeit nur innerhalb des Konstruktors erzeugen.


set lft(p_x) { this.v_lft = p_x; this.v_width = this.v_rgt - p_x; }
Also implementieren Sie nur den Konstruktor:
set rgt(p_x) { this.v_rgt = p_x; this.v_width  = p_x - this.v_lft; }
<source lang="javascript">
set top(p_y) { this.v_top = p_y; this.v_height = this.v_btm - p_y; }
constructor({left:  p_left  = 0,
set btm(p_y) { this.v_btm = p_y; this.v_height = p_y - this.v_top; }
            right:  p_right = 0,
            top:    p_top    = 0,
            bottom: p_bottom = 0
          })
{ this.left  = p_left;
  this.right  = p_right;
  this.top    = p_top;
  this.bottom = p_bottom;
}
</source>
</source>


* Bislang enthalten alle Attribute eines Bühnenobjekt den Wert <code>0</code>, nachdem es mit Hilfe des Befehls <source lang="javascript"> let buehne = new Stage()</source>erzeugt wurde. Das ist eine ziemlich unbrauchbare Bühne. Mann könnte es nut mittels <source lang="javascript">buehne.width = 500; buehne.height = 400;</source> an die gewünschten Gegebenheiten anpassen. Die Setterfunktionen würden dann nicht nur die Breite und Höhe, sondern auch gleich noch den rechten und den unteren Rand entsprechend anpassen. Dieses Vorgehen ist aber etwas umständlich. Schöner ist es, wenn man die Initialwerte dem neuen Objekt gleich bei dessen Erzeugung übergeben kann: <source lang="javascript">new Stage( { width: 500, height: 400 } )</source> Um dies zu erreichen, übergeben Sie dem Konstrouktor ein Initialisierungsobjekt, dessen Attribute mit Hilfe der zuvor definierten Setter-Funktionen dem Objekt noch während der Erzeugung zugewiesen werden. Ergänzen Sie im Konstruktor den Parameter <source lang="javascript">p_config = {}</source> und fügen Sie dann folgende Schleife<ref>[https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/ ES6 In Depth: Iterators and the for-of loop]</ref> als letzten Befehl in den Konstruktor ein:
Hier kommt eine Parametersyntax zum Einsatz, die in EcmaScript 6 unter dem Namen [https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Destrukturierende_Zuweisung Destructuring] eingeführt wurde.


In EcmaScript 5 hätten Sie das noch folgendermaßen schreiben müssen:
<source lang="javascript">
<source lang="javascript">
for (let l_key of Object.keys(p_config))
constructor(p_config)
{
{ this.left  = p_config.left    == null ? 0 : p_config.left;
   this[l_key] = p_config[l_key];
  this.right  = p_config.right  == null ? 0 : p_config.right;
   this.top    = p_config.top    == null ? 0 : p_config.top;
  this.bottom = p_config.bottom  == null ? 0 : p_config.bottom;
}
}
</source>
</source>


* Diese Schleife liest alle Attribute aus dem Initialisierungsobjekt <code>p_config</code> aus und weist sie dem neu erstellten Objekt <code>this</code> unterdemselben Namen als Attribut zu. Wenn das Initialisierungsobjekt korrekt ist und die Attribute mit den Namen <code>width</code>, <code>height</code>, <code>lft</code>, <code>rgt</code>, <code>top</code> bzw. <code>btm</code> enthält, werden die entsprechenden Setterfunktionen aufgerufen. ''Achung:'' Es wird nicht sichergestellt, dass nur diese sechst Attributwerte im neuen Bühnenobjekt gespeichert werden. Jedes Attribut, das in <code>p_config</code> übergeben wird, wird ins Bühnenobjekt kopiert. Hier ist wieder Programmierdisziplin gefragt.
In beiden Fällen erwartet der Konstruktor ein Objekt als Argument, das die Initialwerte für die vier Attribute enthält. Sollte ein Wert nicht angegeben werden,
so wird er mit dem Defaultwert <code>0</code> initialisiert.
 
Probieren Sie aus, ob ihr Code funktioniert. Fügen Sie in die Datei <code>app.js</code> folgende Befehle ein und sehen Sie nach,
was im Konsolfenster des Browsers ausgegeben wird, wenn Sie die Web-App starten.
 
<source lang="javascript">
const
  c_model_stage =
    new ModelStage
        ({ "left":  0,
          "right":  document.documentElement.clientWidth,
          "top":    0,
          "bottom": document.documentElement.clientHeight
        });
 
console.log(c_model_stage);
</source>
 
Den <code>console.log</code>-Befehl sollten Sie anschließend wieder löschen, der wird nicht mehr benötigt.
Die Konstante sollte dagegen bestehen bleiben.
 
Übrigens, mittels geeigneten [[JSDoc]]-Kommentare, können Sie zumindest dokumentiren, dass in den Attributen eines Stageobjektes nur Zahlen gespeichert werden dürfen:
 
Folgender Kommentar sollte vor der Definition der Klasse stehen.
<source lang="javascript">
/**
* @class ModelView
*
* @property {number} left  - x position of the left border
* @property {number} right  - x position of the right border
* @property {number} top    - y position of the top border
* @property {number} bottom - y position of the bottom border
*/
</source>
 
Und folgender Kommentar sollte vor der Definition des Konstruktors stehen:
<source lang="javascript">
/**
* @param {Object} p_config
* @param {number} [p_config.left = 0]
* @param {number} [p_config.right = 0]
* @param {number} [p_config.top = 0]
* @param {number} [p_config.bottom = 0]
*/
</source>
 
=====<code>ModelCircle</code>=====
[[Datei:WK Ball03 ClassModel01 ModelCircle 2018.png|mini|500px|gerahmt|ohne| Attribute und Methoden der Klasse <code>ModelCircle</code>]]
 
Implementieren Sie die Klasse <code>ModelCircle</code> analog zur Klasse <code>ModelStage</code>.
 
Im UML-Diagramm dieser Klasse wird allerdings noch die Methode <code>update</code> mit dem Inputparameter <code>p_delta_s</code> aufgeführt.
Implementieren Sie diese Methode ebenfalls. Im Rumpf der Methode müssen Sie die ersten beiden Zeilen der Funktion <code>update</code> einfügen,
die Sie in der Datei <code>game.js</code> vorfinden und den Bezeichner <code>v_ball</code> geeignet umbenennen.
 
Testen Sie diese Klasse, indem Sie folgende Konstante in die Datei <code>app.js</code> einfügen:
 
<source lang="javascript">
const
  c_model_ball =
    new ModelCircle
    ({ "r":  75,
      "x":  75,
      "y":  75,
      "vx": 400,
      "vy": 300
    });
</source>
 
Fügen Sie anschließend folgende Befehle in  <code>app.js</code> ein und überprüfen Sie im Konsolfenster des Browsers, ob alles funktioniert.
 
<source lang="javascript">
console.log(c_model_ball);
c_model_ball.update(1/60);
console.log(c_model_ball);
</source>
 
Diese drei Testbefehle sollten Sie anschließend wieder löschen.
 
=====<code>collisionCircleStage</code>=====
[[Datei:WK Ball03 ClassModel01 collisionCircleStage 2018.png|mini|250px|gerahmt|ohne| Funktion <code>collisionCircleStage</code>]]
 
In die Funktion <code>collisionCircleStage</code> in der Datei <code>collisionCircleStage.js</code>
fügen Sie zunächst die beiden Parameter ein, die Sie im obigen Diagramm vorfinden. In den Rumpf fügen Sie die restlichen Befehle
aus der Funktion <code>update</code> der Datei <code>game.js</code> ein. Das sind die Befehle zur Kollisionserkennung und -behandlung.
Beachten Sie, dass die Objekte nicht mehr <code>v_ball</code> und <code>v_stage</code> heißen, sondern <code>p_circle</code> und <code>p_stage</code>.
 
Diese Funktion wird nicht sofort, sondern später gemeinsam mit den Funktionen des Updatemoduls getestet.
 
=====<code>update</code>=====
[[Datei:WK Ball03 ClassModel01 update 2018.png|mini|300px|gerahmt|ohne| Funktionen des Moduls <code>update</code>]]
 
In diesem Modul müssen Sie zwei Funktionen implementieren.
 
Zunächst sollten Sie aber das Module <code>collisionCircleStage</code> importieren.
Danach sollten Sie '''vor''' den beiden Funktionen zwei Variablen definieren, in denen die Objekte
gespeichert werden, auf die regelmäßig von der Update-Funktion zugegriffen werden muss:
 
<source lang="javascript">
let v_circle, v_stage;
</source>
 
Nun müssen Sie dafür sorgen, dass die Funktion <code>initUpdater</code> die Werte, die
ihr in den Parametern <code>p_circle</code> und <code>p_stage</code> übergeben werden, in diesen beiden Variablen gespeichert werden.
 
Als letztes implementieren Sie die Funktion <code>update</code>. Diese muss die Position des Kreisobjekts <code>v_circle</code>
mittels der Updatemethode der Klasse <code>ModelCircle</code> aktualisieren. Natürlich muss dabei das Argument, das ihr
im Parameter <code>p_delta_s</code> übergeben wurde, an die Update-Methode der Klasse <code>ModelCircle</code> weitergeleitet werden.
 
Im zweiten Schritt muss sie die zuvor importierte Kollisionsfunktion
für die beiden Objekte <code>v_circle</code> und <code>v_stage</code> ausführen.
 
Wenn Sie die Klasse implementiert haben,
können Sie in der Datei <code>app.js</code> im Anschluss an die Konstantendefinitionen schon mal die Update-Loop initialisieren.  
<source lang="javascript">
initUpdater({stage: c_model_stage, circle: c_model_ball});
</source>


* Ändern Sie nun die Datei <code>game.js</code> geeignet ab. Weisen Sie der Variablen <code>v_stage</code> ein neu erstelltes Bühnenobjekt zu: <code>new Stage()</code>. Da die Bühnengröße innerhalb der Funktion <code>init</code> an die Bildschirmgröß angepasst wird, ist es hier nicht notwendig, dem Konstruktor ein Initialisierungsobjekt zu übergeben. Wenn Sie nun die Anwendung wider starten (<code>grunt</code> nicht vergessen), sollte die Anwendung wieder funktionieren. (Die Klasse <code>Stage</code> wird bereits importiert.)
Damit ändert sich zunächst einmal nichts, da die Update-Loop noch nicht (in der Game Loop) gestartet wird. Aber sie sehen,
ob Sie irgenwelche Syntaxfehler gemacht haben, die entweder WebStorm oder webpack oder die Browser-Konsole meldet.


* Nun ist es an der Zeit, die Korrektheit der Setter-Funktionen auszuprobieren: Ersetzen Sie in den ersten beiden Befehlen der Funktionen <code>init</code> die Befehle <source lang="javascript">v_stage.rgt = p_stage.width; v_stage.btm = p_stage.height;</source> durch <source lang="javascript">v_stage.width = p_stage.width; v_stage.height = p_stage.height;</source>. Die Anwendung sollte immer noch funktionieren! (''Vorsicht, Falle:'' Wenn Sie vergessen <code>grunt</code> aufzurufen, funktioniert die App ebenfalls weiterhin, da die Bundle-Datei <code>app1.bundle.js</code> nicht neu erstellt wird.)
=====<code>ViewCircle</code>=====
[[Datei:WK Ball03 ClassModel01 ViewCircle 2018.png|mini|800px|gerahmt|ohne| Attribute und Methoden der Klasse <code>ViewCircle</code>]]


* Erstellen Sie nun die Klasse <code>Circle</code> analog. Allerdings gelten nun hinsichtlich der Entscheidung „Attribut speichern oder berechnen“ andere Regeln. Ein Kreis ist beweglich. Entsprechend oft ändern sich seine Position und seine Geschwindigkeit. Auch die Beschleunigung kann sich regelmäßig ändern ({{zB}}, wenn der Benutzer den Ball mit Hilfe eines Eingabegeräts wie Tastatur oder Maus steuert). Und selbst der Radius kann sich häufig ändern ({{zB}} weil der Ball aufgrund einer Aktion wächst oder schrumpft.)
Die Aufgabe der Objekte dieser Klasse ist es, Kreise mittels der <code>Graphics</code>-Klasse von PixiJS zu viausalisieren.
Bei jeder Änderung die vier Ränder <code>lft</code>, <code>rgt</code>, <code>top</code> und <code>btm</code>, die es laut Datenmodell geben muss (siehe Grafik) neu zu berechnen und zu speichern, kostet zu viel Zeit. Besser ist es den Wert eines dieser Attribute nur dann zu berechnen, wenn dies bei der Kollissionserkennung und -behandlung benötigt wird. Schreiben Sie den Konstruktor also so, dass der Radius, die Positionsattribute, die Geschwindigkeitsattribute und Beschleunigungsattribute direkt gepeichert werden:
Daher müssen Sie zunächst diese Klasse importieren:


<source lang="javascript">
<source lang="javascript">
this. r = 0;
import {Graphics}  from 'pixi.js';
...
import stringToHex from '/wk_pixi/util/stringToHex';
</source>
</source>


* Die Gettermethoden für die Ränder der [[Bounding Box]] definieren Sie so, dass die jeweiligen Werte mit Hilfe von Mittelplunkt und Radius ermittelt werden:
Wie Sie sehen, wird auch noch eine Utility-Funktion <code>stringToHex</code> importiert,
die Hex-Strings der Art <code>'#FFAA00'</code>
in Integerzahlen umwandelt. Dabei entspricht der Hexwert der resultierende Integerzahl
dem Hex-String. Für das obige Beispiel ergäbe sich der Wert <code>0xFFAA00</code>.
Diese Übersetzung ist notwendig, da PixiJS nur mit Integerzahlen umgehen kann, aber in
[[JSON]]-Dateien, die künftig zur Initialisierung von Modulen eingesetzt werden sollen,
keine Integerzahlen im Hex-Format gespeichert werden können.
 
Der Konstruktor der Klasse <code>ViewCircle</code> muss ein
<code>Graphics</code>-Objekt erzeugen, initialisieren und  
im Attribut <code>this.sprite</code> speichern. Die Erzeugung
und Initialisierung erfolgt analog zur Erzeugung der View in der Funktion <code>init</code>
der Datei <code>game.js</code>. Beachten Sie jedoch: In der Datei <code>game.js</code> werden beim Aufruf der
<code>Graphics</code>-Methoden konstante Werte wie die Zahl <code>5</code>
oder der Wert <code>0xFFAA00</code> verwendet. Hier müssen Sie natürlich auf die Parameter
des Konstruktors zurückgreifen. (Und denken Sie daran, dass Sie Hex-Strings mittels der Funktion <code>stringToHex</code>
in Integerzahlen verwanden müssen.)
 
Das neu erzeugte <code>Graphics</code>-Objekt muss anschließend im Attribut des PixiJS-<code>Application</code>-Objekts
<code>p_pixi_app</code> als Kind-Objekt gespeichert werden:


<source lang="javascript">
<source lang="javascript">
get lft() { return this.x - this.r; }
p_pixi_app.stage.addChild(this.sprite);
...
</source>
</source>


* Und die Setter-Methoden definieren Sie so, dass jeweils die <math>x</math>- bzw. die <math>y</math>-Position so angepasst wird, dass sich der entsprechende Rand an der gewünschten Position befindet:
Zu guter Letzt (oder auch ganz zu Beginn), muss das
dem Konstruktor übergebene Model-Objekt <code>p_model</code> im Attribut
<code>this.model</code> gespeichert werden, damit
die Renderfunktion darauf zugreifen kann. Im Model-Objekt sind die Position, die Größe und evtl. weitere
Informationen gespeichert, die beim Rendern des Objektes berücksichtigt werden müssen.
 
In der Klasse <code>ViewCircle</code> muss noch die Methode <code>render</code> implementiert
werden. Hier kann im Prinzip die Renderfunktion übernommen werden, die sich in der Datei
<code>game.js</code> befindet. Man muss nur beachten, dass die Objekte <code>v_view_ball</code> und <code>v_ball</code>
hier <code>this.sprite</code> und <code>this.model</code> heißen. Jetzt können und sollten Sie
einen '''Aufruf''' der  Funktion <code>render</code> (genauer: <code>this.render</code>) als letzten Befehl in den
Konstruktor einfügen, damit die View von Anfang an auf der Bühne an der korrekten Position angezeigt wird.
 
Testen Sie die Klasse, indem Sie in die Datei <code>app.js</code> folgende Konstante einfügen:


<source lang="javascript">
<source lang="javascript">
set lft(p_x) { this.x = p_x + this.r; }
const
...
  c_view_ball =
    new ViewCircle
        ( c_pixi_app,
          c_model_ball,
          { "border":      5,
            "borderColor": "#AAAAAA",
            "color":      "#FFAA00"
          }
        );
</source>
</source>


* Nun müssen Sie noch die Methode <code>update(p_dt = 1/60)</code> definieren. Kopieren Sie dazu die ersten vier Zeilen aus der Methode <code>modelUpdates</code> und fügen Sie diese in die Methode ein. Beachten Sie, dass das Ball-Objekt jetzt nicht mehr <code>v_ball</code> heißt, sondern <code>this</code>.
Wenn alles klappt, sollten auf im Browser zwei Bälle zu sehen sein, einer, der sich bewegt (<code>game.js</code>) und
einer, der unbewegliche in der linken oberen Ecke des Browsers steht (<code>ModelBall</code>, <code>ViewBall</code>), da die Update- und die Renderfunktionen
noch nicht von der Game Loop aufgerufen werden. Andern Sie in der Konstantendefinition ruhig einmal die Initialisierungswerte <code>border</code>,
<code>borderColor</code> und/oder <code>color</code> und sehen Sie sih das Ergebnis im Brwoser an..
 
=====<code>render</code>=====
[[Datei:WK Ball03 ClassModel01 render 2018.png|mini|150px|gerahmt|ohne| Funktionen des Moduls <code>render</code>''']]
 
Jetzt fehlt nur noch das Rendermodul.
Dieses muss analog zum Updatemodul realisiert werden.
 
Die Initfunktion <code>initUpdater</code> speichert das ihr im Parameter <code>p_views</code> übergebene
Array in einer Variablen <code>v_views</code>. Die <code>render</code>-Funktion durchläuft das Array
<code>v_views</code> und ruft für jedes darin enthaltene Element die Methode <code>render</code>
(siehe Klasse <code>ViewBall</code>) auf.
 
Wenn Sie fertig sind, testen Sie diese Methode:
Initialisieren Sie in der Datei <code>app.js</code> im Anschluss an
die Konstanten-Definition den Renderer. Der Funktion <code>initRender</code>
wird ein Array mit genau einem Element übergeben, das regelmäßig gerendert werden soll: <code>c_view_ball</code>.


* Als nächstes sollten Sie die Datei <code>game.js</code> anpassen, so dass die neu erstellte Kalsse zum Einsatz kommt. Weisen Sie der Variablen <code>v_ball</code> ein neu erstelltes Kreisobjekt zu: <code>new Circle({r:  75, ...})</code>. Als Initialisierungsobjekt verwenden Sie das Objekt, das bislang als Kreisobjekt in der Datei <code>game.js</code> verwendet wird. Außerdem sollten Sie die ersten vier Anweisungen in der Funktion <code>modelUpdates</code> durch einen Methodenaufruf ersetzen:
<source lang="javascript">
<source lang="javascript">
v_ball.update(p_dt);
initRenderer([c_view_ball]);
</source>
</source>


* Das heißt, die Funktion <code>modelUpdates</code> erledigt ihre Aufgabe „Ball bewegen und Kollissionen erkennen und behndeln“ an sofort nur noch mittels zweier Hilfsfuktionen, der Funktion <code>collision</code> und der Methode <code>Circle.update</code>. Testen Sie, ob die Web-Anwendung noch funktioniert.
Wenn sich das Programm fehlerfrei übersetzen lässt, können Sie prüfen, ob alles funktioniert:
Löschen Sie den Importbefehl für die Datei  <code>game.js</code> und ersetzen Sie in der Game Loop
die Funktionen <code>game.update</code> und <code>game.render</code> durch
die importierten Funktionen <code>update</code> und <code>render</code>. Außerdem müssen Sie
in der Initfunktion den Aufruf von <code>game.init(...)</code> löschen.
 
Wenn jetzt alles läuft, dann ist die Modularisierung erfolgreich abgeschlossen
Jetzt könnten sie auch noch die Datei <code>game.js</code> löschen. Aber
vermutlich ist es besser, sie aufzuheben, um die nicht-modularisierte Version
mit der modularisierten zu vergleichen.
 
===Aufgabe 2: Konfiguration der Anwendung mittels JSON===
(Musterlösung: [https://gitlab.multimedia.hs-augsburg.de/kowa/WK_Ball03.git Gitlab: <code>WK_Ball03</code>, <code>app02</code>];
unter <code>app02a</code> finden Sie eine Variante, in der die Funktion <code>/wk/lib/util/concretize</code> verwendet wurde, um die Breite und Höhe des Browser-Fensters nachträglich in das JSON-Objekt einzufügen) 
 
Sie sollten zunächst eine Kopie Ihrer Web-App erstellen:
* Erstellen Sie eine Kopie des Ordners <code>src/js/app01</code> unter dem Namen <code>src/js/app02</code>.
* Erstellen Sie eine Kopie der Datei <code>src/index01.html</code> unter dem Namen <code>src/index02.html</code> und  ändern Sie den Titel in dieser Datei entsprechend.
* Starten Sie gegebenenfalls <code>npm run watch</code> neu (Abbruch des Watchers: <code>Strg-c</code> bzw. <code>Crtl-c</code>).
 
Erstellen Sie nun die [[JSON]]-Datei <code>src/js/app02/config/config.json</code>
und fügen Sie folgendes JSON-Objekt in diese Datei ein:
<source lang="javascript">
{ "model":
  { "stage":
    { "left":  0,
      "right":  500,
      "top":    0,
      "bottom": 500
    },
 
    "ball":
    { "r":  75,
      "x":  75,
      "y":  75,
      "vx": 400,
      "vy": 300
    }
  },


* Zu guter Letzt sollten Sie noch eine Unsauberkeit in der Datei <code>collision.js</code> bereinigen. In der Kollissionsfunktion wird auf das kreisspezifische Attribut <code>r</code> zugegriffen. Doch nicht jedes bewegliche hat einen Radius. Seitdem Sie die Klasse <code>Circle</code> definiert haben, hat jedes Kreisobjekt zusätlich die Attribute <code>lft</code>, <code>rgt</code>, <code>top</code> und <code>btm</code>. Diese Attribute können nicht nur gelesen, sondern auch geändert werden. Schreiben Sie den Code im Rumpf der Funktion <code>colission</code> so um, dass das Attribut <code>r</code> gar nicht mehr verwendet wird.
  "view":
  { "ball":
    { "border":      10,
      "borderColor": "#000000",
      "color":      "#00AAFF"
    }
  }
}
</source>


====Modularisierung der View-Komponenten====
Importieren Sie das in dieser Datei enthaltene Objekt in Ihre Anwendung <code>app02/app.js</code>:


Erstellen Sie eine Klasse <code>ViewCircle</code> mit dem Konstruktor <code>constructor(p_pixi, p_canvas, p_model, p_config  = {})</code>  
<source lang="javascript">
zur Erzeugung eines Viewobjekts für den Ball. In der Datei <code>game.js</code> wird dieses Objekt in der Variablen <code>v_ball_view</code>  
import config from './config/config.json';
gespeichert. Das eigentliche Viewobjekt wird derzeit in der Initialisierungsfunktion <code>init</code> erzeugt und initialisiert.  
</source>
Mit Hilfe Ihrer Klasse sollte dazu nur noch ein Befehl notwendig sein:
 
Über die Variable <code>config</code> sind nun alle alle Informationen zugänglich, die in der JSON-Datei enthalten sind. Beispiel.


<source lang="javascript">
<source lang="javascript">
v_ball_view
console.log(config.view.ball)
   = new ViewCircle(p_pixi, p_canvas, v_ball,
                  {
{ "border":      10,
                    border:      5,
  "borderColor": "#000000",
                    borderColor: 0xAAAAAA,
  "color":      "#00AAFF"
                     color:       0xFFAA00
}
</source>
 
Ersetzen Sie nun in den Konstruktoren der Objekte <code>c_model_stage</code>, <code>c_model_ball</code> und <code>c_view_ball</code> den darin enthaltenen JSON-Code durch den entsprechenden JSON-Code im Objekt <code>config</code>.
 
Testen Sie Ihre Anwendung und committen Sie die Änderungen, wenn alles läuft.
 
Ein (aus Sicherheitsgründen gewolltes) Problem von JSON ist, dass dort keine Funktionen definiert
werden können. Das heißt, die Höhe und Breite der Bühne kann nicht mittels JSON dynamisch an die
Breite des Brwoserfensters angepasst werden. In der Datei wurden die Höhe und die Breite auf jeweils 500 (Pixel) festgelegt. Um die Höhe und Breite an die Fenstergröße anzupassen, müssen
Sie sie das Stage-initialisierungs-Objekt in der Datei <code>app02/app.js</code> ändern, '''nachdem''' Sie die
JSON-Datei importiert haben:
 
<source lang="javascript">
const
   c_config_model_stage = config.model.stage,
  c_document_element  = document.documentElement;
 
c_config_model_stage.right  = c_document_element.clientWidth;
c_config_model_stage.bottom = c_document_element.clientHeight;
</source>
 
Anmerkung: Es ist vorteilhaft, Objekte, die mehrfach verwendet werden,
zunächst in einer Konstanten zu speichern.
Der folgende Code würde auch funktionieren, hätte aber ein paar kleine Nachteile.
 
<source lang="javascript">
config.model.stage.right  = document.documentElement.clientWidth;
config.model.stage.bottom = document.documentElement.clientHeight;
</source>
 
Nachteile:
# Der Code ist nicht [[DRY]].
# Die Ausführungszeit ist evtl. etwas länger, da dieselben Objekte mehrfach aus eine komplexeren Objektstruktur extrahiert werden müssen (außer man hat einen richtig guten JavaScript-Compiler, der diese redundaten Befehlsschritte wegoptimieren kann).
# Der Code  ohne Konstanten kann weniger stark komprimiert werden, da lokale Konstantennamen vom Uglyfier durch kurze Namen ersetzt werden können. Attributnamen können hingegen nicht verkürzt werden.
 
Es gibt auch noch eine zweite Möglichkeit, das geladen JSON-Objekt nachträglich zu verändern.
Importieren Sie die Funktion <code>/wk/lib/util/concretize</code>. Diese Funktione dient dazu, in einem JSON-Objekt Strings, die mit einen @-Symbol beginnen, nachträglich durch andere Werte zu ersetzen.
 
Beispielsweise liefert <code>concretize({"@min": 10, "@max": 20})</code> eine Zufallszahl zwischen 10 und 20,  <code>concretize(["@some", "karo", "herz", ''pik", "kreuz"]})</code> liefert zufällig einen der vier Strings
<code>"karo"</code>, <code>"herz"</code>, <code>''pik"</code>, und <code>"kreuz"</code>, die im Array enthalten sind. Eine Dokumentation des Befehls finden Sie [https://glossar.hs-augsburg.de/beispiel/tutorium/2018/ball/WK_Ball03/doc/jsdoc/module-wk_util_concretize.html im Ordner <code>doc/jsdoc</code> der Musterlösung].
 
Um <code>conrretize</code> verwenden zu können,
müssen Sie zunächst das Stage-Objekt on der JSON-Datei <code>config.json</code> ändern:
 
<source lang="javascript">
"stage":
{ "left":  0,
  "right":  "@right",
  "top":    0,
  "bottom": "@bottom"
}
</source>
 
Die Strings <code>"@right"</code> und <code>"@bottom"</code> haben keinerlei spezielle Bedeutung.
Sie wurden (von mir) frei gewählt. Sie sollen später mittels <code>concretize</code> durch konkrete
Werte (die Bühnenbreite bzw. -höhe) ersetzt werden.
 
Löschen Sie nun in der DAtei <code>app02/app.js</code> wieder die zuvor eingefügten Befehle:
<source lang="javascript">
const
  c_config_model_stage = config.model.stage,
  c_document_element  = document.documentElement;
 
c_config_model_stage.right  = c_document_element.clientWidth;
c_config_model_stage.bottom = c_document_element.clientHeight;
</source>
 
Wenden Sie dafür die Funktion <code>concretize</code> auf das JSON-Objekt an,
dass Sie dem Konstruktor <code>ModelStage</code> übergeben.
<source lang="javascript">
c_model_stage =
  new ModelStage(concretize
                ({config:      config.model.stage,
                  environment:
                  { '@right': document.documentElement.clientWidth,
                     '@bottom': document.documentElement.clientHeight
                   }
                   }
                  );
                })
                ),
</source>
</source>


Ein Viewobjekt muss zwei Attribute anbieten, da die Funktion <code>viewUpdates</code> n der Datei <code>game.js</code>
Sie sehen, dass hier der Funktion <code>concretize</code> nicht nur das zu modifizierende
darauf zugreift: <code>x</code> und </code>y</code>.  
JSON-Objekt übergeben wird, sondern auch ein so genanntes Environment-Objekt (''environment'' = Umgebung, Umwelt, Ausstattung), das festlegt, durch welche Werte die beiden Strings <code>"@right"</code> und <code>"@bottom"</code> ersetzt werden sollen.
Diese Attribute müssen einen Zugriff auf das zugehörige Pixi-Sprite gewähren,
 
welches innerhalb des Viewobjekt gespeichert wird. Dazu sind je eine Getter- und eine Setter-Methode vorzüglich geeignet.
===Aufgabe 3: Ersetzen der <code>Graphics</code>-View durch eine <code>Sprite</code>-View===
(Musterlösung: [https://gitlab.multimedia.hs-augsburg.de/kowa/WK_Ball03.git Gitlab: <code>WK_Ball03</code>, <code>app03</code>];
unter <code>app02a</code> finden Sie eine Variante, in der zwar alle erlaubten Bilder in der <code>app.js</code> aufgeführt sind, aber nur diejenigen geladen werden, die in der Konfigurationsdatei unter <code>init.pixiImages</code> aufgelistet werden)
 
Ersetzen Sie nun die <code>Graphics</code>-View des Balls durch eine <code>Sprite</code>-View.
(Sie können dazu auch eine Version <code>app03</code> erstellen, wenn Sie später die beiden Versionen nochmals vergleichen möchten. Tipp: Im Dateibaum von WebStorm zwei Dateien selektieren, wie {{zB}}
<code>app02/app.js</code> und <code>app02a/app.js</code> und dann <coded>Strg-d</code> bzw.
<code>Ctrl-d</code> oder auch Mausklick rechts → <code>Compare files</code>.)


Wenn Sie in der Klasse <code>ViewCircle</code> auch noch eine Update-Methode implementieren – analog zur Update-Methode
Gehen Sie folgendermaßen vor, um eine <code>Sprite</code>-View zu erstellen und zu verwenden:
in der Klasse <code>MovableCircle</code> , können Sie die Funktion <code>viewUpdates</code> sogar noch vereinfachen.
* Nennen Sie <code>ViewCircle.js</code> in <code>ViewCircleGraphics.js</code> um ( Mausklick rechts → <code>Refactor</code> → <code>Rename</code>, alle Optionen selektieren; innerhalb der Datei muss der Name allerdings noch angepasst werden).
Sie brauchen nur noch die neu definierte Updatemethode geeignet aufzurufen. (Anmerkung: Nachdem Sie dies gemacht haben,
* Definieren Sie eine Klasse <code>ViewCircleSprite</code> analog zu <code>ViewCircleGraphics</code>. Diese muss die PixiJS-Klasse <code>Sprite</code> anstelle von <code>Graphics</code> importieren und geeignet initialisieren (siehe [[MMProg: Praktikum: WiSe 2018/19: Ball02|Ball02, Aufgabe 2a]]). Insbesondere benötigt sie dazu das Ressources-Objekt, das zuvor vom PixiJS-Loader dynamisch geladen wird  (siehe [[MMProg: Praktikum: WiSe 2018/19: Ball02|Ball02, Aufgabe 3a]]). Für das Laden der Bilder ist das Modul </code>app.js</code> zuständig. Dem Konstruktor der Klasse <code>ViewCircleSprite</code> wird es in einem zusätzlichen Parameter <code>p_ressources</code> übergeben. In der Konfigurationsdatei stehen nicht mehr die Attribute für das <code>Graphics</code>-Objekt, sondern der Name des Bildes, das angezeigt werden soll (und in <code>p_ressources</code> enthalten sein muss).  
sind die beiden zuvor definierten Attribute <code>x</code> und </code>y</code> eigentlich überflüssig.)
* Importieren Sie nun die neue Klasse code>ViewCircleSprite</code> anstelle von <code>ViewCircleGraphics</code> in das Modul <code>app.js</code>. Und nun müssen Sie wie im Tutorium [[MMProg: Praktikum: WiSe 2018/19: Ball02|Ball02, Aufgabe 3a]] vorgehen (vgl. [https://glossar.hs-augsburg.de/beispiel/tutorium/2018/ball/WK_Ball02/src/js/app03c.js Musterlösung]):
** Gewünschte Bilder, PixiJS-Loader sowie <code>/wk_pixi/loader/loadResources</code> importieren.
** Importierte Bilder zum PixiJS-Loader hinzufügen.
** Die Ressourcen asynchron '''im Rumpf''' der Init-Funktion laden.
** '''Danach''' ({{dh}} auch im Rumpf der Init-Funktion), das Objekt <code>c_view_ball</code> mittels des neuen Konstruktors <code>ViewCircleSprite</code> erzeugen (erst jetzt gibt es das Ressources-Objekt).
** Danach den Renderer initialisieren (erst jetzt existiert das zugehörige View-Objekt).


==Quellen==
==Quellen==

Aktuelle Version vom 2. Oktober 2019, 08:30 Uhr

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

Korrektheit: 3
(zu größeren Teilen überprüft)
Umfang: 4
(unwichtige Fakten fehlen)
Quellenangaben: 3
(wichtige Quellen vorhanden)
Quellenarten: 5
(ausgezeichnet)
Konformität: 3
(gut)

Vorlesung MMProg

Inhalt | EcmaScript01 | EcmaScript02 | EcmaScript03 | Ball 01| Ball 02 | Ball 03 | Pong 01

Musterlösung: Web-Auftritt (Git-Repository)

Ziel

Ziel dieser Praktikumsaufgabe ist es, Lösungen des zweiten Teils des Tutoriums zu modularisieren. Die Modularisierung hat mehrere Vorteile:

  • Das Prinzip „Don't repeat yourself“ (DRY) wird unterstützt. Um dies zu erreichen, muss sichergestellt werden, dass viele Module in diversen Projekten wiederverwendet werden können.[1][2]
  • Mehrere Programmierer können gleichzeitig an einem Projekt arbeiten. Hier muss sichergestellt sein, dass möglichst saubere Schnittstellen definiert werden, damit ein Programmierer nicht ständig an die Änderungen eines anderen Programmierers anpassen muss. (Die Definition von Schnittstellen ist eine der Kernaufgaben von Informatikern. Dabei handelt es sich um einen kreativen Prozess. Der Programmierer hingegen braucht „lediglich“ die Spezifikation umzusetzen. Das ist i. Allg. deutlich weniger kreativ.)
  • Jede Änderung an einem vorhandenen Code kann Fehler zur Fogle haben. Daher ist es von Vorteil, wenn ausgetestete, wiederverwendbare Module bestehen. Bei der Fehlersuche befindet sich der Code i. Allg. nicht in einem dieser Module, sondern im neu erstellten Code. Das vereinfacht die Fehlersuche deutlich.

Vorbereitung

Importieren Sie das leere Git-Projekt Ball03 in WebStorm. Laden Sie anschließend mittels npm i alle benötigten Node.js-Module in das Projekt.

Sie können Ihr Projekt zur Übung auch in Ihrem Git-Repository speichern. Das ist aber nicht so wichtig. Falls Sie das machen möchten, müssen Sie es zuvor von meinem (schreibgeschützten) Repository lösen:

git remote remove origin
git remote add origin https://gitlab.multimedia.hs-augsburg.de/BENUTZER/Ball03.git

Aufgaben

In Ihrem Projekt finden Sie drei Web-Anwendungen: src/index01.html, src/index02.html und src/index03.html. Allerdings existiert derzeit nur der JavaScript-Ordner src/js/app01 für die Web-App index01.html. Die fehlenden JavaScript-Ordner src/js/app02 und src/js/app03 werden erst zu einem späteren Zeitpunt erstellt.

Im Ordner src/js/app01 befinden sich derzeit zwei Dateien: app.js und game.js. Diese Dateien enthalten eine App, bei der sich ein Ball schräg über die Bühne bewegt. Diese App ist eine Mischung der Musterlösungen von Teil 2 des Ball-Tutoriums: Bei der View des Balls wurde die Graphics-Version aus der ersten Teilaufgabe dieser Tutoriumsaufgabe genommen, die Update-Funktionalität und die Kollisionserkennung entstammt dagegen dem vierten Teil.

Beachten Sie, dass sich die Ordnerstruktur gegenüber Teil 2 des Tutoriums geändert hat. Im Ordner src/js gibt es für jede Web-App einen Ordner mit Namen appXY, in dem eine Datei app.js enthalten sein muss, die die App initialisiert. Es können im selben Ordner beliebig viele weitere Dateien und Unterordner enthalten sein, die von app.js direkt oder indirekt importiert werden. Die Umstrukturierung ist sinnvoll, da ab sofort eine Web-App aus deutlich mehr als drei Dateien bestehen wird.

Zu jedem Web-App-Ordner src/js/appXY muss es eine zugehörige Datei src/indexXY.html geben.

Anmerkung: Die Datei webpack.config.js wurde speziell für diese Struktur entwickelt. Die im Teil 2 enthaltene Datei gleichen Namens ist damit nicht kompatibel.

Aufgabe 0: Analyse von app01

Es gibt in WK_Ball03 eine app01 mit zwei Modulen app und game, die in zwei Dateien app.js und game.js implementiert wurden.

Sehen Sie sich zunächst diese App an und analysieren Sie, wie diese funktioniert. Beachten Sie insbesondere Folgendes:

  • Es wird ein Ball-Objekt erzeugt, mit Attributen „Radius“, „Position“ und „Geschwindigkeit“.
  • Dieser Ball wird durch einen einfarbigen Kreis mit Rand visualisiert.
  • Der Ball bewegt sich gemäß seinen Initialparametern schräg über die Bühne.
  • Bei einer Kollision mit dem Bühnenrand ändert er seine Bewegungsrichtung.

Die gesamte beschriebene Spiellogik ist in einer Datei enthalten: game.js. So eine Datei bezeichne ich als Moloch. Das Prinzip „Implementiere keinen Moloch“ nennt man fachsprachlich „Modularisierung“.

Initiales Moduldiagramm von app01

Eigentlich ist das Programm gar nicht so molochartig, wie es zunächst scheint. Es kommen einige Module zum Einsatz:

  • index01.html: Eine HTML-Seite zum Starten der eigentlichen Web-Anwendung, sobald sie vom Browser geladen wird.
  • head.css: Eine CSS-Datei, die das Layout der HTML-Datei festlegt (insbesondere die Hintergrundfarbe), solange die App geladen wird. Diese Datei wird mittels eines link-Elements direkt von index01.html eingebunden. Mittels webpack wird der Inhalt dieser Datei komprimiert und in die zugehörige HTML-Datei injiziert, indem das link-Element durch ein script-Element ersetzt wird.
  • app.css: Eine CSS-Datei, die das Layout der Web-App festlegt. Diese Datei wird von app.js importiert. Mittels webpack wird dafür gesorgt, dass der JavaScript-Code von app.js eine komprimierte Version vom Inhalt der CSS-Datei in index01.html injiziert wird, sobald die Web-App vollständig geladen wurde.
  • GameLoop: Eine Game-Loop-Klasse, die Ihnen im Rahmen des Praktikums zur Verfügung gestellt wird. Dieses Modul benutzt weitere Module der WK-Bibliothek: Automaton und EventDispatcher.
  • wait: Eine asynchrone Funktion, die in der WK-Biliothek bereitsteht, um eine gewisse Zeitspanne zu warten, bevor eine Aktion durchgeführt wird. Hier wird sie verwendet, um den Start der App ein paar Millisekunden zu verzögern, nachdem die App vollständig geladen und sichbar gemacht wurde.
  • PixiJS: Eine sehr mächtige 2D-Grafik-Bibliothek, die sehr modular aufgebaut ist. Aus dieser Biliothek werden zwei Klassen verwendet (auch diese verwenden – wie die GameLoop – zahlreiche weitere Module, um ihre Aufgaben zu erfüllen; es würde allerdings das Diagramm vollkommen unlesbar machen, alle diese Module hier aufzuführen):
    • PIXI.Application: Die PixiJS-Root-Klasse. Das zugehörige Objekt enthält alle Elemente, um graphische Elemente effizient auf einem HTML-Canvas-Element darzustellen.
    • PIXI.Graphics: Eine PixiJS-Klasse zum Zeichnen einfacher geometrischer Formen wie Kreisen, Rechtecken, Linien etc.
  • app: Das Hauptmodul. Es stellt mit Hilfe der zuvor genannten Module die Spielumgebung zur Verfügung: Eine PixiJS-Bühne, eine Game Loop sowie eine CSS-Datei. Sobald die Umgebung erstellt und initialisiert wurde, startet diese Modul leicht verzögert die Update- und die Render-Funktion des eigentlichen Spielmoduls game.
  • game: Das immer noch molochartige Spielmodul, das im Rahmen des Praktikums in diverse Einzelmodule aufgeteilt werden wird.

Aufgabe 1: Modularisierung ders Game-Moduls

(Musterlösung: Gitlab: WK_Ball03, app01; unter app01a finden Sie eine Variante, in der das Update- und das Render-Modul nicht als Funktionsmodule, sondern als Klassenmodule realisiert wurden)

Ein wichtiges Programmierprinzip besagt, dass jedes Modul nur eine Aufgabe erledigen soll. Bislang ist das Game-Modul in dieser Hinsicht noch ziemlich schlecht, da es zahlreiche Aufgabe erfüllt. Dies sollen Sie ändern.

geplantes Klassendiagramm von app01

Als Ersatz für das Game-Modul sollen insgesamt sechs Komponenten erstellt werden „Bühne (Model)“, „Ball (Model)“, „Ball (View)“, „Kollisionserkennung und -behandlung (Model)“, „Update-Funktion (Model)“ und „Render-Funktion (View)“. Oben sehen Sie das zugehörige Klassendiagramm, bestehend aus insgesamt sieben Modulen:

  • Modul app: Zuständig für die Initialisierung der Spielumgebung und das anschließende Starten des Spiels. Dieses Modul besteht aus einer Folge von Anweisungen, die direkt beim Start der Web-App ausgeführt werden.
  • Klasse ModelStage: Jedes Objekt dieser Klasse repräsentiert das Modell einer Spielbühne. Üblicherweise gibt es nur eine Spielbühne. Es gibt aber auch Spielsituationen mit mehreren Spielbühnen (z. B. wenn neben der Hauptbühne, die nur einen Ausschnitt der Spielwelt zeigt, eine Minimap existiert, die einen Überblick auf die Spielwelt gewährt). Diese Klasse kann in vielen Anwendungen wiederverwendet werden. Im Laufe der Zeit werden allerdings sicher noch weitere Attribute und Methoden ergänzt werden.
  • Klasse ModelCircle: Eine ebenfalls sehr gut wiederverwendbare Klasse, die für kreisförmige Objekte aller Art in einem Spiel zum Einsatz kommen kann. Normalerweise gibt es zahlreiche kreisförmige Objekte in einem Spiel. Auch hier wird es im Laufe der Zeit sicher diverse Erweiterungen geben.
  • Funktion collisionCircleStage: Ein Modul, das eine Funktion zur Kollisionserkennung und -behandlung von (beweglichen) Kreisobjekten mit (unbeweglichen) Rändern der Bühne zur Verfügung stellt. Auch dieses Modul kann sehr gut wiederverwendet werden. Allerdings gibt es (wie bei allen Fragen der Kollisionserkennung und -behandlung) noch zahlreiches Verbesserungspotential.
  • Funktionssammlung update: Dieses Modul enthält zwei Funktionen: initUpdater und update. Die erste Funktion dient dazu, die Update-Funktion zu initialisieren, d. h., ihr die Objekte bekannt zu geben, die sie regelmäßig aktualisieren soll. Die zweite Funktion wird der Game Loop als Callback-Funktion übergeben, um diese Aktualisierungen regelmäßig (z. B. genau sechzig mal pro Sekunde) durchzuführen. Außerdem ist die Update-Funktion dafür zuständig, die Kollisionserkennung und -behandlung zu initiieren. Sie selbst führt diese Aufgabe allerdings nicht durch, sondern überträgt sie an geeignete Hilfsmodule (in diesem Fall das Modul collisionCircleStage). Das Update-Modul kann meist nicht problemlos wiederverwendet werden, da jede Web-App ihre ganz eigene Objektwelt verwaltet.
  • Klasse ViewCircle: Eine sehr gut wiederverwendbare Klasse, die die kreisförmigen Objekte visualisieren soll. In Aufgabe 1 wird der Ball mit Hilfe von PixiJS-Graphics-Befehlen gezeichnet (weshalb die Klasse vielleicht besser ViewCircleGraphics heißen sollte). In einer späteren Aufgabe sollen sie die Visualisierung mit Hilfe von Bildern unter Zuhilfenahme der PixiJS-Klasse Sprite realisieren. (Diese Klasse sollte daher eventuell ViewCircleSprite genannt werden.)
  • Funktionssammlung render: Dieses Modul enthält zwei Funktionen: initRenderer und render. Die erste Funktion dient dazu, die Render-Funktion zu initialisieren, d. h., ihr die Objekte bekannt zu geben, deren Visualisierung sie regelmäßig aktualisieren soll. Die zweite Funktion wird der Game Loop als Callback-Funktion übergeben, um diese Aktualisierungen regelmäßig (möglichst sechzig mal pro Sekunde) durchzuführen. Im Gegensatz zur Update-Funktion ist diese Funktion sehr gut wiederverwendbar. Ihre einzige Aufgabe ist es, für alle View-Objekte, die in einen Array enthalten sind (das ihr bei Initialisierung übergeben wurde), jeweils die Update-Funktion aufzurufen, die jedes View-Objekt bereitstellen muss.

Erstellen der Modul-Struktur

Legen Sie zunächst zwei Ordner an:

  • src/js/app01/model
  • src/js/app01/view

Erzeugen Sie dann innerhalb des Ordners src/js/app01/ für jedes Modul eine leere EcmaScript-Datei:

  • app.js (diese Datei gibt es schon, sie wird im Folgenden allerdings modifiziert.
  • model/ModelStage.js
  • model/ModelCircle.js
  • model/collisionCircleStage.js
  • model/update.js
  • view/ViewCircle.js
  • view/render.js

Die Idee, jedes Modul in einer eigenen Datei zu platzieren, geht auf Java zurück. Im Gegensatz zu anderen Sprachen erzwingt Java, dass jede Klasse in eine eigene Datei geschrieben werden muss (vgl. Why is each public class in a separate file?). Außerdem gibt es in Java nur eine Art von Modulen: Klassen. Beide Restriktionen gelten für JavaScript nicht. Sie sollten sich aber angewöhnen, zumindest die Regel „ein Modul === eine Datei mit demselben Namen plus der Endung .js“ zu beachten.

Fügen Sie in jede Datei einen geeigneten Rahmencode ein. In ein Modul, das eine Klasse enthält (siehe obiges Diagramm), fügen Sie bitte folgenden Code ein, wobei Sie jeweils CLASSNAME durch den Klassenname ersetzen müssen.

class CLASSNAME { 
  constructor() {
  }
}

export default CLASSNAME;

oder auch (je nachdem, welche Klammerstruktur sie bevorzugen; bei der folgenden Struktor müssen Sie den Code allerdings Strg-Shift-vPaste without Formating einfügen, damit sie erhalten bleibt)

class CLASSNAME
{ constructor()
  { 
  }
}

export default CLASSNAME;

Für Funktionsmodule, die mehr als eine Funktion exportieren (hier sind das die Module update und render; siehe oben), sollten Sie folgenden Code einfügen (wobei Sie die Funktionsnamen natürlich geeignet ersetzen müssen):

function FUNKTIONSNAME_1()
{ 
}

function FUNKTIONSNAME_2()
{ 
}

export { FUNKTIONSNAME_1, FUNKTIONSNAME_2 };

Beachten Sie, dass Sie später diese Funktionen (z. B. in der Datei app.js) mittels

import { FUNKTIONSNAME_1, FUNKTIONSNAME_2 } from './ORDNER/MODULNAME.js';

importieren können.

Für Funktionsmodule, die nur eine Funktion exportieren (hier ist das das Modul collisionCircleStage), sollten Sie folgenden Code einfügen (wobei Sie den Funktionsnamen natürlich ebenfalls geeignet ersetzen müssen):

function FUNKTIONSNAME()
{ 
}

export default FUNKTIONSNAME;

Da hier die Funktion als Default-Objekt exportiert wird, können Sie diese Funktion später mittels

import FUNKTIONSNAME from './ORDNER/MODULNAME.js';

importieren. Hier können bzw. müssen Sie beim Importieren also auf die geschweiften Klammern verzichten. Sollten Sie die geschweiften Klammern unbedingt verwenden wollen, müssten Sie die Exportanweisung anlog zum Modul mit mehreren Funktionsobjekten definieren.

Starten Sie nun npm run watch und überprüfen Sie , ob die Web-App app01 ausführbar ist. Das sollte auf jeden Fall funktionieren, da Sie ja an der App (d. h. an den Dateien app.js und game.js bislang gar nichts geändert haben.

Importieren Sie nun die fünf Module, auf die das Modul app im obigen Diagramm verweist, in die Datei app.js. Beachten Sie, dass die Syntax der Importanweisungen von den Exportanweisungen abhängen, die im jeweiligen Modul verwendet wurde (siehe Anmerkungen zum Funktionscode zuvor).

Wenn Sie keine syntaktischen Fehler gemacht haben, sollte webpack die App immer noch fehlerfrei übersetzen und ausführen können. WebStorm warnt Sie allerdings bei den Import-Anweisungen, dass Sie Objekte improtieren, die Sie gar nicht verwenden. Das stimmt derzeit ja auch, also ist alles in Ordnung.

Implementierung der Komponenten

ModelStage
Attribute und Methoden der Klasse ModelStage

Implementieren Sie zunächst die Klasse StageModel. Im UML-Klassendiagramm sehen Sie, dass jedes Objekt dieser Klasse vier Attribute hat: left, right, top, bottom. Es sollen jeweils Zahlen darin gespeichert werden. Allerdings ist JavaScript untypisiert, so dass Sie diese Bedingungen höchstens in JSDoc-Kommentaren formulieren können. Erschwerend kommt hinzu, das man in EcmaScript-Klassen derzeit Attribute nur mit Hilfe von Getter- und Setter-Methoden definieren kann. Direkt kann man sie zurzeit nur innerhalb des Konstruktors erzeugen.

Also implementieren Sie nur den Konstruktor:

constructor({left:   p_left   = 0,
             right:  p_right  = 0,
             top:    p_top    = 0,
             bottom: p_bottom = 0
           })
{ this.left   = p_left;
  this.right  = p_right;
  this.top    = p_top;
  this.bottom = p_bottom;
}

Hier kommt eine Parametersyntax zum Einsatz, die in EcmaScript 6 unter dem Namen Destructuring eingeführt wurde.

In EcmaScript 5 hätten Sie das noch folgendermaßen schreiben müssen:

constructor(p_config)
{ this.left   = p_config.left    == null ? 0 : p_config.left;
  this.right  = p_config.right   == null ? 0 : p_config.right;
  this.top    = p_config.top     == null ? 0 : p_config.top;
  this.bottom = p_config.bottom  == null ? 0 : p_config.bottom;
}

In beiden Fällen erwartet der Konstruktor ein Objekt als Argument, das die Initialwerte für die vier Attribute enthält. Sollte ein Wert nicht angegeben werden, so wird er mit dem Defaultwert 0 initialisiert.

Probieren Sie aus, ob ihr Code funktioniert. Fügen Sie in die Datei app.js folgende Befehle ein und sehen Sie nach, was im Konsolfenster des Browsers ausgegeben wird, wenn Sie die Web-App starten.

const
  c_model_stage =
    new ModelStage
        ({ "left":   0,
           "right":  document.documentElement.clientWidth,
           "top":    0,
           "bottom": document.documentElement.clientHeight
        });

console.log(c_model_stage);

Den console.log-Befehl sollten Sie anschließend wieder löschen, der wird nicht mehr benötigt. Die Konstante sollte dagegen bestehen bleiben.

Übrigens, mittels geeigneten JSDoc-Kommentare, können Sie zumindest dokumentiren, dass in den Attributen eines Stageobjektes nur Zahlen gespeichert werden dürfen:

Folgender Kommentar sollte vor der Definition der Klasse stehen.

/**
 * @class ModelView
 *
 * @property {number} left   - x position of the left border
 * @property {number} right  - x position of the right border
 * @property {number} top    - y position of the top border
 * @property {number} bottom - y position of the bottom border
 */

Und folgender Kommentar sollte vor der Definition des Konstruktors stehen:

/**
 * @param {Object} p_config
 * @param {number} [p_config.left = 0]
 * @param {number} [p_config.right = 0]
 * @param {number} [p_config.top = 0]
 * @param {number} [p_config.bottom = 0]
 */
ModelCircle
Attribute und Methoden der Klasse ModelCircle

Implementieren Sie die Klasse ModelCircle analog zur Klasse ModelStage.

Im UML-Diagramm dieser Klasse wird allerdings noch die Methode update mit dem Inputparameter p_delta_s aufgeführt. Implementieren Sie diese Methode ebenfalls. Im Rumpf der Methode müssen Sie die ersten beiden Zeilen der Funktion update einfügen, die Sie in der Datei game.js vorfinden und den Bezeichner v_ball geeignet umbenennen.

Testen Sie diese Klasse, indem Sie folgende Konstante in die Datei app.js einfügen:

const
  c_model_ball =
    new ModelCircle
    ({ "r":   75,
       "x":   75,
       "y":   75,
       "vx": 400,
       "vy": 300
    });

Fügen Sie anschließend folgende Befehle in app.js ein und überprüfen Sie im Konsolfenster des Browsers, ob alles funktioniert.

console.log(c_model_ball);
c_model_ball.update(1/60);
console.log(c_model_ball);

Diese drei Testbefehle sollten Sie anschließend wieder löschen.

collisionCircleStage
Funktion collisionCircleStage

In die Funktion collisionCircleStage in der Datei collisionCircleStage.js fügen Sie zunächst die beiden Parameter ein, die Sie im obigen Diagramm vorfinden. In den Rumpf fügen Sie die restlichen Befehle aus der Funktion update der Datei game.js ein. Das sind die Befehle zur Kollisionserkennung und -behandlung. Beachten Sie, dass die Objekte nicht mehr v_ball und v_stage heißen, sondern p_circle und p_stage.

Diese Funktion wird nicht sofort, sondern später gemeinsam mit den Funktionen des Updatemoduls getestet.

update
Funktionen des Moduls update

In diesem Modul müssen Sie zwei Funktionen implementieren.

Zunächst sollten Sie aber das Module collisionCircleStage importieren. Danach sollten Sie vor den beiden Funktionen zwei Variablen definieren, in denen die Objekte gespeichert werden, auf die regelmäßig von der Update-Funktion zugegriffen werden muss:

let v_circle, v_stage;

Nun müssen Sie dafür sorgen, dass die Funktion initUpdater die Werte, die ihr in den Parametern p_circle und p_stage übergeben werden, in diesen beiden Variablen gespeichert werden.

Als letztes implementieren Sie die Funktion update. Diese muss die Position des Kreisobjekts v_circle mittels der Updatemethode der Klasse ModelCircle aktualisieren. Natürlich muss dabei das Argument, das ihr im Parameter p_delta_s übergeben wurde, an die Update-Methode der Klasse ModelCircle weitergeleitet werden.

Im zweiten Schritt muss sie die zuvor importierte Kollisionsfunktion für die beiden Objekte v_circle und v_stage ausführen.

Wenn Sie die Klasse implementiert haben, können Sie in der Datei app.js im Anschluss an die Konstantendefinitionen schon mal die Update-Loop initialisieren.

initUpdater({stage: c_model_stage, circle: c_model_ball});

Damit ändert sich zunächst einmal nichts, da die Update-Loop noch nicht (in der Game Loop) gestartet wird. Aber sie sehen, ob Sie irgenwelche Syntaxfehler gemacht haben, die entweder WebStorm oder webpack oder die Browser-Konsole meldet.

ViewCircle
Attribute und Methoden der Klasse ViewCircle

Die Aufgabe der Objekte dieser Klasse ist es, Kreise mittels der Graphics-Klasse von PixiJS zu viausalisieren. Daher müssen Sie zunächst diese Klasse importieren:

import {Graphics}  from 'pixi.js';
import stringToHex from '/wk_pixi/util/stringToHex';

Wie Sie sehen, wird auch noch eine Utility-Funktion stringToHex importiert, die Hex-Strings der Art '#FFAA00' in Integerzahlen umwandelt. Dabei entspricht der Hexwert der resultierende Integerzahl dem Hex-String. Für das obige Beispiel ergäbe sich der Wert 0xFFAA00. Diese Übersetzung ist notwendig, da PixiJS nur mit Integerzahlen umgehen kann, aber in JSON-Dateien, die künftig zur Initialisierung von Modulen eingesetzt werden sollen, keine Integerzahlen im Hex-Format gespeichert werden können.

Der Konstruktor der Klasse ViewCircle muss ein Graphics-Objekt erzeugen, initialisieren und im Attribut this.sprite speichern. Die Erzeugung und Initialisierung erfolgt analog zur Erzeugung der View in der Funktion init der Datei game.js. Beachten Sie jedoch: In der Datei game.js werden beim Aufruf der Graphics-Methoden konstante Werte wie die Zahl 5 oder der Wert 0xFFAA00 verwendet. Hier müssen Sie natürlich auf die Parameter des Konstruktors zurückgreifen. (Und denken Sie daran, dass Sie Hex-Strings mittels der Funktion stringToHex in Integerzahlen verwanden müssen.)

Das neu erzeugte Graphics-Objekt muss anschließend im Attribut des PixiJS-Application-Objekts p_pixi_app als Kind-Objekt gespeichert werden:

p_pixi_app.stage.addChild(this.sprite);

Zu guter Letzt (oder auch ganz zu Beginn), muss das dem Konstruktor übergebene Model-Objekt p_model im Attribut this.model gespeichert werden, damit die Renderfunktion darauf zugreifen kann. Im Model-Objekt sind die Position, die Größe und evtl. weitere Informationen gespeichert, die beim Rendern des Objektes berücksichtigt werden müssen.

In der Klasse ViewCircle muss noch die Methode render implementiert werden. Hier kann im Prinzip die Renderfunktion übernommen werden, die sich in der Datei game.js befindet. Man muss nur beachten, dass die Objekte v_view_ball und v_ball hier this.sprite und this.model heißen. Jetzt können und sollten Sie einen Aufruf der Funktion render (genauer: this.render) als letzten Befehl in den Konstruktor einfügen, damit die View von Anfang an auf der Bühne an der korrekten Position angezeigt wird.

Testen Sie die Klasse, indem Sie in die Datei app.js folgende Konstante einfügen:

const
  c_view_ball =
    new ViewCircle
        ( c_pixi_app,
          c_model_ball,
          { "border":      5,
            "borderColor": "#AAAAAA",
            "color":       "#FFAA00"
          }
        );

Wenn alles klappt, sollten auf im Browser zwei Bälle zu sehen sein, einer, der sich bewegt (game.js) und einer, der unbewegliche in der linken oberen Ecke des Browsers steht (ModelBall, ViewBall), da die Update- und die Renderfunktionen noch nicht von der Game Loop aufgerufen werden. Andern Sie in der Konstantendefinition ruhig einmal die Initialisierungswerte border, borderColor und/oder color und sehen Sie sih das Ergebnis im Brwoser an..

render
Funktionen des Moduls render

Jetzt fehlt nur noch das Rendermodul. Dieses muss analog zum Updatemodul realisiert werden.

Die Initfunktion initUpdater speichert das ihr im Parameter p_views übergebene Array in einer Variablen v_views. Die render-Funktion durchläuft das Array v_views und ruft für jedes darin enthaltene Element die Methode render (siehe Klasse ViewBall) auf.

Wenn Sie fertig sind, testen Sie diese Methode: Initialisieren Sie in der Datei app.js im Anschluss an die Konstanten-Definition den Renderer. Der Funktion initRender wird ein Array mit genau einem Element übergeben, das regelmäßig gerendert werden soll: c_view_ball.

initRenderer([c_view_ball]);

Wenn sich das Programm fehlerfrei übersetzen lässt, können Sie prüfen, ob alles funktioniert: Löschen Sie den Importbefehl für die Datei game.js und ersetzen Sie in der Game Loop die Funktionen game.update und game.render durch die importierten Funktionen update und render. Außerdem müssen Sie in der Initfunktion den Aufruf von game.init(...) löschen.

Wenn jetzt alles läuft, dann ist die Modularisierung erfolgreich abgeschlossen Jetzt könnten sie auch noch die Datei game.js löschen. Aber vermutlich ist es besser, sie aufzuheben, um die nicht-modularisierte Version mit der modularisierten zu vergleichen.

Aufgabe 2: Konfiguration der Anwendung mittels JSON

(Musterlösung: Gitlab: WK_Ball03, app02; unter app02a finden Sie eine Variante, in der die Funktion /wk/lib/util/concretize verwendet wurde, um die Breite und Höhe des Browser-Fensters nachträglich in das JSON-Objekt einzufügen)

Sie sollten zunächst eine Kopie Ihrer Web-App erstellen:

  • Erstellen Sie eine Kopie des Ordners src/js/app01 unter dem Namen src/js/app02.
  • Erstellen Sie eine Kopie der Datei src/index01.html unter dem Namen src/index02.html und ändern Sie den Titel in dieser Datei entsprechend.
  • Starten Sie gegebenenfalls npm run watch neu (Abbruch des Watchers: Strg-c bzw. Crtl-c).

Erstellen Sie nun die JSON-Datei src/js/app02/config/config.json und fügen Sie folgendes JSON-Objekt in diese Datei ein:

{ "model":
  { "stage":
    { "left":   0,
      "right":  500,
      "top":    0,
      "bottom": 500
    },

    "ball":
    { "r":   75,
      "x":   75,
      "y":   75,
      "vx": 400,
      "vy": 300
    }
  },

  "view":
  { "ball":
    { "border":      10,
      "borderColor": "#000000",
      "color":       "#00AAFF"
    }
  }
}

Importieren Sie das in dieser Datei enthaltene Objekt in Ihre Anwendung app02/app.js:

import config from './config/config.json';

Über die Variable config sind nun alle alle Informationen zugänglich, die in der JSON-Datei enthalten sind. Beispiel.

console.log(config.view.ball) 

{ "border":      10,
  "borderColor": "#000000",
  "color":       "#00AAFF"
}

Ersetzen Sie nun in den Konstruktoren der Objekte c_model_stage, c_model_ball und c_view_ball den darin enthaltenen JSON-Code durch den entsprechenden JSON-Code im Objekt config.

Testen Sie Ihre Anwendung und committen Sie die Änderungen, wenn alles läuft.

Ein (aus Sicherheitsgründen gewolltes) Problem von JSON ist, dass dort keine Funktionen definiert werden können. Das heißt, die Höhe und Breite der Bühne kann nicht mittels JSON dynamisch an die Breite des Brwoserfensters angepasst werden. In der Datei wurden die Höhe und die Breite auf jeweils 500 (Pixel) festgelegt. Um die Höhe und Breite an die Fenstergröße anzupassen, müssen Sie sie das Stage-initialisierungs-Objekt in der Datei app02/app.js ändern, nachdem Sie die JSON-Datei importiert haben:

const
  c_config_model_stage = config.model.stage,
  c_document_element   = document.documentElement;

c_config_model_stage.right  = c_document_element.clientWidth;
c_config_model_stage.bottom = c_document_element.clientHeight;

Anmerkung: Es ist vorteilhaft, Objekte, die mehrfach verwendet werden, zunächst in einer Konstanten zu speichern. Der folgende Code würde auch funktionieren, hätte aber ein paar kleine Nachteile.

config.model.stage.right  = document.documentElement.clientWidth;
config.model.stage.bottom = document.documentElement.clientHeight;

Nachteile:

  1. Der Code ist nicht DRY.
  2. Die Ausführungszeit ist evtl. etwas länger, da dieselben Objekte mehrfach aus eine komplexeren Objektstruktur extrahiert werden müssen (außer man hat einen richtig guten JavaScript-Compiler, der diese redundaten Befehlsschritte wegoptimieren kann).
  3. Der Code ohne Konstanten kann weniger stark komprimiert werden, da lokale Konstantennamen vom Uglyfier durch kurze Namen ersetzt werden können. Attributnamen können hingegen nicht verkürzt werden.

Es gibt auch noch eine zweite Möglichkeit, das geladen JSON-Objekt nachträglich zu verändern. Importieren Sie die Funktion /wk/lib/util/concretize. Diese Funktione dient dazu, in einem JSON-Objekt Strings, die mit einen @-Symbol beginnen, nachträglich durch andere Werte zu ersetzen.

Beispielsweise liefert concretize({"@min": 10, "@max": 20}) eine Zufallszahl zwischen 10 und 20, concretize(["@some", "karo", "herz", pik", "kreuz"]}) liefert zufällig einen der vier Strings "karo", "herz", pik", und "kreuz", die im Array enthalten sind. Eine Dokumentation des Befehls finden Sie im Ordner doc/jsdoc der Musterlösung.

Um conrretize verwenden zu können, müssen Sie zunächst das Stage-Objekt on der JSON-Datei config.json ändern:

"stage":
{ "left":   0,
  "right":  "@right",
  "top":    0,
  "bottom": "@bottom"
}

Die Strings "@right" und "@bottom" haben keinerlei spezielle Bedeutung. Sie wurden (von mir) frei gewählt. Sie sollen später mittels concretize durch konkrete Werte (die Bühnenbreite bzw. -höhe) ersetzt werden.

Löschen Sie nun in der DAtei app02/app.js wieder die zuvor eingefügten Befehle:

const
  c_config_model_stage = config.model.stage,
  c_document_element   = document.documentElement;

c_config_model_stage.right  = c_document_element.clientWidth;
c_config_model_stage.bottom = c_document_element.clientHeight;

Wenden Sie dafür die Funktion concretize auf das JSON-Objekt an, dass Sie dem Konstruktor ModelStage übergeben.

c_model_stage =
  new ModelStage(concretize
                 ({config:      config.model.stage,
                   environment:
                   { '@right':  document.documentElement.clientWidth,
                     '@bottom': document.documentElement.clientHeight
                   }
                 })
                ),

Sie sehen, dass hier der Funktion concretize nicht nur das zu modifizierende JSON-Objekt übergeben wird, sondern auch ein so genanntes Environment-Objekt (environment = Umgebung, Umwelt, Ausstattung), das festlegt, durch welche Werte die beiden Strings "@right" und "@bottom" ersetzt werden sollen.

Aufgabe 3: Ersetzen der Graphics-View durch eine Sprite-View

(Musterlösung: Gitlab: WK_Ball03, app03; unter app02a finden Sie eine Variante, in der zwar alle erlaubten Bilder in der app.js aufgeführt sind, aber nur diejenigen geladen werden, die in der Konfigurationsdatei unter init.pixiImages aufgelistet werden)

Ersetzen Sie nun die Graphics-View des Balls durch eine Sprite-View. (Sie können dazu auch eine Version app03 erstellen, wenn Sie später die beiden Versionen nochmals vergleichen möchten. Tipp: Im Dateibaum von WebStorm zwei Dateien selektieren, wie z. B. app02/app.js und app02a/app.js und dann <coded>Strg-d bzw. Ctrl-d oder auch Mausklick rechts → Compare files.)

Gehen Sie folgendermaßen vor, um eine Sprite-View zu erstellen und zu verwenden:

  • Nennen Sie ViewCircle.js in ViewCircleGraphics.js um ( Mausklick rechts → RefactorRename, alle Optionen selektieren; innerhalb der Datei muss der Name allerdings noch angepasst werden).
  • Definieren Sie eine Klasse ViewCircleSprite analog zu ViewCircleGraphics. Diese muss die PixiJS-Klasse Sprite anstelle von Graphics importieren und geeignet initialisieren (siehe Ball02, Aufgabe 2a). Insbesondere benötigt sie dazu das Ressources-Objekt, das zuvor vom PixiJS-Loader dynamisch geladen wird (siehe Ball02, Aufgabe 3a). Für das Laden der Bilder ist das Modul app.js zuständig. Dem Konstruktor der Klasse ViewCircleSprite wird es in einem zusätzlichen Parameter p_ressources übergeben. In der Konfigurationsdatei stehen nicht mehr die Attribute für das Graphics-Objekt, sondern der Name des Bildes, das angezeigt werden soll (und in p_ressources enthalten sein muss).
  • Importieren Sie nun die neue Klasse code>ViewCircleSprite anstelle von ViewCircleGraphics in das Modul app.js. Und nun müssen Sie wie im Tutorium Ball02, Aufgabe 3a vorgehen (vgl. Musterlösung):
    • Gewünschte Bilder, PixiJS-Loader sowie /wk_pixi/loader/loadResources importieren.
    • Importierte Bilder zum PixiJS-Loader hinzufügen.
    • Die Ressourcen asynchron im Rumpf der Init-Funktion laden.
    • Danach (d. h. auch im Rumpf der Init-Funktion), das Objekt c_view_ball mittels des neuen Konstruktors ViewCircleSprite erzeugen (erst jetzt gibt es das Ressources-Objekt).
    • Danach den Renderer initialisieren (erst jetzt existiert das zugehörige View-Objekt).

Quellen

  1. Kowarschick (MMProg): Wolfgang Kowarschick; Vorlesung „Multimedia-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 3 (Vorlesung)