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

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Keine Bearbeitungszusammenfassung
(31 dazwischenliegende Versionen von einem anderen Benutzer 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_Ball02/web/ Web-Auftritt] ([https://gitlab.multimedia.hs-augsburg.de:8888/kowa/WK_Ball02.git Git-Repository], '''noch nicht online''')  
'''Musterlösung''': [https://glossar.hs-augsburg.de/beispiel/tutorium/2018/ball/WK_Ball02/web/ Web-Auftritt] ([https://gitlab.multimedia.hs-augsburg.de:8888/kowa/WK_Ball02.git Git-Repository])  
==Vorbereitung==
==Vorbereitung==
Importieren Sie das leere Git-Projekt [https://gitlab.multimedia.hs-augsburg.de:8888/kowa/Ball02.git Ball02] in WebStorm.
Importieren Sie das leere Git-Projekt [https://gitlab.multimedia.hs-augsburg.de:8888/kowa/Ball02.git Ball02] in WebStorm.
Zeile 7: Zeile 7:


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


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


==Ziel==
==Ziel==
Zeile 52: Zeile 51:
'''<code>app01.js</code>'''
'''<code>app01.js</code>'''


* Es wird zusätzlich die in der JavaScript-Bibliothek <code>pixi.js</code> enthaltene  Klasse <code>Application</code> importiert.
* Es wird zusätzlich die in der JavaScript-Bibliothek <code>pixi.js</code> enthaltene  Klasse <code>Application</code> importiert. Beachten Sie, dass hier diejenige Klasse der PixiJS-Bibliothek importiert wird, die auch wirklich benötigt wird. Man könnte auch <code>import * as PIXI from 'pixi.js';</code> schreiben und damit alle PixiJS-Klassen, -Funktionen und -Objekte in der Variablen <code>PIXI</code> speichern. Es ist allerdings besser, nur die benötigten Teil der PixiJS-Bibliothek einzubinden, da webpack dann die Chance hat, im Web-Ordner kleinere JavaScript-Dateien zu erzeugen. Dies kann eine deutliche Reduktion der Ladezeiten der Web-App zur Folge haben.  
* Anstelle des GameLoop-Objektes könnte auch der PixiJS-Ticker verwendet werden. Dieser stellt jedoch nicht sicher, dass das Model garantiert 60 mal pro Sekunde aktualisiert wird. Wenn Sie irgendwann PixiJS-Animationen einsetzen möchten, müssen Sie den PixiJS-Ticker allerdings zusätzlich starten, da ansonsten die Animationen nicht animiert werden.  
* Anstelle des GameLoop-Objektes könnte auch der PixiJS-Ticker verwendet werden. Dieser stellt jedoch nicht sicher, dass das Model garantiert 60 mal pro Sekunde aktualisiert wird. Wenn Sie irgendwann PixiJS-Animationen einsetzen möchten, müssen Sie den PixiJS-Ticker allerdings zusätzlich starten, da ansonsten die Animationen nicht animiert werden.  
* Das Root-Objekt der PixiJS-Anwendung wird erstellt. Die wichtigsten Argumente sind die Breite und Höhe der Bühne sowie das Canvas-Element, auf dem die Grafiken gezeichnet werden. (Wenn man der Anwendung kein Canvas-Element zuteilt, erstellt sie selbst eines, das man anschließend per JavaScript in das HTML-Dokument einfügen muss.) Darüber hinaus gibt es diverse weitere Optionen, wie {{zB}} die Festlegung, ob die Leinwand transparent ist, damit der HTML-Hintergrund durchscheint.<ref>[http://pixijs.download/dev/docs/PIXI.Application.html PixiJS API Documentation: PIXI.Application ]</ref>
* Das Root-Objekt der PixiJS-Anwendung wird erstellt. Die wichtigsten Argumente sind die Breite und Höhe der Bühne sowie das Canvas-Element, auf dem die Grafiken gezeichnet werden. (Wenn man der Anwendung kein Canvas-Element zuteilt, erstellt sie selbst eines, das man anschließend per JavaScript in das HTML-Dokument einfügen muss.) Darüber hinaus gibt es diverse weitere Optionen, wie {{zB}} die Festlegung, ob die Leinwand transparent ist, damit der HTML-Hintergrund durchscheint.<ref>[http://pixijs.download/dev/docs/PIXI.Application.html PixiJS API Documentation: PIXI.Application ]</ref>
Zeile 58: Zeile 57:


'''<code>game01.js</code>'''
'''<code>game01.js</code>'''
* Der Ordner <code>ball</code> wurde in <code>game</code> umbenannt und die Datei <code>ball01.js</code> in <code>game01.js</code> unbenannt, da hier nicht nur das Ball-Objekt, sondern auch alle andere Aspekte des „Spiels“  „Ein Ball bewegt sich hin und her“ definiert werden.
* Es wird die in der PixiJS-Bibliothek enthaltene  Klasse <code>Graphics</code> importiert, um damit eine View für das Ball-Objekt zu zeugen.
* Es gibt eine Initialisierungsfunktion (Initfunktion), die zu Beginn der Anwendung aufgerufen wird, um sie zu initialisieren.
* Darüber hinaus gibt es auch noch eine Resetfunktion, die jedes Mal aufgerufen wird, wenn die Anwendung neu gestartet wird, {{zB}} weil ein neues Spiellevel begonnen werden soll. (Diese Funktion gab es im Gegensatz zur Initfunktion auch schon in der ersten Praktikumsaufgabe.) Diese Funktion ist für wiederkehrende Initialisierungaufgaben verantwortlich, wohingegen die Initialisierungsfunktion nur ein einziges Mal zu Spielbeginn aufgerufen wird. Um [[DRY|Code-Duplikationen]] zu vermeiden, ruft die Initialisierungsfunktion auch die Resetfunktion auf.
* Das Modell der Bühne (<code>v_stage</code>) wird nur teilweise initialisiert. Die Breite und Höhe wird erst später von der Initfunktion festgelegt.
* Das Ballobjekt <code>v_ball</code> wird erst durch die Resetfunktion initialisiert. Diese wird erstmals von der Init-Funktion aufgerufen und kann später von der Game Loop bei Spielstart oder -neustart damit zurückgesetzt werden.
* Das Ballobjekt beinhaltet schon die y-Koordinate und die y-Geschwindigkeit des Balls. In der ursprünglichen Aufgabe fehlten diese Attribute noch. Sie wurden erst in der zweiten Aufgabe („Ball vertikal fliegen lassen“) eingeführt. Außerdem wurde der Durchmesser (<code>d</code>) durch den Radius (<code>r</code>) ersetzt.
* Der Ankerpunkt des Balls wurde von der linken oberen Ecke der Eule ins Zentrum des Balls verschoben. Dies hat Auswirkungen auf die Implementierung der Kollisionserkennung und -behandlung.
* Die Renderfunktion unterscheidet sich leicht. In der ursprünglichen Aufgabe wurden CSS-Attribute des animierten <code>div</code>-Elements verändert. Nun müssen die Attribute des PixiJS-Grafikobjekts, das in der Init-Funktion erzeugt wurde, an die aktuellen Modellwerte angepasst werden.


* Das Modell der Bühne (<code>v_stage</code>) wird nur teilweise initialisiert. Die Breite und Höhe wird erst später von der Methode <code>init</code> festgelegt.
Die Initfunktion nimmt folgende Aufgaben wahr:
* Die Variable <code>v_owl</code> wurde in <code>v_ball</code> umbenannt. Der neue Bezeichner betont den Modellaspekt des Objektes mehr (ein Ball hat einen Mittelpunkt und einen Radius), wohingegen der ursprüngliche Name den View-Aspekt („es wird eine Eule visualisiert“) deutlicher betont.
* Breite und Höhe des Bühnenmodells werden an die Breite und Höhe der PixiJS-Anwendung angepasst.
* Das Objekt <code>v_ball</code> beinhalte schon die y-Koordinate und die y-Geschwindigkeit des Balls. In der ursprünglichen Aufgabe fehlten diese Attribute noch. Sie wurden erst in der zweiten Aufgabe („Eule vertikal fliegen lassen“) eingeführt. Außerdem wurde die Objektbreite (<code>width</code>) durch den Radius (<code>r</code>) ersetzt.
* Die Modelle des Spiels werden initialisiert.
* Der Ankerpunkt des Balls wurde von der linken oberen Ecke der Eule ins Zentrum des Balls verschoben. Dies hat Auswirkungen auf die Implementierung der Kollisionserkennung und -behandlung. Beachten Sie bitte, dass in der Update-Methode der Parameter <code>p_frac_s</code> durch die üblichere Bezeichnung <code>dt</code> (für ''delta time'') ersetzt wurde.
* Die View des Balls wird initialisiert und auf der PixiJS-Bühne platziert. Dies kann erst jetzt erfolgen, da das Application-Objekt der PixiJS-Anwendung erst jetzt zur Verfügung steht. Mit Hilfe der PixJS-Klasse [http://pixijs.download/dev/docs/PIXI.Graphics.<code>Graphics</code>]wird zunächst ein Grafikobjekt erzeugt. Dieses wird mit Inhalt gefüllt (einem Kreis von bestimmter Farbe mit einem Rand in einer anderen Farbe) und zu guter Letzt zur PixiJS-Bühne ({{dh}} im Prinzip zum zugehörigen Canvaselement) hinzugefügt. Wenn sich später bestimmte Attribute des Grafikobjektes – wie {{zB}} die Position oder die Größe – ändern, wird die graphische Darstellung des Objektes auf dem Canvas automatisch von PixiJS angepasst. Die Änderung der Grafikattribute werden in der Funktion <code>render</code> vorgenommen. Dort wird derzeit allerdings nur die Position aus dem Model in die View übertragen. Andere Attribute der View ändern sich in dieser Anwendung nicht.
* Die Renderfunktion unterscheidet sich leicht. In der ursprünglichen Aufgabe wurden CSS-Attribute des animierten DIV-Elements verändert. Nun müssen die Attribute des PixiJS-Grafikobjekts an die aktuellen Modellwerte angepasst werden.
* Es gibt eine Initialisierungsfunktion, die zu Beginn der Anwendung aufgerufen wird, um sie zu initialisieren. (Meist gibt es auch noch eine Resetfunktion, die jedesmal aufgerufen wird, wenn die Anwendung neu gestartet wird, {{zB}} weil ein neues Spiellevel begonnen werden soll. Diese Funktion ist dann für wiederkehrende Initialisierungaufgaben verantwortlich, wohingegen die Initialisierungsfunktion nur ein einziges Mal zu Spielbeginn aufgerufen wird. Um Code-Duplikationen zu vermeiden, ruft die Initialisierungsfunktion {{iAllg}} auch die Resetfunktion auf. In dieser Praktikumsaufgabe kommt allerdings keine Resetfunktion zum Einsatz.)
* Die initialisierungsfunktion nimmt folgende Aufgaben wahr:
** Breite und Höhe des Bühnenmodells werden an die Breite und Höhe der PixiJS-Anwendung angepasst.
** Die Modelle des Spiels könnten erzeugt und/oder initalisiert werden. Das ist in der ersten Aufgabe noch nicht notwendig, da das Ballobjekt bereits zuvor explizit im Code definiert. Falls aber die Position eines Balls von den Ausmaßen der Bühne abhängen oder zufällig berechnet werden sollte, müsste die Initialisierungsfunktion auch in dieser Hinsicht aktiv werden.
** Die Views des Spiels müssen initialisiert werden. Dies kann erst jetzt erfolgen, da das Root-Objekt der PixiJS-Anwendung erst jetzt zur Verfügung steht. Mit Hilfe von PixiJS-Befehlen (http://pixijs.download/dev/docs/index.html<ref>[http://pixijs.download/dev/docs/index.html PixiJS API Documentation]</ref>) wird zunächst ein Grafikobjekt erzeugt. Dieses Grafikobjekt wird mit Inhalt gefüllt (einem Kreis von besimmter Farbe mit einem Rand in einer anderen Farbe) und zu guter Letzt zu der PixiJS-Bühne ({{dh}} im Prinzip zum zugehörigen Canvaselement) hinzugefügt. Wenn sich später bestimmte Attribute des Grafikobjektes – wie {{zB}} die Position oder die Größe – ändern, wird die graphische Darstellung des Objektes auf dem Canvas automatisch von PixiJS angepasst (sofern die PixiJS-Game-Loop aktiv ist).  


===Aufgabe 2===
===Aufgabe 2===


Lösen Sie Aufgabe 2 der [[MMProg: Praktikum: WiSe 2017/18: GameLoop01#Aufgabe 2|ersten Praktikumsaufgabe]] mit Hilfe von PixiJS:
Erstellen Sie eine erste Version der Lösung der Aufgabe 2, indem Sie die Lösung der Aufgabe 1 kopieren:
''Lassen Sie die Eule nicht waagerecht, sondern in der horizontalen Mitte des Browserfensters senkrecht von Fenstrrand zu Fensterrand fliegen.'' Anstelle einer Eule verwenden Sie bitte analog zu Aufgabe 1 einen farbigen „Ball“ mit Rand.
* Kopieren Sie <code>src/js/game/game01.js</code> nach  <code>src/js/game/game02.js</code>
* Kopieren Sie <code>src/js/app01.js</code> nach  <code>src/js/app02.js</code> und ersetzen Sie in dieser Datei <code>'./game/game01.js'</code> durch <code>'./game/game02.js'</code>.
* Kopieren Sie <code>src/index01.html</code> nach  <code>src/index02.html</code> und ersetzen Sie in dieser Datei <code>&lt;title&gt;Ball02 (app01)&lt;/title&gt;</code> durch <code>&lt;title&gt;Ball02 (app02)&lt;/title&gt;</code>. (Einen Verweis auf die JavaScript-Datei <code>app02.js</code> gibt es in dieser Datei nicht. Der korrekte Verweis wird von webpack automatisch beim Erstellen der Dateien des Web-Ordners injiziert.)
* Starten Sie <code>npm run watch</code> neu, damit auch die neu erstellten Dateien automatisch von webpack übersetzt werden.
 
Lösen Sie Aufgabe 2 der [[MMProg: Praktikum: WiSe 2018/19: Ball01#Aufgabe 2|ersten Praktikumsaufgabe]] mit Hilfe von PixiJS. Das heißt, ändern Sie Ihre Version von
Aufgabe 2, die Sie gerade durch Kopieren erstellt haben so ab, dass Folgendes passiert:  
''Lassen Sie den Ball nicht waagerecht, sondern in der horizontalen Mitte des Browserfensters senkrecht von Fensterrand zu Fensterrand fliegen.'' Anstelle einer Eule verwenden Sie bitte analog zu Aufgabe 1 einen farbigen „Ball“ mit Rand.


Vergessen Sie nicht auch die Kollissionserkennung und -behandlung für die beiden Bildschirmseiten <code>top</code> und <code>botton</code>
Vergessen Sie nicht auch die Kollisionserkennung und -behandlung für die beiden Bildschirmseiten <code>top</code> und <code>botton</code>
zu implementieren.
zu implementieren.


Und denken Sie daran, <code>grunt</code> oder besser noch <code>grunt watch</code> zu verwenden. :-)
Und denken Sie daran, <code>npm run dev</code> oder besser noch <code>npm run watch</code> zu verwenden. :-)


===Aufgabe 2a===
===Aufgabe 2a===
Erstellen Sie zunächst wie in Aufgabe 2 beschrieben eine Kopie der Lösung von Aufgabe 2 und nennen Sie sie 2a (<code>index02a.html</code>, <code>app02a.js</code>, <code>game02a.js</code>).


Ändern Sie nun die View des Balls: Anstelle eines farbigen Kreises stellen Sie bitte eine Eule oder einen Pinguin dar.
Ändern Sie nun die View des Balls: Anstelle eines farbigen Kreises stellen Sie bitte eine Eule oder einen Pinguin dar.
Importieren Sie zunächst das gewünschte graphische Objekt:
Importieren Sie zunächst das gewünschte graphische Objekt in die Datei <code>game02a.js</code>:


<source lang="javascript">
<source lang="javascript">
import sprite from '../../img/owl-150.png';
import imgBall from '../../img/owl-150.png';
</source>
</source>
oder
oder
<source lang="javascript">
<source lang="javascript">
import sprite from '../../img/tux-150.png';
import imgBall from '../../img/tux-150.png';
</source>
</source>


(Genaugenommen weisen Sie Webpack mit dieser Anweisung an, das Grafikobjket im Web-Bereich zu erzeugen und die zugehörige URL in der
(Genau genommen weisen Sie webpack mit dieser Anweisung an, das Grafikobjekt im Ordner <code>web/img</code> zu erzeugen und die zugehörige URL in der
Variablen <code>sprite</code> zu speichern.)
Variablen <code>imgBall</code> zu speichern. Ohne  webpack müssten Sie das Bild von Hand in diesem Ordner ablegen und dem Spriteobjekt – siehe unten – die korrekte URL übergeben.)
 
Darüber hinaus müssen Sie dafür sorgen, das von <code>pixi.js</code> nicht mehr die Klasse <code>Graphics</code>,
sondern stattdessen die Klasse <code>Sprite</code> importiert wird.
 
Ändern Sie in der Initfunktion die Befehle zur Erstellung der View so ab, dass kein Kreis mehr gezeichnet, sondern das Bild
der Eule oder des Pinguins dargestellt wird.
 
<source lang="javascript">
let
  ...
  v_ball_view = Sprite.fromImage(imgBall); // imgBall contains the URL of the image
</source>


Ändern Sie in der Initialiseirungsfunktion die Befehle zur Erstellung der View so ab, dass kein Kreis mehr gezeichnet, sondern das Bild
In der Initfunktion wird nur noch der Ankerpunkt in die Mitte des Viewobjekts verschoben.
der Eule oder des Pinguins dargestellt wird:
Sie müssen die vier Befehle zur ERzeugung des Grafikobjektes also löschen.
Dieses Objekt wird dann wieder zur PixiJS-Bühne hinzugefügt.


<source lang="javascript">
<source lang="javascript">
v_ball_view = p_pixi.Sprite.fromImage(sprite); // sprite contains the URL of the image
v_ball_view.anchor.set(0.5); // center the anchor
v_ball_view.anchor.set(0.5); // center the anchor
 
p_pixi_app.stage.addChild(v_ball_view);
p_stage_view.addChild(v_ball_view);
</source>
</source>


===Aufgabe 3===
===Aufgabe 3===
Erstellen Sie zunächst wie in Aufgabe 2 beschrieben eine Kopie der Lösung von Aufgabe 2a und nennen Sie sie 3 (<code>index03.html</code>, <code>app03.js</code>, <code>game03.js</code>).


Lösen Sie Aufgabe 3 der [[MMProg: Praktikum: WiSe 2017/18: GameLoop01#Aufgabe 2|ersten Praktikumsaufgabe]] mit Hilfe von PixiJS:
Lösen Sie Aufgabe 3 der [[MMProg: Praktikum: WiSe 2017/18: GameLoop01#Aufgabe 2|ersten Praktikumsaufgabe]] mit Hilfe von PixiJS:
''Lassen Sie die Eule von der Browsermitte aus schräg über den Bildschirm fliegen. In x-Richtung soll sie doppelt so schnell sein (200 Pixel/s) wie in y-Richtung (100 Pixel/s).''  
''Lassen Sie der Ball von der Mitte des Browserfensters aus schräg über den Bildschirm fliegen. In x-Richtung soll er doppelt so schnell sein (200 Pixel/s)
wie in y-Richtung (100 Pixel/s).''  
(Diesmal sollten Sie gleich eine Eule oder einen Pinguin fliegen lassen und nicht erst einen Kreis.)
(Diesmal sollten Sie gleich eine Eule oder einen Pinguin fliegen lassen und nicht erst einen Kreis.)


Einen Nachteil hat die Lösung der Aufgabe 2a allerdings: Das Bild des Vogels wird mittels <code>p_pixi.Sprite.fromImage</code> synchron geladen.
===Aufgabe 3a===
 
Erstellen Sie zunächst wie in Aufgabe 2 beschrieben eine Kopie der Lösung von Aufgabe 3 und nennen Sie sie 3a (<code>index03a.html</code>, <code>app03a.js</code>, <code>game03a.js</code>).
 
Einen Nachteil hat die Lösung der Aufgaben 2a und 3: Das Bild des Vogels wird mittels <code>Sprite.fromImage</code> synchron vom Web-Server geladen.
Das heißt, solange bis das Bild geladen wurde, ist die Anwendung „eingefroren“. Bei einem kleinen Bild ist das sicher kein Problem, wenn aber
Das heißt, solange bis das Bild geladen wurde, ist die Anwendung „eingefroren“. Bei einem kleinen Bild ist das sicher kein Problem, wenn aber
eine Vielzahl von oder große Medien geladen werden müssen – wie dies bei realen Web-Anwendungen der Fall ist –, ist dies sicher keine Option mehr.
eine Vielzahl von oder große Medien geladen werden müssen – wie dies bei realen Web-Anwendungen der Fall ist –, ist dies sicher keine Option mehr.


Sie sollten die Aufgabe zunächst einmal auf Basis Ihrer Erkenntnisse von Aufgabe 2a lösen.
Nehmen Sie an Ihrer Kopie 3a folgende Änderung vor:
 
Solbald diese Lösung funktioniert, sollten Sie folgene Änderung vornehmen:
Laden Sie das Bild des Vogels asynchron mit Hilfe eines PixiJS-Loader-Objektes  
Laden Sie das Bild des Vogels asynchron mit Hilfe eines PixiJS-Loader-Objektes  
(https://github.com/englercj/resource-loader, http://pixijs.download/dev/docs/PIXI.loaders.Loader.html)<ref>[https://github.com/englercj/resource-loader GitHub: resource-loader
([http://pixijs.download/dev/docs/PIXI.loaders.Loader.html MDN web doc: PIXI.loaders.Loader ], [https://github.com/englercj/resource-loader GitHub: resource-loader])<ref>[http://pixijs.download/dev/docs/PIXI.loaders.Loader.html PixiJS API Documentation: PIXI.loaders.Loader]</ref><ref>[https://github.com/englercj/resource-loader GitHub: resource-loader
]</ref><ref>[http://pixijs.download/dev/docs/PIXI.loaders.Loader.html PixiJS API Documentation: PIXI.loaders.Loader]</ref>.
]</ref>.


Eine wesentliche Änderung, die sich wegen der Asynchronität ergibt, is, dass der Ladevorgang später endet als die Ausführung der Initialisierungsfunktion.
Zunächst ein wenige Theorie:
Wenn man direkt im Anschluss an die Ausführung der Initialisierungsfunktion die Game Loop startet, ist die Wahrscheinlichkeit groß, dass die Bilder noch gar nicht
geladen wurden und die Anwendung daher abstürzt. Abhilfe schafft hier eine Callback-Funktion, die vom PixiJS-Loader aufgerufen wird, sobald der Ladevorgang beendet ist.
Stellen Sie in der Datei <code>app3.js</code> eine Callback-Funktion bereit, deren einzige Aufgabe es ist, die Game Loop zu starten.


Ersetzen Sie in der Datei <code>app3.js</code> den Code
====Asynchronität in EcmaScript 2017====
<source lang="javascript">
game.init(PIXI, c_stage, c_pixi_app.stage);
new GameLoopPixi(c_pixi_app.ticker, game.update, game.render).start();
</source>
durch
<source lang="javascript">
game.init
( PIXI, c_stage, c_pixi_app.stage,
  // callback function: if the game has been initialized, start it.
  function()
  { new GameLoopPixi(c_pixi_app.ticker, game.update, game.render).start(); }
);
</source>


Zum Zeitpunkt der Ausführung der Callback-Funktion wurden alle Bilder geladen und daher kann die Game Lopp nicht mehr aufgrund fehlender Medien abstürzen.
Eine wesentliche Änderung, die sich wegen der Asynchronität ergibt, ist, dass der Ladevorgang später endet als die Ausführung der Initialisierungsfunktion.
Wenn man direkt im Anschluss an die Ausführung der Initialisierungsfunktion die Game Loop startet, ist die Wahrscheinlichkeit groß, dass die Bilder noch gar nicht
geladen wurden und die Anwendung daher abstürzt. Abhilfe schafft hier der Einsatz von  modernen asynchronen JavaScript-Anweisungen:
* <code>Promise</code>-Objekte ([https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Promise MDN web docs: Promise])
*<code>async</code>-Funktionen ([https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Statements/async_function MDN web docs: async function])
* <code>await</code>-Operator ([https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/await MDN web docs: await])


Nun muss noch die Initialisierungsfunktion in der Datei <code>game3.js</code> entsprechend angepasst werden.
Diese Konstrukte wurden eingeführt, da man in einem klassischen ES5-Programm häufig den Wald vor lauter Bäumen,
{{dh}} den Code vor lauter Callback-Funktionen nicht mehr gesehen hat, wenn eine Vielzahl von asynchronen Anweisungen benötigt wurden.


Zunächst wird sie um einen Callback-Parameter <code>f_ready</code> erweitert:  
Mit ES 2015 wurde die globale Klasse <code>Promise</code> eingeführt.
Ein <code>Promise</code>-Objekt führt eine Aktion, wie das Laden eines Bildes oder das Warten auf ein Timer-Ereignis, asynchron aus. Sobald
die Aktion erfolgreich beendet wird, führt sie eine Callback-Funktion aus, die ihr in der Methode <code>then</code> übergeben wurde.
Der Callback-Funktion wird das Ergebnis der asynchronen Aktion als Ergebnis übergeben, so dass sie damit weiterarbeiten kann.
Falls diese Funktion wieder ein <code>Promise</code>-Objekt  als Ergebnis liefert, kann man den nächsten asynchronen Befehl mittels eines
weiteren Aufrufs der Methode <code>then</code> anfügen. Etc. pp. Auf diese Weise konnte man eine Abfolge von asynchronen Anweisungen zumindest
linear notieren:


<source lang="javascript">
<source lang="javascript">
function init(p_pixi, p_stage, p_stage_view, f_ready)
myPromise
  .then(function(p_value) { ...})
  .then(function(p_value) { ...})
  .then(function(p_value) { ...})
</source>
</source>


In diesem Parameter wird der Initialisierungsmethode die zuvor definierte Callback-Funktion übergeben, die aufgerufen weren muss, sobald der Initalisierungsvorgang
Wenn man die kürzere Arrow-Schreibweise für Funktionen verwendet, die mit ES 2015 eingeführt wurde ([https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Functions/Pfeilfunktionen MDN web doc: Pfeilfunktionen]), wird der oder noch etwas kompakter und damit lesbarer:
vollständig abgearbeitet wurde.
 
Bislang wird das Bild der Eule synchron geladen und dem View-Objekt zugewiesen.
Dies soll künfig jedoch asynchron erfolgen. Löschen Sie daher in der Initialisierungsfunktion
zunächst alle drei Befehle, die mit dem View-Objekt  <code>v_ball_view</code> zu tun haben. 
 
Die benötigten Medien (hier: das Bild der Eule) werden künftig mit Hilfe eines PixiJS-Loaders '''asynchron''' geladen:
 
<source lang="javascript">
<source lang="javascript">
p_pixi
myPromise
    .loader
  .then(p_value => { ...})
    .add('sprite', sprite)
  .then(p_value => { ...})
    .load(function(p_loader, p_resources)
  .then(p_value => { ...})
          {
           
            f_ready();
          }
    );
</source>
</source>


Der Pixi-Loader <code>p_pixi.loader</code> wird zunächst angewiesen, das Medium, dessen URL in der Variablen
Allerdings ist das auch nicht sonderlich schön zu lesen, vor allem wenn man umfangreiche asynchrone Funktionen zu definieren hat.
<code>sprite</code> steht, zu laden und unter dem (sehr einfaltsreichen) Namen <code>'sprite'</code> in einem JavaScript-Objekt zu speichern.
Und jede dieser Funktionen muss ein eigenes <code>Promise</code>-Objekt erstellen, was den Code auch nicht gerade lesbarer macht.
Es ist durchaus üblich, eine Vielzahl von Medien auf einmal ({{dh}} parallel) zu laden:


Daher wurden in ES 2017 die asynchronen Funktionen eingeführt:
<source lang="javascript">
<source lang="javascript">
p_pixi.loader.add(...).add(...).add(...).add(...)...
async function myFunction(...)
{ ...}
</source>
</source>


oder auch
In dieser Funktionen werden Anweisungen geschrieben, wie in jeder anderen Funktion auch. Und diese Anweisungen werden der Reihe nach abgearbeitet.
Allerdings gibt es einen fundamentalen Unterschied. Der Funktionsrumpf wird asynchron ausgeführt. Das Ergebnis der Funktion ist ein (implizites)
<code>Promise</code>-Objekt, dessen <code>then</code>-Funktion erst aufgerufen wird, sobald alle asynchronen Befehle im Rumpf
der Funktion erfolgreich beendet wurden.


<source lang="javascript">
Da heißt, bei Aufruf der Funktion wird diese sofort wieder beendet. Der Browser wird durch die Funktion nicht blockiert, wie es {{zB}} beim
p_pixi.loader.add(...);
synchronen Laden eines Bildes der Fall wäre. Die im Rumpf der Funktion enthaltenen Befehle werden trotzdem der Reihe nach ausgeführt, aber asynchron.
p_pixi.loader.add(...);
p_pixi.loader.add(...);
p_pixi.loader.add(...);
...
</source>
 
Zu guter Letzt wird die Methode <code>p_pixi.loader.load(...)</code> aufgerufen. Diese startet alle erwünschten Ladevorgänge und führt,  
sobald alle Ressourcen geladen wurden, die ihr übergebene Callback-Funktion aus. Dieser Callback-Funktion werden vom Pixi-Loader
zwei Argumente übergeben:
* Das Loader-Objekt, mit dem die Medien geladen wurden. (Dieses Objekt wird allerdings nur in Ausnahmenfällen benötigt.)
* Ein Ressource-Objekt, in dem alle zuvorgeladenen Medien unter den in den Add-Anweisungen angegebenen Namen enthalten sind.


Innerhalb dieser Funktion kann nun das View-Objekt des Balls erstellt und in der PixiJS-Bühne gespeichert werden.
Wenn Sie die Funktion <code>init</code> in der Datei <code>app01.js</code> näher betrachten, fällt Ihnen auf, das sie als <code>async</code>-Funktion
Fügen Sie daher vor dem Befehl <code>f_ready();</code> folgende Befehle ein:
definiert wurde. In ihrem Rumpf befinden sich neben drei Standard-Anweisungen auch die Anweisung <code>await wait(500);</code>. Die Funktion <code>wait</code>
wurde von mir in der Datei <code>src/lib/wk/util/wait.js</code> definiert:


<source lang="javascript">
<source lang="javascript">
v_ball_view = new p_pixi.Sprite(p_resources['sprite'].texture);
function wait(p_time)
v_ball_view.anchor.set(0.5); // center the anchor
{ return new Promise(function(p_resolve)
 
                    { setTimeout(p_resolve, p_time); }
p_stage_view.addChild(v_ball_view);
                    )
}
</source>
</source>
(In Wirklichkeit habe ich die modernere Arrow-Schreibweise verwendet,
aber das Ergebnis ist dasselbe.)


Beachten Sie, dass das geladene Bild als Textur für das Ball-View-Objekt verwendet wird. Ein Bild kann also durchaus für diverse PixiJS-Grafik-Objekte als Textur verwendet werden. Beispielsweise können viele gleichartige Moorhühner oder Vögel oder Schweine erzeugt werden,  
Die Funktion <code>wait</code> erstellt ein <code>Promise</code>-Objekt, das die
ohne dass die zugehörige Textur mehrfach vom Server geladen werden muss.
Funktion <code>setTimeout</code> ausführt ([https://developer.mozilla.org/de/docs/Web/API/WindowTimers/setTimeout MDN web doc:
WindowTimers.setTimeout()]). Diese Funktion wartet eine gewisse Zeit <code>p_time</code> und ruft dann eine Callback-Funktion auf.
Das ist die typische ES5-Vorgehensweise  zur Behandlung von asynchronen Aktionen. In diesem Fall wird die Callback-Funktion <code>p_resolve</code>
aufgerufen. Das ist eine Funktion, die dem <code>Promise</code>-Objekt mitteilt, dass die asynchrone Aktion erfolgreich beendet wurde
(und das <code>Promise</code>-Objekt nun die <code>then</code>-Funktion ausführen könnte).


Ganz wichtig ist es, als '''letzten Befehl''' der soeben definierten Callback-Funktion die Callback-Funktion <code>f_ready</code> auszuführen
Seit ES 2017 kann man in <code>async</code>-Funktionen den <code>await</code>-Operator verwenden, um das Ergebnis eines  <code>Promise</code>-Objekts
(also mittels <code>f_ready();</code> aufzurufen). Anderenfalls wird die Game Loop nie gestartet und die Web-Anwendung verharrt im Initialzustand.
zu verarbeiten. Das ist '''viel''' eleganter, als mit den (auch schon wieder veralteten) <code>then</code>-Funktionen.
Sie schreiben einfach den Befehl <code>await wait(500);</code> in den Rumpf Ihrer <code>async</code>-Funktion. Dann wird die (asynchrone) Verarbeitung
des Rumpfes dieser funktion um 500 ms ({{dh}} um eine halbe Sekunde) unterbrochen, bevor der nächste Befehl abgearbeitet wird.


Wenn Sie schon dabei sind, sollten Sie gleich noch eine zweite Unsauberkeit eliminieren. Die Konfiguration der Modell-Objekte <code>v_stage</code>
====Asynchrones Laden von Bildern für PixiJS====
und <code>v_ball</code> sollte mittels einer JSON-Datei erfolgen. Im leeren Projekt finden Sie die JSON-Datei <code>src/js/init3.json</code>, die diese
beiden Objekte enthält. Importieren Sie diese Datei in die Datei <code>game3.js</code>
(vgl. [[HTML5-Tutorium: JavaScript: Hello World 05#JSON|HTML5-Tutorium: JavaScript: Hello World 05]]) und weisen Sie die beiden importierten
Objekte den Variablen <code>v_stage</code> bzw. <code>v_ball</code> zu.


'''Tipp''': Um die Anzahl der WebStorm-Warnings zu reduzieren, können Sie direkt vor den JSON-Import-Befehl zusätzlich folgenden
PixiJS stellt das Object <code>PIXI.loader</code> zur Verfügung
Import-Befehl einfügen:
([http://pixijs.download/dev/docs/PIXI.loaders.Loader.html PixiJS API Documentation: PIXI.loaders.Loader]),
mit dem Sie Bilder (oder auch andere Medien) asynchron laden können. Die Bilder werden dabei gleich so gespeichert,
dass sie problemlos von PixiJS weiterverarbeitet werden können.


Importieren Sie diesen Loader in Ihre Datei <code>app03.js</code>.
Ersetzen Sie
<source lang="javascript">
import {Application} from 'pixi.js';
</source>
durch
<source lang="javascript">
<source lang="javascript">
import '../../json/init3.js';
import {loader, Application} from 'pixi.js';
</source>
</source>  


Die Datei <code>init3.js</code> enthält keinerlei ausführbaren Code, sondern nur einen Kommentar, der von Webpack spätestens dann entfernt wird,
Fügen Sie außerdem folgenden Import-Befehl in die Datei <code>app03.js</code> ein:
wenn die Ausgabedatei <code>app3.bundle.js</code> mittels <code>grunt compress</code> komprimiert wird. Der Kommentar hat es allerdings in sich.
<source lang="javascript">
Er beschreit im JSDoc-Format<ref>[http://usejsdoc.org/ JSDoc]</ref> den Inhalt der JSON-Datei. Dieser Kommentar wird von WebStorm interpretiert, um
import imgBall from '/img/tux-150.png';
die Anzahl der Warnings zu reduzieren.
</source>  


===Aufgabe 3a===
Diesen Befehl kennen Sie schon. Er befindet sich noch in der Datei <code>game03a.js</code>.
Dort sollten Sie ihn löschen (In der Datei <code>game03.js</code> dürfen sie ihn dagegen nicht löschen.)
Wie Sie wissen, bewirkt dieser Import-Befehl, dass die
URL des Bildes <code>/img/tux-150.png</code> in der Variablen
<code>imgBall</code> gespeichert ist (und das Bild in den
Web-Ordner kopiert wird).


Erweitern Sie Ihre Lösung von Aufgabe 3 so, dass 5 unterschiedliche Vögel auf unterschiedlichen Routen über die Bühne fliegen.
Fügen Sie nun vor der Initfunktion den folgenden Befehl ein.
In der Datei <code>src/json/init3a.json</code> finden Sie die zuanimierenden Objekte. Anstelle eines Objektes <code>ball</code> ist
<source lang="javascript">
hier ein Array <code>balls</code> definiert, das fünf Ball-Objekte enthält. Jedes dieser Objekte hat neben den bereits bekannten
loader.add('imgBall', imgBall);
Attributen ein weiteres Attribut <code>img</code>, das beschreibt, mit welcher Textur das Objekt visualisiert werden soll.
</source>  


Importieren Sie diese JSON-Datei wieder und speichern Sie die darin enthaltenen Objekte in den Variablen <code>v_stage</code> bzw. <code>v_balls</code>
Damit teilen Sie dem Loader mit, welches Bild er später asynchron laden soll.
(nicht <code>v_ball</code>!). Definieren Sie außerdem eine Variable <code>v_ball_views</code> und initialisieren Sie diese mit einem leeren Array.
Als Argumente erwartet einen Namen, mit dem später auf das Bild zugegriffen werden kann,
In diesem werden anschließend von der Initialisierungsmethode die zugehörigen View-Objeke abgelegt.
sowie die URL des Bildes, under der er es im Web-Auftritt findet.
 
Es ist durchaus üblich, eine Vielzahl von Medien auf einmal ({{dh}} parallel) zu laden:
Alls nächstes müssen Sie die Methode <code>update</code> umbenennen und um den Parameter <code>p_ball</code> erweitern:


<source lang="javascript">
<source lang="javascript">
function update_ball(p_ball, dt)
p_pixi.loader
  .add(...)
  .add(...)
  .add(...)
  .add(...)...
</source>
</source>


Bislang hat die Update-Methode auf das global definierte Ball-Modell <code>v_ball</code>
Jetzt könnten Sie die Bilder mittels <code>loader.load(geeigneteCallbackFunktion)</code>
zugegriffen, um die neue Postiion zuberechnen und die Kollissionserkennung und
asynchron laden. Da PixiJS keine Proimises unterstützt, müsste man hier auf die alte ES-5-Methode zurückgreifen.
-behandlung durchzuführen. Nun gibt es fünf Ball-Objekte. Die Update-Funktion muss für jeden einzelnen dieser Bälle
durchgeführt werden. Daher wird sie fünfmal aufgerufen. Bei jedem Aufruf wird ihr im Parameter <code>p_ball</code>
Unter <code>/wk_pixi/loader/asyncLoader</code> finden Sie eine kleine Funktion, die das
ein anderer Ball zur Bearbeitung übergeben.
eleganter mit Hilfe eines <code>Promise</code>-Objektes macht. Importieren Sie diese Funktion:


Die eigentliche Update-Methode code>update</code> existiert weiterhin, doch ihre Aufgabe hat sich geändert:
<source lang="javascript">
Sie muss mit Hilfe einer For-Schleife<ref>[https://wiki.selfhtml.org/wiki/JavaScript/Schleife SELFHTML-Wiki: JavaScript/Schleife]</ref> das Array <code>v_balls</code> durchlaufen und für jedes Element die (Hilfs-)Methode
import loadResources from '/wk_pixi/loader/asyncLoader';
<code>update_ball</code> aufrufen.
</source>  


Die Renderfunktion muss analog angepasst werden: Sie muss ebenfalls das Array <code>v_balls</code> durchlaufen.
Jetzt können Sie die Ressourcen ganz elegant mit <code>await</code> laden.
Für jedes Ball-Modell-Objekt muss dessen aktuelle Position dem zugehörigen Ball-View-Objekt zuweisen werden.
Fügen Sie  folgenden Code als erste Zeile in den Rumpf der Initfunktion ein:
Das aktuelle View-Objekt findet sie jeweils im Array <code>v_ball_views</code> an derselben Position.
<source lang="javascript">
(Sie könnten auch das Array <code>v_ball_views</code> durchlaufen. Das macht keinen Unterschied, weil beide Arrays
const c_resources = await loadResources();
gleichviele Elemente enthalten.)
</source>  


Zu guter Letzt muss noch die Callback-Funktion des Pixi-Loaders angepasst werden. Diese muss ebenfalls
Damit wird die asynchrone Ausführung der Initfunktion solange unterbrochen, bis alle Bilder geladen wurden.
das Array <code>v_balls</code> durchlaufen und für jedes Modell-Objekt ein zugehöriges View-Objekt
Danach wird der nächste Befehl ausgeführt.
erzeugen. Jedes neu erzeugte View-Objekt wird abschließend mittels
Das ist der Befehl, der das Spiel initialisiert:
<source lang="javascript">
game.init(c_pixi_app);
</source>  


Ersetzen Sie ihn durch:
<source lang="javascript">
<source lang="javascript">
v_ball_views.push(l_ball_view);
game.init(c_pixi_app, c_resources);
p_stage_view.addChild(l_ball_view);
</source>
</source>


sowohl ans Ende des Arrays <code>v_ball_views</code> als auch in die PixiJS-Bühne eingefügt.
Das heißt, übergeben Sie der Initfunktion nicht nur das PixiJS-App-Objekt, sondern auch die Ressourcen (das Bild), das Sie geladen haben.
Jetzt müssen Sie nur noch die Initfunktion in der Datei <code>game03a.js</code> anpassen.  


Eine Sache ist bei der Erzeugung der Views noch zu beachten. Es gibt zwei Texturen und in jedem Modell-Objekt ist
Ersetzen Sie dort in der <code>let</code>-Anweisung
vermerkt, welche Textur verwendet werden soll.
<source lang="javascript">
 
v_ball = {},
Importieren Sie also die URLs beider Texturen:
v_ball_view =  ...;
</source>
durch
<source lang="javascript">
v_ball = {},
v_ball_view;
</source>


Das heißt, das Ball-View-Objekt wird nicht mehr hier erzeugt, sondern erst in der Initfunktion.
Fügen Sie zu diesem Zweck den Parameter <code>p_resources</code> in die Parameterliste
der Initfunktion ein:
<source lang="javascript">
<source lang="javascript">
import owl from '../../img/owl-50.png';
function init(p_pixi_app, p_resources)
import tux from '../../img/tux-50.png';
</source>
</source>


und laden sie die zugehörigen Bilder anschließend mit Hilfe des Pixi-Loaders:
In diesem Parameter werden der Funktion die Ressourcen (Bilder) übergeben,
die Sie zuvor asynchron) geladen haben. Damit können Sie jetzt die View erzeugen.


Fügen Sie vor die beiden Befehle
<source lang="javascript">
<source lang="javascript">
p_pixi.loader.add('owl', owl).add('tux', tux)...
v_ball_view.anchor.set(0.5),
p_pixi_app.stage.addChild(v_ball_view);
</source>
den Befehl
<source lang="javascript">
v_ball_view = new Sprite(p_resources['imgBall'].texture);
</source>
</source>


Beim Erzeugen der Views (innerhalb der zuvor genannten For-Schleife) verwenden Sie dann jeweils die im Model-Objekt beschriebene Textur:
Es wird, wie auch schon in den Aufgaben 2a und 3, ein <code>Sprite</code>-Objekt
erstellt. Diesmal wird jedoch das Bild nicht aus einer Datei gelesen (<code>Sprite.fromImage...)</code>)
sondern aus dem vom Loader erstellten Ressourcen-Objekt unter dem Namen <code>'imgBall'</code>
extrahiert. Warum man nicht direkt das Objekt <code>p_resources['imgBall']</code> verwenden kann, sondern dessen Textur verwenden muss,
ist eines von den unergründlichen Geheimnissen von PixiJS.


BTW: Man bräuchte das Objekt <code>p_resources</code> gar nicht als Argument an die Initfunktion übergeben.
Man könnte auch in der Datei </code>game3a.js</code>, auch wieder den Loader importieren
<source lang="javascript">
<source lang="javascript">
let l_ball_view = new p_pixi.Sprite(p_resources[v_balls[i].img].texture);
import {loader, Sprite} from 'pixi.js';
</source>
</source>
und dann mittels <code>load.resources</code> auf dieses Objekt zugreifen.
Allerdings ist die Kommunikation zweier Module über ein globales Objekt (wie das Objekt <code>loader</code>)
''deprecated'', da man nicht weiß, wann und ob überhaupt das darin enthaltene Ressourcenobjekt sauber initialisiert wurde.
Eine direkte Kommunikation mittels Parametern und Argumenten ist dem vorzuziehen.
===Aufgabe 3b===
Erstellen Sie zunächst wie in Aufgabe 2 beschrieben eine Kopie der Lösung von Aufgabe 3a und nennen Sie sie 3b (<code>index03b.html</code>, <code>app03b.js</code>, <code>game03b.js</code>).
Erweitern Sie Ihre Lösung von Aufgabe 3 so, dass 5 unterschiedliche Vögel auf unterschiedlichen Routen über die Bühne fliegen.
(Hier benötigen Sie Ihr Wissen über den Umgang mit Arrays: siehe Praktikumsaufgaben [[MMProg: Praktikum: WiSe 2018/19: EcmaScript01|EcmaScript01]] und [[MMProg: Praktikum: WiSe 2018/19: EcmaScript02|EcmaScript02]].)
Gehen Sie dazu folgendermaßen vor:
* Laden Sie in der Datei <code>app03b.js</code> die beiden Bilder <code>/img/owl-150.png</code> und <code>/img/owl-150.png</code> asynchron mittels des PixiJS-Loaders.
* Bearbeiten Sie nun die Datei <code>game03b.js</code>*
* Legen Sie in einer Variablen oder Konstanten <code>v_ball_nr</code> die Anzahl der Vögel fest, die fliegen sollen. Initialisieren Sie diese mit der Zahl <code>5</code>.
* Ersetzen Sie <code>v_ball</code> und <code>v_ball_view</code> durch <code>v_balls</code> und <code>v_ball_views</code>. Diese Variablen sollen jeweils ein Array mit <code>v_ball_nr</code> Model-Objekten bzw. <code>v_ball_nr</code> zugehörigen View-Objekten beinhalten. Die Variable <code>v_ball_views</code> wird in der Initfunktion initialisiert und die Variable <code>v_balls</code> in der Resetfunktion.
* Initialisieren Sie das Array <code>v_ball_views</code> in der Initfunktion mit Hilfe einer Schleife und des [https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/push Push-Befehls]. Sie müssen <code>v_ball_nr</code> Sprite-Objekte erzeugen und ins Array einfügen. Als Bild wählen Sie per Zufallsentscheidung (<code>Math.random() < 0.5</code>) jeweils die Eule oder Tux.
* Initialisieren Sie das Array <code>v_balls</code> in der Resetfunktion mit Hilfe einer Schleife und des Push-Befehls. Der Radius aller fünf Bälle sei 75. Die Position wählen Sie zufällig <code>Math.random()</code> innerhalb der Bühne, die Geschwindigkeit in $x$- und $y$-Richtung ebenfalls zufällig jeweils zwischen 100 und 400.
* Sorgen Sie in der Updatefunktion mit Hilfe einer Schleife dafür, dass all 5 Objekte im Array <code>v_balls</code> aktualisiert werden.
* Sorgen Sie in der Renderfunktion mit Hilfe einer Schleife dafür, dass all 5 Objekte im Array <code>v_ball_views</code> rerendert werden.


===Aufgaben 4 bis 10===
===Aufgaben 4 bis 10===

Version vom 30. November 2018, 12: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)

Vorbereitung

Importieren Sie das leere Git-Projekt Ball02 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 dies 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:8888/BENUTZER/Ball02.git

Ziel

Ziel dieser Praktikumsaufgabe ist es, die “HTML5 Creation Engine” PixiJS v4 kennenzulernen.[1] Bei PixiJS handelt es sich um eine sehr mächtige (und damit auch sehr große) JavaScript-Bibliothek zur Realisierung von 2D-Animationen und -Spielen.

Mit HTML5 wurde das Canvas-Element (Leinwand-Element) eingeführt.[2] „Auf“ einem Canvas-Element können Grafiken gezeichnet werden. Aktuelle Browser unterstützen üblicherweise sowohl den CanvasRenderingContext2D zum Erstellen von 2D-Grafiken[3] als auch WebGL zum Erstellen von 3D-Grafiken (auf Basis von OpenGL)[4]. Die Erstellung von Grafiken mit WebGL ist wesentlich effizienter als die Erstellung von Grafiken mit dem CanvasRenderingContext2D, zumindest dann wenn, eine Grafikkarte zur Verfügung steht, deren Prozessor (GPU) OpenGL nativ unterstützt. Dies ist insbesondere dann von Bedeutung, wenn Animationen erstellt werden, d. h., wenn 60 mal pro Sekunde (also alle 16,7 ms) eine neue Grafik auf dem Canvas angezeigt werden soll.

PixiJS ist als Ersatz für den CanvasRenderingContext2D gedacht. Die Bibliothek stellt im Wesentlichen dieselben Grafik-Befehle wie der 2D-Kontext zur Verfügung, erstellt aber, wann immer möglich, die 2D-Grafiken mit Hilfe von WebGL. Nur wenn diese Schnittstelle vom Browser nicht zur Verfügung gestellt wird, erfolgt als Fallback das Rendering mit Hilfe des 2D-Kontextes. Dies hat allerdings meist einen deutlichen Performanz-Einbruch zur Folge.

Aufgaben

In Ihrem Projekt finden Sie wiederum mehrere Web-Anwendungen: index01.html verwendet die gepackte Version von app01.js, die ihrerseits das Spiel game01.js einbindet. Et cetera.

Schreiben Sie Ihre Lösungen der Aufgabe $i$ in die Datei game$i$.js. Am einfachsten ist es, wenn Sie jeweils die Lösung der vorangegangenen Aufgabe kopieren und diese Kopie dann weiterentwickeln.

Aufgabe 1

Diese Aufgabe entspricht Aufgabe 1 der Praktikumsaufgabe MMProg: Praktikum: WiSe 2017/18: GameLoop01: Lassen Sie die Eule horizontal vom linken bis zum rechten Fensterrand des Browsers fliegen.

Im Projekt WK_Ball02_Empty ist eine mögliche Lösung dieser Aufgabe bereits enthalten, wobei allerdings ein Kreis „fliegt“ anstelle einer Eule. Ihre Aufgabe ist es nun, sich die Unterschiede zur Lösung der ersten Praktikumsaufgabe WK_Ball01, die sich durch die Verwendung von PixiJS ergeben, klar zu machen.

app01.js

  • Es wird zusätzlich die in der JavaScript-Bibliothek pixi.js enthaltene Klasse Application importiert. Beachten Sie, dass hier diejenige Klasse der PixiJS-Bibliothek importiert wird, die auch wirklich benötigt wird. Man könnte auch import * as PIXI from 'pixi.js'; schreiben und damit alle PixiJS-Klassen, -Funktionen und -Objekte in der Variablen PIXI speichern. Es ist allerdings besser, nur die benötigten Teil der PixiJS-Bibliothek einzubinden, da webpack dann die Chance hat, im Web-Ordner kleinere JavaScript-Dateien zu erzeugen. Dies kann eine deutliche Reduktion der Ladezeiten der Web-App zur Folge haben.
  • Anstelle des GameLoop-Objektes könnte auch der PixiJS-Ticker verwendet werden. Dieser stellt jedoch nicht sicher, dass das Model garantiert 60 mal pro Sekunde aktualisiert wird. Wenn Sie irgendwann PixiJS-Animationen einsetzen möchten, müssen Sie den PixiJS-Ticker allerdings zusätzlich starten, da ansonsten die Animationen nicht animiert werden.
  • Das Root-Objekt der PixiJS-Anwendung wird erstellt. Die wichtigsten Argumente sind die Breite und Höhe der Bühne sowie das Canvas-Element, auf dem die Grafiken gezeichnet werden. (Wenn man der Anwendung kein Canvas-Element zuteilt, erstellt sie selbst eines, das man anschließend per JavaScript in das HTML-Dokument einfügen muss.) Darüber hinaus gibt es diverse weitere Optionen, wie z. B. die Festlegung, ob die Leinwand transparent ist, damit der HTML-Hintergrund durchscheint.[5]
  • Das Spiel wird mit Hilfe einer Methode init initialisiert, bevor die Game Loop gestartet werden kann. Das ist notwendig, da die Grafikobjekte, die später animiert werden sollen, zunächst erstellt werden müssen. In der ersten Praktikumsaufgabe wurde diese Tätigkeit per CSS (und nicht per JavaScript) erledigt.

game01.js

  • Der Ordner ball wurde in game umbenannt und die Datei ball01.js in game01.js unbenannt, da hier nicht nur das Ball-Objekt, sondern auch alle andere Aspekte des „Spiels“ „Ein Ball bewegt sich hin und her“ definiert werden.
  • Es wird die in der PixiJS-Bibliothek enthaltene Klasse Graphics importiert, um damit eine View für das Ball-Objekt zu zeugen.
  • Es gibt eine Initialisierungsfunktion (Initfunktion), die zu Beginn der Anwendung aufgerufen wird, um sie zu initialisieren.
  • Darüber hinaus gibt es auch noch eine Resetfunktion, die jedes Mal aufgerufen wird, wenn die Anwendung neu gestartet wird, z. B. weil ein neues Spiellevel begonnen werden soll. (Diese Funktion gab es im Gegensatz zur Initfunktion auch schon in der ersten Praktikumsaufgabe.) Diese Funktion ist für wiederkehrende Initialisierungaufgaben verantwortlich, wohingegen die Initialisierungsfunktion nur ein einziges Mal zu Spielbeginn aufgerufen wird. Um Code-Duplikationen zu vermeiden, ruft die Initialisierungsfunktion auch die Resetfunktion auf.
  • Das Modell der Bühne (v_stage) wird nur teilweise initialisiert. Die Breite und Höhe wird erst später von der Initfunktion festgelegt.
  • Das Ballobjekt v_ball wird erst durch die Resetfunktion initialisiert. Diese wird erstmals von der Init-Funktion aufgerufen und kann später von der Game Loop bei Spielstart oder -neustart damit zurückgesetzt werden.
  • Das Ballobjekt beinhaltet schon die y-Koordinate und die y-Geschwindigkeit des Balls. In der ursprünglichen Aufgabe fehlten diese Attribute noch. Sie wurden erst in der zweiten Aufgabe („Ball vertikal fliegen lassen“) eingeführt. Außerdem wurde der Durchmesser (d) durch den Radius (r) ersetzt.
  • Der Ankerpunkt des Balls wurde von der linken oberen Ecke der Eule ins Zentrum des Balls verschoben. Dies hat Auswirkungen auf die Implementierung der Kollisionserkennung und -behandlung.
  • Die Renderfunktion unterscheidet sich leicht. In der ursprünglichen Aufgabe wurden CSS-Attribute des animierten div-Elements verändert. Nun müssen die Attribute des PixiJS-Grafikobjekts, das in der Init-Funktion erzeugt wurde, an die aktuellen Modellwerte angepasst werden.

Die Initfunktion nimmt folgende Aufgaben wahr:

  • Breite und Höhe des Bühnenmodells werden an die Breite und Höhe der PixiJS-Anwendung angepasst.
  • Die Modelle des Spiels werden initialisiert.
  • Die View des Balls wird initialisiert und auf der PixiJS-Bühne platziert. Dies kann erst jetzt erfolgen, da das Application-Objekt der PixiJS-Anwendung erst jetzt zur Verfügung steht. Mit Hilfe der PixJS-Klasse Graphicswird zunächst ein Grafikobjekt erzeugt. Dieses wird mit Inhalt gefüllt (einem Kreis von bestimmter Farbe mit einem Rand in einer anderen Farbe) und zu guter Letzt zur PixiJS-Bühne (d. h. im Prinzip zum zugehörigen Canvaselement) hinzugefügt. Wenn sich später bestimmte Attribute des Grafikobjektes – wie z. B. die Position oder die Größe – ändern, wird die graphische Darstellung des Objektes auf dem Canvas automatisch von PixiJS angepasst. Die Änderung der Grafikattribute werden in der Funktion render vorgenommen. Dort wird derzeit allerdings nur die Position aus dem Model in die View übertragen. Andere Attribute der View ändern sich in dieser Anwendung nicht.

Aufgabe 2

Erstellen Sie eine erste Version der Lösung der Aufgabe 2, indem Sie die Lösung der Aufgabe 1 kopieren:

  • Kopieren Sie src/js/game/game01.js nach src/js/game/game02.js
  • Kopieren Sie src/js/app01.js nach src/js/app02.js und ersetzen Sie in dieser Datei './game/game01.js' durch './game/game02.js'.
  • Kopieren Sie src/index01.html nach src/index02.html und ersetzen Sie in dieser Datei <title>Ball02 (app01)</title> durch <title>Ball02 (app02)</title>. (Einen Verweis auf die JavaScript-Datei app02.js gibt es in dieser Datei nicht. Der korrekte Verweis wird von webpack automatisch beim Erstellen der Dateien des Web-Ordners injiziert.)
  • Starten Sie npm run watch neu, damit auch die neu erstellten Dateien automatisch von webpack übersetzt werden.

Lösen Sie Aufgabe 2 der ersten Praktikumsaufgabe mit Hilfe von PixiJS. Das heißt, ändern Sie Ihre Version von Aufgabe 2, die Sie gerade durch Kopieren erstellt haben so ab, dass Folgendes passiert: Lassen Sie den Ball nicht waagerecht, sondern in der horizontalen Mitte des Browserfensters senkrecht von Fensterrand zu Fensterrand fliegen. Anstelle einer Eule verwenden Sie bitte analog zu Aufgabe 1 einen farbigen „Ball“ mit Rand.

Vergessen Sie nicht auch die Kollisionserkennung und -behandlung für die beiden Bildschirmseiten top und botton zu implementieren.

Und denken Sie daran, npm run dev oder besser noch npm run watch zu verwenden. :-)

Aufgabe 2a

Erstellen Sie zunächst wie in Aufgabe 2 beschrieben eine Kopie der Lösung von Aufgabe 2 und nennen Sie sie 2a (index02a.html, app02a.js, game02a.js).

Ändern Sie nun die View des Balls: Anstelle eines farbigen Kreises stellen Sie bitte eine Eule oder einen Pinguin dar. Importieren Sie zunächst das gewünschte graphische Objekt in die Datei game02a.js:

import imgBall from '../../img/owl-150.png';

oder

import imgBall from '../../img/tux-150.png';

(Genau genommen weisen Sie webpack mit dieser Anweisung an, das Grafikobjekt im Ordner web/img zu erzeugen und die zugehörige URL in der Variablen imgBall zu speichern. Ohne webpack müssten Sie das Bild von Hand in diesem Ordner ablegen und dem Spriteobjekt – siehe unten – die korrekte URL übergeben.)

Darüber hinaus müssen Sie dafür sorgen, das von pixi.js nicht mehr die Klasse Graphics, sondern stattdessen die Klasse Sprite importiert wird.

Ändern Sie in der Initfunktion die Befehle zur Erstellung der View so ab, dass kein Kreis mehr gezeichnet, sondern das Bild der Eule oder des Pinguins dargestellt wird.

let
  ...
  v_ball_view = Sprite.fromImage(imgBall); // imgBall contains the URL of the image

In der Initfunktion wird nur noch der Ankerpunkt in die Mitte des Viewobjekts verschoben. Sie müssen die vier Befehle zur ERzeugung des Grafikobjektes also löschen. Dieses Objekt wird dann wieder zur PixiJS-Bühne hinzugefügt.

v_ball_view.anchor.set(0.5); // center the anchor
p_pixi_app.stage.addChild(v_ball_view);

Aufgabe 3

Erstellen Sie zunächst wie in Aufgabe 2 beschrieben eine Kopie der Lösung von Aufgabe 2a und nennen Sie sie 3 (index03.html, app03.js, game03.js).

Lösen Sie Aufgabe 3 der ersten Praktikumsaufgabe mit Hilfe von PixiJS: Lassen Sie der Ball von der Mitte des Browserfensters aus schräg über den Bildschirm fliegen. In x-Richtung soll er doppelt so schnell sein (200 Pixel/s) wie in y-Richtung (100 Pixel/s). (Diesmal sollten Sie gleich eine Eule oder einen Pinguin fliegen lassen und nicht erst einen Kreis.)

Aufgabe 3a

Erstellen Sie zunächst wie in Aufgabe 2 beschrieben eine Kopie der Lösung von Aufgabe 3 und nennen Sie sie 3a (index03a.html, app03a.js, game03a.js).

Einen Nachteil hat die Lösung der Aufgaben 2a und 3: Das Bild des Vogels wird mittels Sprite.fromImage synchron vom Web-Server geladen. Das heißt, solange bis das Bild geladen wurde, ist die Anwendung „eingefroren“. Bei einem kleinen Bild ist das sicher kein Problem, wenn aber eine Vielzahl von oder große Medien geladen werden müssen – wie dies bei realen Web-Anwendungen der Fall ist –, ist dies sicher keine Option mehr.

Nehmen Sie an Ihrer Kopie 3a folgende Änderung vor: Laden Sie das Bild des Vogels asynchron mit Hilfe eines PixiJS-Loader-Objektes (MDN web doc: PIXI.loaders.Loader , GitHub: resource-loader)[6][7].

Zunächst ein wenige Theorie:

Asynchronität in EcmaScript 2017

Eine wesentliche Änderung, die sich wegen der Asynchronität ergibt, ist, dass der Ladevorgang später endet als die Ausführung der Initialisierungsfunktion. Wenn man direkt im Anschluss an die Ausführung der Initialisierungsfunktion die Game Loop startet, ist die Wahrscheinlichkeit groß, dass die Bilder noch gar nicht geladen wurden und die Anwendung daher abstürzt. Abhilfe schafft hier der Einsatz von modernen asynchronen JavaScript-Anweisungen:

Diese Konstrukte wurden eingeführt, da man in einem klassischen ES5-Programm häufig den Wald vor lauter Bäumen, d. h. den Code vor lauter Callback-Funktionen nicht mehr gesehen hat, wenn eine Vielzahl von asynchronen Anweisungen benötigt wurden.

Mit ES 2015 wurde die globale Klasse Promise eingeführt. Ein Promise-Objekt führt eine Aktion, wie das Laden eines Bildes oder das Warten auf ein Timer-Ereignis, asynchron aus. Sobald die Aktion erfolgreich beendet wird, führt sie eine Callback-Funktion aus, die ihr in der Methode then übergeben wurde. Der Callback-Funktion wird das Ergebnis der asynchronen Aktion als Ergebnis übergeben, so dass sie damit weiterarbeiten kann. Falls diese Funktion wieder ein Promise-Objekt als Ergebnis liefert, kann man den nächsten asynchronen Befehl mittels eines weiteren Aufrufs der Methode then anfügen. Etc. pp. Auf diese Weise konnte man eine Abfolge von asynchronen Anweisungen zumindest linear notieren:

myPromise
  .then(function(p_value) { ...})
  .then(function(p_value) { ...})
  .then(function(p_value) { ...})

Wenn man die kürzere Arrow-Schreibweise für Funktionen verwendet, die mit ES 2015 eingeführt wurde (MDN web doc: Pfeilfunktionen), wird der oder noch etwas kompakter und damit lesbarer:

myPromise
  .then(p_value => { ...})
  .then(p_value => { ...})
  .then(p_value => { ...})

Allerdings ist das auch nicht sonderlich schön zu lesen, vor allem wenn man umfangreiche asynchrone Funktionen zu definieren hat. Und jede dieser Funktionen muss ein eigenes Promise-Objekt erstellen, was den Code auch nicht gerade lesbarer macht.

Daher wurden in ES 2017 die asynchronen Funktionen eingeführt:

async function myFunction(...)
{ ...}

In dieser Funktionen werden Anweisungen geschrieben, wie in jeder anderen Funktion auch. Und diese Anweisungen werden der Reihe nach abgearbeitet. Allerdings gibt es einen fundamentalen Unterschied. Der Funktionsrumpf wird asynchron ausgeführt. Das Ergebnis der Funktion ist ein (implizites) Promise-Objekt, dessen then-Funktion erst aufgerufen wird, sobald alle asynchronen Befehle im Rumpf der Funktion erfolgreich beendet wurden.

Da heißt, bei Aufruf der Funktion wird diese sofort wieder beendet. Der Browser wird durch die Funktion nicht blockiert, wie es z. B. beim synchronen Laden eines Bildes der Fall wäre. Die im Rumpf der Funktion enthaltenen Befehle werden trotzdem der Reihe nach ausgeführt, aber asynchron.

Wenn Sie die Funktion init in der Datei app01.js näher betrachten, fällt Ihnen auf, das sie als async-Funktion definiert wurde. In ihrem Rumpf befinden sich neben drei Standard-Anweisungen auch die Anweisung await wait(500);. Die Funktion wait wurde von mir in der Datei src/lib/wk/util/wait.js definiert:

function wait(p_time)
{ return new Promise(function(p_resolve)
                     { setTimeout(p_resolve, p_time); }
                    )
}

(In Wirklichkeit habe ich die modernere Arrow-Schreibweise verwendet, aber das Ergebnis ist dasselbe.)

Die Funktion wait erstellt ein Promise-Objekt, das die Funktion setTimeout ausführt ([https://developer.mozilla.org/de/docs/Web/API/WindowTimers/setTimeout MDN web doc: WindowTimers.setTimeout()]). Diese Funktion wartet eine gewisse Zeit p_time und ruft dann eine Callback-Funktion auf. Das ist die typische ES5-Vorgehensweise zur Behandlung von asynchronen Aktionen. In diesem Fall wird die Callback-Funktion p_resolve aufgerufen. Das ist eine Funktion, die dem Promise-Objekt mitteilt, dass die asynchrone Aktion erfolgreich beendet wurde (und das Promise-Objekt nun die then-Funktion ausführen könnte).

Seit ES 2017 kann man in async-Funktionen den await-Operator verwenden, um das Ergebnis eines Promise-Objekts zu verarbeiten. Das ist viel eleganter, als mit den (auch schon wieder veralteten) then-Funktionen. Sie schreiben einfach den Befehl await wait(500); in den Rumpf Ihrer async-Funktion. Dann wird die (asynchrone) Verarbeitung des Rumpfes dieser funktion um 500 ms (d. h. um eine halbe Sekunde) unterbrochen, bevor der nächste Befehl abgearbeitet wird.

Asynchrones Laden von Bildern für PixiJS

PixiJS stellt das Object PIXI.loader zur Verfügung (PixiJS API Documentation: PIXI.loaders.Loader), mit dem Sie Bilder (oder auch andere Medien) asynchron laden können. Die Bilder werden dabei gleich so gespeichert, dass sie problemlos von PixiJS weiterverarbeitet werden können.

Importieren Sie diesen Loader in Ihre Datei app03.js. Ersetzen Sie

import {Application} from 'pixi.js';

durch

import {loader, Application} from 'pixi.js';

Fügen Sie außerdem folgenden Import-Befehl in die Datei app03.js ein:

import imgBall from '/img/tux-150.png';

Diesen Befehl kennen Sie schon. Er befindet sich noch in der Datei game03a.js. Dort sollten Sie ihn löschen (In der Datei game03.js dürfen sie ihn dagegen nicht löschen.) Wie Sie wissen, bewirkt dieser Import-Befehl, dass die URL des Bildes /img/tux-150.png in der Variablen imgBall gespeichert ist (und das Bild in den Web-Ordner kopiert wird).

Fügen Sie nun vor der Initfunktion den folgenden Befehl ein.

loader.add('imgBall', imgBall);

Damit teilen Sie dem Loader mit, welches Bild er später asynchron laden soll. Als Argumente erwartet einen Namen, mit dem später auf das Bild zugegriffen werden kann, sowie die URL des Bildes, under der er es im Web-Auftritt findet. Es ist durchaus üblich, eine Vielzahl von Medien auf einmal (d. h. parallel) zu laden:

p_pixi.loader
  .add(...)
  .add(...)
  .add(...)
  .add(...)...

Jetzt könnten Sie die Bilder mittels loader.load(geeigneteCallbackFunktion) asynchron laden. Da PixiJS keine Proimises unterstützt, müsste man hier auf die alte ES-5-Methode zurückgreifen.

Unter /wk_pixi/loader/asyncLoader finden Sie eine kleine Funktion, die das eleganter mit Hilfe eines Promise-Objektes macht. Importieren Sie diese Funktion:

import loadResources from '/wk_pixi/loader/asyncLoader';

Jetzt können Sie die Ressourcen ganz elegant mit await laden. Fügen Sie folgenden Code als erste Zeile in den Rumpf der Initfunktion ein:

const c_resources = await loadResources();

Damit wird die asynchrone Ausführung der Initfunktion solange unterbrochen, bis alle Bilder geladen wurden. Danach wird der nächste Befehl ausgeführt. Das ist der Befehl, der das Spiel initialisiert:

game.init(c_pixi_app);

Ersetzen Sie ihn durch:

game.init(c_pixi_app, c_resources);

Das heißt, übergeben Sie der Initfunktion nicht nur das PixiJS-App-Objekt, sondern auch die Ressourcen (das Bild), das Sie geladen haben. Jetzt müssen Sie nur noch die Initfunktion in der Datei game03a.js anpassen.

Ersetzen Sie dort in der let-Anweisung

v_ball = {},
v_ball_view =  ...;

durch

v_ball = {},
v_ball_view;

Das heißt, das Ball-View-Objekt wird nicht mehr hier erzeugt, sondern erst in der Initfunktion. Fügen Sie zu diesem Zweck den Parameter p_resources in die Parameterliste der Initfunktion ein:

function init(p_pixi_app, p_resources)

In diesem Parameter werden der Funktion die Ressourcen (Bilder) übergeben, die Sie zuvor asynchron) geladen haben. Damit können Sie jetzt die View erzeugen.

Fügen Sie vor die beiden Befehle

v_ball_view.anchor.set(0.5),
p_pixi_app.stage.addChild(v_ball_view);

den Befehl

v_ball_view = new Sprite(p_resources['imgBall'].texture);

Es wird, wie auch schon in den Aufgaben 2a und 3, ein Sprite-Objekt erstellt. Diesmal wird jedoch das Bild nicht aus einer Datei gelesen (Sprite.fromImage...)) sondern aus dem vom Loader erstellten Ressourcen-Objekt unter dem Namen 'imgBall' extrahiert. Warum man nicht direkt das Objekt p_resources['imgBall'] verwenden kann, sondern dessen Textur verwenden muss, ist eines von den unergründlichen Geheimnissen von PixiJS.

BTW: Man bräuchte das Objekt p_resources gar nicht als Argument an die Initfunktion übergeben. Man könnte auch in der Datei game3a.js, auch wieder den Loader importieren

import {loader, Sprite} from 'pixi.js';

und dann mittels load.resources auf dieses Objekt zugreifen. Allerdings ist die Kommunikation zweier Module über ein globales Objekt (wie das Objekt loader) deprecated, da man nicht weiß, wann und ob überhaupt das darin enthaltene Ressourcenobjekt sauber initialisiert wurde. Eine direkte Kommunikation mittels Parametern und Argumenten ist dem vorzuziehen.

Aufgabe 3b

Erstellen Sie zunächst wie in Aufgabe 2 beschrieben eine Kopie der Lösung von Aufgabe 3a und nennen Sie sie 3b (index03b.html, app03b.js, game03b.js).

Erweitern Sie Ihre Lösung von Aufgabe 3 so, dass 5 unterschiedliche Vögel auf unterschiedlichen Routen über die Bühne fliegen. (Hier benötigen Sie Ihr Wissen über den Umgang mit Arrays: siehe Praktikumsaufgaben EcmaScript01 und EcmaScript02.)

Gehen Sie dazu folgendermaßen vor:

  • Laden Sie in der Datei app03b.js die beiden Bilder /img/owl-150.png und /img/owl-150.png asynchron mittels des PixiJS-Loaders.
  • Bearbeiten Sie nun die Datei game03b.js*
  • Legen Sie in einer Variablen oder Konstanten v_ball_nr die Anzahl der Vögel fest, die fliegen sollen. Initialisieren Sie diese mit der Zahl 5.
  • Ersetzen Sie v_ball und v_ball_view durch v_balls und v_ball_views. Diese Variablen sollen jeweils ein Array mit v_ball_nr Model-Objekten bzw. v_ball_nr zugehörigen View-Objekten beinhalten. Die Variable v_ball_views wird in der Initfunktion initialisiert und die Variable v_balls in der Resetfunktion.
  • Initialisieren Sie das Array v_ball_views in der Initfunktion mit Hilfe einer Schleife und des Push-Befehls. Sie müssen v_ball_nr Sprite-Objekte erzeugen und ins Array einfügen. Als Bild wählen Sie per Zufallsentscheidung (Math.random() < 0.5) jeweils die Eule oder Tux.
  • Initialisieren Sie das Array v_balls in der Resetfunktion mit Hilfe einer Schleife und des Push-Befehls. Der Radius aller fünf Bälle sei 75. Die Position wählen Sie zufällig Math.random() innerhalb der Bühne, die Geschwindigkeit in $x$- und $y$-Richtung ebenfalls zufällig jeweils zwischen 100 und 400.
  • Sorgen Sie in der Updatefunktion mit Hilfe einer Schleife dafür, dass all 5 Objekte im Array v_balls aktualisiert werden.
  • Sorgen Sie in der Renderfunktion mit Hilfe einer Schleife dafür, dass all 5 Objekte im Array v_ball_views rerendert werden.

Aufgaben 4 bis 10

Nun können und sollten Sie zur Übung die Aufgaben 4 bis 10 der ersten Praktikumsaufgabe ebenfalls mit Hilfe von PixiJS lösen. Laden Sie dabei die Bilder wieder asynchron und verwenden Sie JSON zur Konfiguration. Sie sollten ruhig auch mal mehr als einen Vogel animieren, z. B. indem Sie mehrere Eulen hintereinander die Wand entlang fliegen lassen.

Quellen

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