MMProg: Praktikum: WiSe 2017/18: Ball02: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
(21 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 8: Zeile 8:
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.  
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 eingeführt.<ref>[https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API MDN web docs: Canvas API]</ref>
Mit HTML5 wurde das Canvas-Element (Leinwand-Element) eingeführt.<ref>[https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API MDN web docs: Canvas API]</ref>
In einem Canvas-Element können Grafiken gezeichnet werden. Aktuelle Browser unterstützen üblicherweise sowohl den <code>CanvasRenderingContext2D</code>  
„Auf“ einem Canvas-Element können Grafiken gezeichnet werden. Aktuelle Browser unterstützen üblicherweise sowohl den <code>CanvasRenderingContext2D</code>  
zum Erstellen von 2D-Grafiken<ref>[https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D MDN web docs: CanvasRenderingContext2D]</ref>  
zum Erstellen von 2D-Grafiken<ref>[https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D MDN web docs: CanvasRenderingContext2D]</ref>  
als auch <code>WebGL</code> zum Erstellen von 3D-Grafiken (auf Basis von [[OpenGL]]) <ref>[https://developer.mozilla.org/de/docs/Web/API/WebGL_API/Tutorial/Einf%C3%BChrung_in_WebGL MDN web docs: Einführung in WebGL]</ref>. Die Erstellung von Grafiken mit WebGL ist wesentlich effizienter als die
als auch <code>WebGL</code> zum Erstellen von 3D-Grafiken (auf Basis von [[OpenGL]])<ref>[https://developer.mozilla.org/de/docs/Web/API/WebGL_API/Tutorial/Einf%C3%BChrung_in_WebGL MDN web docs: Einführung in WebGL]</ref>. Die Erstellung von Grafiken mit WebGL ist wesentlich effizienter als die
Erstellung von Grafiken mit dem <code>CanvasRenderingContext2D</code>, zumindest dann wenn, eine Grafikkarte zur Verfügung steht, deren Prozessor
Erstellung von Grafiken mit dem <code>CanvasRenderingContext2D</code>, zumindest dann wenn, eine Grafikkarte zur Verfügung steht, deren Prozessor
([[GPU]]) OpenGL nativ unterstützt. Die ist insbesondere dann von Bedeutung, wenn Animationen erstellt werden, {{dh}}, wenn 60 mal pro Sekunde (also
([[GPU]]) OpenGL nativ unterstützt. Dies ist insbesondere dann von Bedeutung, wenn Animationen erstellt werden, {{dh}}, wenn 60 mal pro Sekunde (also
alle 16,7 ms) eine neue Grafik im Canvas angezeigt werden soll.
alle 16,7 ms) eine neue Grafik auf dem Canvas angezeigt werden soll.


PixiJS ist als Ersatz für den <code>CanvasRenderingContext2D</code> gedacht. Die Bibliothek stellt im Wesentlichen dieselben Grafik-Befehle  
PixiJS ist als Ersatz für den <code>CanvasRenderingContext2D</code> 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  
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  
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.
Performanz-Einbruch zur Folge.
Zeile 23: Zeile 23:
==Aufgaben==
==Aufgaben==


Laden Sie das leere Projekt [https://glossar.hs-augsburg.de/beispiel/tutorium/es6/empty/WK_Ball02_Empty WK_Ball02_Empty] auf Ihren Rechner.
Laden Sie das leere Projekt [https://glossar.hs-augsburg.de/beispiel/tutorium/es6/empty/WK_Ball02_empty WK_Ball02_empty] auf Ihren Rechner.
'''Installieren Sie aber nicht die Node.js-Module''', das machen Sie später. Sie finden das leere Projekt im  
'''Installieren Sie aber nicht die Node.js-Module''', das machen Sie später. Sie finden das leere Projekt im  
Repository-Pfad <code>https://glossar.hs-augsburg.de/beispiel/tutorium/es6</code> im Unterordner <code>empty</code>.
Repository-Pfad <code>https://glossar.hs-augsburg.de/beispiel/tutorium/es6</code> im Unterordner <code>empty</code>.
Zeile 29: Zeile 29:
Erstellen Sie ein neues Projekt <code>praktikum02</code> und kopieren Sie die Ordner <code>src</code> und <code>web</code> (samt Inhalt)
Erstellen Sie ein neues Projekt <code>praktikum02</code> und kopieren Sie die Ordner <code>src</code> und <code>web</code> (samt Inhalt)
sowie alle Dateien, die Sie im Wurzelverzeichnis des Projektes <code>WK_Ball02_Empty</code>
sowie alle Dateien, die Sie im Wurzelverzeichnis des Projektes <code>WK_Ball02_Empty</code>
finden mittels <code>Crtl-/Apfel-C</code> <code>Crtl-/Apfel-V</code> in Ihr eigenes Projekt. (Die Frage, ob WebStorm seinen eigenen File Watcher zum
finden, mittels <code>Ctrl-/Apfel-C</code> <code>Ctrl-/Apfel-V</code> in Ihr eigenes Projekt. (Die Frage, ob WebStorm seinen eigenen File Watcher zum
Übersetzen von ES6-Code in ES5-Code verwenden soll, beantworten Sie bitte mit „No“. Das erledigt Webpack für Sie.)
Übersetzen von ES6-Code in ES5-Code verwenden soll, beantworten Sie bitte mit „No“. Das erledigt Webpack für Sie.)
Sie können Ihr Projekt zur Übung auch im Subversion-Repository speichern. Das ist aber nicht so wichtig.


Nun können Sie in Ihrem eigenen Projekt die benötigten Node.js-Module installieren: <code>npm i</code>.
Nun können Sie in Ihrem eigenen Projekt die benötigten Node.js-Module installieren: <code>npm i</code>.
Sie können Ihr Projekt zur Übung auch im Subversion-Repository speichern. Das ist aber nicht so wichtig.


In Ihrem Projekt finden Sie wiederum mehrere Web-Anwendungen: <code>index01.html</code> verwendet die gepackte Version von <code>app01.js</code>,
In Ihrem Projekt finden Sie wiederum mehrere Web-Anwendungen: <code>index01.html</code> verwendet die gepackte Version von <code>app01.js</code>,
Zeile 42: Zeile 42:
Am einfachsten ist es, wenn Sie jeweils die Lösung der vorangegangenen Aufgabe kopieren und diese Kopie dann weiterentwickeln.
Am einfachsten ist es, wenn Sie jeweils die Lösung der vorangegangenen Aufgabe kopieren und diese Kopie dann weiterentwickeln.


<!--
===Aufgabe 1===
===Aufgabe 1===


Lassen Sie die Eule horizontal vom linken bis zum rechten Fensterrand des Browsers fliegen.  
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 <code>WK_Ball02_Empty</code> 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 <code>WK_Ball01</code>, die sich durch die Verwendung von PixiJS
ergeben, klar zu machen.
 
'''<code>app1.js</code>'''


Tipp: http://ryanve.com/lab/dimensions/
* Es wird zusätzlich die JavaScript-Bibliothek <code>pixi.js</code> importiert.
* Anstelle von <code>GameLoop.js</code> wird <code>GameLoopPixi.js</code> importiert. Dieses Modul verwendet die  PixiJS-eigene GameLoop, stellt aber weiterhin sicher, dass Modell-Updates nach Möglichkeit stets genau 60 mal pro Sekunde erfolgen.<ref>vgl. [https://isaacsukin.com/news/2015/01/detailed-explanation-javascript-game-loops-and-timing Isaac Sukin: A Detailed Explanation of JavaScript Game Loops and Timing]</ref>. Außerdem verzögert dieses Modul den Start der Modellberechnung um ein paar Frames, da sich PixiJS hier anscheinend teilweise etwas „warmläuft“ und diese Frames deutlich länger als 16,7 Millisekunden dauern können. Ohne die Verzögerung der Modellberechnung würde die Web-Anwendung etwas „ruckelig“ starten.
* Ein rudimentäres Modell der Bühne (<code>stage</code>) wird bereits in <code>app1.js</code> und nicht erst in <code>game1.js</code> definert, da es bereits bei der Definition des PixiJS-Applikations-Objekts benötigt wird.
* 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 Spiel wird mit Hilfe einer Methode <code>init</code> initialisiert, bevor die Game Loop gestartet weren kann. Das ist notwendig, da die Grafikobjekte, die später animiert werden sollen, zunächst erstellt werden müssen. In der [[MMProg: Praktikum: WiSe 2017/18: GameLoop01|ersten Praktikumsaufgabe]] wurde diese Tätigkeit per CSS (und nicht per JavaScript) erledigt.


Berücksichtigen Sie, dass die Eule 150 Pixel breit ist und das der Ankerpunkt im linken oberen Eck des zugehörigen Bildes liegt.  
'''Anmerkung''': Das Modul <code>pixi.js</code> wird wie alle anderen Module auch mittels <code>npm i</code> geladen und im Ordner <code>node_modules</code> abgelegt.
Sie müssen daher im Eulen-Objekt auch die Breite der Eule speichern und diese Breite bei der Kollissionserkennung
Allerdings ist WebStorm derzeit etwas buggy. Wenn ein Modulname auf <code>.js</code> endet, findet Webstorm das Modul im Ordner <code>node_modules</code> nicht.
und -behandlung berücksichtigen.
Das hat zur Folge, dass on WebStorm für jede PixiJS-Methode eine Warnung ausgegeben wird, dass diese (angeblich) nicht exsitiere. Als Workaround habe ich einfach die Source-Datei
<code>pixi.sj</code> dupliziert und in den Ordner <code>src/js</code> gelegt. Schön ist das nicht, aber die Anzahl der Warnungen wird dadurch drastisch reduziert
(außer beim ersten Speichern dieser Datei im Repository). Eine andere Aufgabe hat die Datei <code>src/js/pixi.js</code> nicht.


Beachten Sie bitte, dass Sie zur Lösung dieser Aufgabe einfach die Datei <code>game01.js</code> anpassen müssen.
'''<code>game1.js</code>'''
Lassen Sie sich durch den Code der Datei <code>owl.js</code> aus dem Beispiel <code>WK_GameLoop02</code> inspirieren.
(Sie können auch <code>owl_interpolate.js</code> verwenden, aber das verwirrt Sie vermutlich zurzeit mehr, als dass es
Inhen nützt.) Eine Reset-Funktion, wie sie im genannten Beispiel definiert wird, brauchen Sie nicht zu implementieren,
da Sie das „Spiel“ nicht mit Hilfe von irgendwelchen Buttons anhalten und zurücksetzen werden. Dies gilt auch für alle folgenden
Aufgaben.


Vergessen Sie <code>grunt</code> oder besser noch </code>grunt watch</code> nicht. :-)
* 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 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.
* 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.
* Der Ankerpunkt des Balls wurde von der linken obren Ecke der Eule ins Zentrum des Balls verschoben. Dies hat Auswirkungen auf die Implementierung der Kollissionserkennung 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 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===


Lassen Sie die Eule nicht waagerecht, sondern in der horizontalen Mitte des Browserfesnters senkrecht von Fenstrrand zu Festerrand fliegen.
Lösen Sie Aufgabe 2 der [[MMProg: Praktikum: WiSe 2017/18: GameLoop01#Aufgabe 2|ersten Praktikumsaufgabe]] mit Hilfe von PixiJS:
 
''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.
Um die Aufgabe zu lösen, müssen Sie für die Eule zusätzlich eine y-Position und eine y-Geschwindigkeit definieren. Die Berechnung der aktuellen y-Position funktioniert
analog zur Berechnung der aktuellen x-Position.  


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 Kollissionserkennung und -behandlung für die beiden Bildschirmseiten <code>top</code> und <code>botton</code>
zu implementieren.
zu implementieren.


Und Sie müssen natürlich die Render-Funktion anpassen, sodass die aktuelle y-Position beim Rendern auch berücksichtigt wird.
Und denken Sie daran, <code>grunt</code> oder besser noch <code>grunt watch</code> zu verwenden. :-)


===Aufgabe 3===
===Aufgabe 2a===


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)
Ändern Sie nun die View des Balls: Anstelle eines farbigen Kreises stellen Sie bitte eine Eule oder einen Pinguin dar.
wie in y-Richtung (100 Pixel/s).
Importieren Sie zunächst das gewünschte graphische Objekt:


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


Setzen Sie die x-Start-Position der Eule auf <code>-100</code> und starten Sie die Web-App. Den Effekt, den Sie sehen, nennt
(Genaugenommen weisen Sie Webpack mit dieser Anweisung an, das Grafikobjket im Web-Bereich zu erzeugen und die zugehörige URL in der
man [[Kollisionserkennung und -behandlung|Penetration]]. Die Eule hängt in der Wand fest, da Sie in einem Schritt nicht den Kollissionsbereich
Variablen <code>sprite</code> zu speichern.)
(die linke Wand) verlässt. Daher besteht die Kollission fort und im nächsten Schritt ändert sie wieder ihre Flugrichtung.


Verbessern Sie das, indem Sie die Kollisionserkennung und -behandlung verbessern:
Ändern Sie in der Initialiseirungsfunktion 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">
<source lang="javascript">
if (v_owl.x <= v_stage.left)
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_owl.vx = Math.abs(v_owl.vx);
 
}
p_stage_view.addChild(v_ball_view);
if (v_owl.x >= v_stage.right - v_owl.width)
{
  v_owl.vx = -Math.abs(v_owl.vx);
}
</source>
</source>


Passen Sie die Kollisionserkennung und -behandlung in y-Richtung analog an.  
===Aufgabe 3===
 
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).''
(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.
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.


Sie können und sollten sogar noch einen Schritt weiter gehen und die Eule
Sie sollten die Aufgabe zunächst einmal auf Basis Ihrer Erkenntnisse von Aufgabe 2a lösen.  
wieder zurück auf die Bühne schieben, wenn Sie in die Wand eingedrungen ist.
In die Wand einzudringen ist zwar physikalisch nicht möglich, aber leider in
einer Simulation der physikalischen Welt nur schwer zu vermeiden, da die
Position nur alle 16,7 ms berechnet wird. War die Eule zu einem Zeitpunkt
noch vor der Mauer, kann Sie beim nächsten Schritt schon drinnen stecken.
Insbesondere bei sehr schnellen Objekten kann das dann zu PEnetrations-Effekten führen.


Also schieben Sie die Eule besser zurück auf die Bühne.
Solbald diese Lösung funktioniert, sollten Sie folgene Änderung vornehmen:
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
]</ref><ref>[http://pixijs.download/dev/docs/PIXI.loaders.Loader.html PixiJS API Documentation: PIXI.loaders.Loader]</ref>.


Eine wesentliche Änderung, die sich wegen der Asynchronität ergibt, is, 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 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
<source lang="javascript">
<source lang="javascript">
if (v_owl.x <= v_stage.left)
game.init(PIXI, c_stage, c_pixi_app.stage);
{
new GameLoopPixi(c_pixi_app.ticker, game.update, game.render).start();
  v_owl.x = v_stage.left;
</source>
  v_owl.vx = Math.abs(v_owl.vx);
durch
}
<source lang="javascript">
if (v_owl.x >= v_stage.right - v_owl.width)
game.init
{
( PIXI, c_stage, c_pixi_app.stage,
  v_owl.x = v_stage.right - v_owl.width;
  // callback function: if the game has been initialized, start it.
  v_owl.vx = -Math.abs(v_owl.vx);
  function()
}
  { new GameLoopPixi(c_pixi_app.ticker, game.update, game.render).start(); }
);
</source>
</source>


Passen Sie die Kollisionserkennung und -behandlung in y-Richtung wiederum analog an.  
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.


===Aufgabe 5===
Nun muss noch die Initialisierungsfunktion in der Datei <code>game3.js</code> entsprechend angepasst werden.


Ändern Sie die Kollisionserkennung und -behandlung so ab, dass die Eule ausgehend von der linken oberen Fensterecke
Zunächst wird sie um einen Callback-Parameter <code>f_ready</code> erweitert:
im Uhrzeigersinn sich immer entlang des Fensterrandes bewegt.


Beachten Sie bitte: Hier ist es besonders wichtig, dass Sie die Eule wieder  auf die Bühne zurückbewegen, wenn sie mit einer
<source lang="javascript">
Wand kollidiert. Ansonsten verschwindet die Eule schnell im Nirgendwo.
function init(p_pixi, p_stage, p_stage_view, f_ready)
</source>


(Anmerkung: Dies war einmal eine Aufgabe im Rahmen des Prüfungspraktikums, wobei die Lösung zur 3. Aufgabe vorgegeben war.)
In diesem Parameter wird der Initialisierungsmethode die zuvor definierte Callback-Funktion übergeben, die aufgerufen weren muss, sobald der Initalisierungsvorgang
vollständig abgearbeitet wurde.


===Aufgabe 6===
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. 


Kopieren Sie diesmal nicht die Lösung von Aufgabe 5, sondern von Aufgabe 4. Im folgenden arbeiten Sie
Die benötigten Medien (hier: das Bild der Eule) werden künftig mit Hilfe eines PixiJS-Loaders '''asynchron''' geladen:
wieder mit der normalen Kollissionserkennung und -behandlung. Positionieren Sie die Eule allerdings wieder im linken oberen Eck
(in Aufgabe 4 hatten Sie sie außerhalb der Bühne platziert).


Die neue Position der Eule berechnet man mit Hilfe der Geschwindigkeit (velocity). Doch auch die
<source lang="javascript">
Geschwindigkeit kann sich ändern. Dazu benötigt man die Beschleunigung (acceleration).
p_pixi
    .loader
    .add('sprite', sprite)
    .load(function(p_loader, p_resources)
          {
           
            f_ready();
          }
    );
</source>


Fügen Sie zu Ihrer Eule zwei weitere Attribute <code>ax</code> (Beschleunigung in x-Richtung) und
Der Pixi-Loader <code>p_pixi.loader</code> wird zunächst angewiesen, das Medium, dessen URL in der Variablen
<code>ay</code> (Beschleunigung in y-Richtung) hinzu. Setzen Sie die Initialwerte auf
<code>sprite</code> steht, zu laden und unter dem (sehr einfaltsreichen) Namen <code>'sprite'</code> in einem JavaScript-Objekt zu speichern.
<code>400</code> (Pixel pro Sekunde) bzw. <code>200</code> (Pixel pro Sekunde). Das heißt, Sie möchten,
Es ist durchaus üblich, eine Vielzahl von Medien auf einmal ({{dh}} parallel) zu laden:
dass die Eule in jeder Sekunde um 400 bzw. 200 Pixel pro Sekunde mehr zurücklegt als zuvor.


Wenn Sie jetzt die Web-App starten, beschleunigt die Eule allerdings noch nicht.
<source lang="javascript">
p_pixi.loader.add(...).add(...).add(...).add(...)...
</source>


In Ihrem Code wird die Position 60 mal pro Sekunde mit Hilfe des folgenden Codes aktualisiert:
oder auch


<source lang="javascript">
<source lang="javascript">
v_owl.x += v_owl.vx * p_frac_s;
p_pixi.loader.add(...);
v_owl.y += v_owl.vy * p_frac_s;
p_pixi.loader.add(...);
p_pixi.loader.add(...);
p_pixi.loader.add(...);
...
</source>
</source>


Die Geschwindigkeit wird zur Position hinzuaddiert. Allerdings ist die Geschwindigkeit
Zu guter Letzt wird die Methode <code>p_pixi.loader.load(...)</code> aufgerufen. Diese startet alle erwünschten Ladevorgänge und führt,  
in Pixeln pro Sekunde angegeben. Die Modellaktualisierung passiert jedoch alle $0,0167$
sobald alle Ressourcen geladen wurden, die ihr übergebene Callback-Funktion aus. Dieser Callback-Funktion werden vom Pixi-Loader
Sekunden. In dieser Zei bewegt sich die Eule nur um das $0,0167$-fache (= $1,67$%) der Sekundengeschwindigkeit weiter.
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.


Auf genau dieselbe Weise wird die aktuelle Geschwindigkeit mit Hilfe der Beschleunigung
Innerhalb dieser Funktion kann nun das View-Objekt des Balls erstellt und in der PixiJS-Bühne gespeichert werden.
berechnet:
Fügen Sie daher vor dem Befehl <code>f_ready();</code> folgende Befehle ein:


<source lang="javascript">
<source lang="javascript">
v_owl.vx += v_owl.ax * p_frac_s;
v_ball_view = new p_pixi.Sprite(p_resources['sprite'].texture);
v_owl.vy += v_owl.ay * p_frac_s;
v_ball_view.anchor.set(0.5); // center the anchor
 
p_stage_view.addChild(v_ball_view);
</source>
</source>


Fügen Sie diesen Code in Ihre Model-Update-Funktion ein und starten Sie die Eule erneut.
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,  
Wenn Sie jetzt Ihre We-App laufen lassen, stellen Sie fest, dass die Eule zunächst beschleunigt,
ohne dass die zugehörige Textur mehrfach vom Server geladen werden muss.
nach eine Kollission aber wieder abbremst. NAch der nächsten Kollission beschleunigt sie wieder.
 
Ganz wichtig ist es, als '''letzten Befehl''' der soeben definierten Callback-Funktion die Callback-Funktion <code>f_ready</code> auszuführen
(also mittels <code>f_ready();</code> aufzurufen). Anderenfalls wird die Game Loop nie gestartet und die Web-Anwendung verharrt im Initialzustand.


Um diesen Effekt zu vermeiden, müssen Sie jedes mal, wenn Sie bei der Kollissionsbehandlung das Vorzeichen
Wenn Sie schon dabei sind, sollten Sie gleich noch eine zweite Unsauberkeit eliminieren. Die Konfiguration der Modell-Objekte <code>v_stage</code>
der Geschwindigkeit ändern, das Vorzeichen der zugehörigen Beschleunigung analog ändern.
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.


Beispielsweise mus der Code
'''Tipp''': Um die Anzahl der WebStorm-Warnings zu reduzieren, können Sie direkt vor den JSON-Import-Befehl zusätzlich folgenden
Import-Befehl einfügen:


<source lang="javascript">
<source lang="javascript">
v_owl.vx = -Math.abs(v_owl.vx);
import '../../json/init3.js';
</source>
</source>


um
Die Datei <code>init3.js</code> enthält keinerlei ausführbaren Code, sondern nur einen Kommentar, der von Webpack spätestens dann entfernt wird,
wenn die Ausgabedatei <code>app3.bundle.js</code> mittels <code>grunt compress</code> komprimiert wird. Der Kommentar hat es allerdings in sich.
Er beschreit im JSDoc-Format<ref>[http://usejsdoc.org/ JSDoc]</ref> den Inhalt der JSON-Datei. Dieser Kommentar wird von WebStorm interpretiert, um
die Anzahl der Warnings zu reduzieren.
 
===Aufgabe 3a===
 
Erweitern Sie Ihre Lösung von Aufgabe 3 so, dass 5 unterschiedliche Vögel auf unterschiedlichen Routen über die Bühne fliegen.
In der Datei <code>src/json/init3a.json</code> finden Sie die zuanimierenden Objekte. Anstelle eines Objektes <code>ball</code> ist
hier ein Array <code>balls</code> definiert, das fünf Ball-Objekte enthält. Jedes dieser Objekte hat neben den bereits bekannten
Attributen ein weiteres Attribut <code>img</code>, das beschreibt, mit welcher Textur das Objekt visualisiert werden soll.
 
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>
(nicht <code>v_ball</code>!). Definieren Sie außerdem eine Variable <code>v_ball_views</code> und initialisieren Sie diese mit einem leeren Array.
In diesem werden anschließend von der Initialisierungsmethode die zugehörigen View-Objeke abgelegt.
 
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">
v_owl.ax = -Math.abs(v_owl.ax);
function update_ball(p_ball, dt)
</source>
</source>


ergänzt werden.
Bislang hat die Update-Methode auf das global definierte Ball-Modell <code>v_ball</code>
zugegriffen, um die neue Postiion zuberechnen und die Kollissionserkennung und
-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>
ein anderer Ball zur Bearbeitung übergeben.


===Aufgabe 7===
Die eigentliche Update-Methode code>update</code> existiert weiterhin, doch ihre Aufgabe hat sich geändert:
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
<code>update_ball</code> aufrufen.


Schreiben Sie die Kollissionsbehandlung so um, dass im Falle einer Kollission der Eule
Die Renderfunktion muss analog angepasst werden: Sie muss ebenfalls das Array <code>v_balls</code> durchlaufen.
mit dem unteren Fensterrand sowohl die Geschwindigkeit als auch die Beschleunigung
Für jedes Ball-Modell-Objekt muss dessen aktuelle Position dem zugehörigen Ball-View-Objekt zuweisen werden.
jeweils in x- und in y-Richtung auf Null gesetzt wird. Das heißt, die
Das aktuelle View-Objekt findet sie jeweils im Array <code>v_ball_views</code> an derselben Position.
Eule bleibt bei einer Kollision mit dem unteren Rand stehen stehen.
(Sie könnten auch das Array <code>v_ball_views</code> durchlaufen. Das macht keinen Unterschied, weil beide Arrays
gleichviele Elemente enthalten.)


===Aufgabe 8===
Zu guter Letzt muss noch die Callback-Funktion des Pixi-Loaders angepasst werden. Diese muss ebenfalls
das Array <code>v_balls</code> durchlaufen und für jedes Modell-Objekt ein zugehöriges View-Objekt
erzeugen. Jedes neu erzeugte View-Objekt wird abschließend mittels


Positionieren Sie die Eule im linken oberen Eck, geben Sie ihr eine Geschwindigkeit von
<source lang="javascript">
300 Pixeln in x-Richtung und von 0 Pixeln in y-Richtung. Die Beschleunigung in x-Richtung beträgt 0,
v_ball_views.push(l_ball_view);
die Beschleunigung in y-Richtung 800. (Diese Zahlen sind gut für meinen Monitor geeignet,
p_stage_view.addChild(l_ball_view);
der eine Auflösung von 1600x900 hat. Wenn Sie einen deutlich kleineren oder größeren
</source>
Monitor haben, müssen Sie die Zahlen evtl. anpassen.)


Welche Kurve beschreibt die Eule, wenn Sie die Web-App starten?
sowohl ans Ende des Arrays <code>v_ball_views</code> als auch in die PixiJS-Bühne eingefügt.


===Aufgabe 9===
Eine Sache ist bei der Erzeugung der Views noch zu beachten. Es gibt zwei Texturen und in jedem Modell-Objekt ist
vermerkt, welche Textur verwendet werden soll.
 
Importieren Sie also die URLs beider Texturen:
 
<source lang="javascript">
import owl from '../../img/owl-50.png';
import tux from '../../img/tux-50.png';
</source>


Positionieren Sie die Eule im linken unteren Eck, geben Sie ihr eine Geschwindigkeit von
und laden sie die zugehörigen Bilder anschließend mit Hilfe des Pixi-Loaders:
300 Pixeln in x-Richtung und von -400 Pixeln in y-Richtung. Die Beschleunigung in x-Richtung beträgt 0,
die Beschleunigung in y-Richtung 200.


Welche Kurve beschreibt die Eule, wenn Sie die Web-App starten?
<source lang="javascript">
p_pixi.loader.add('owl', owl).add('tux', tux)...
</source>


Jetzt fehlt nur noch en Gummiband, mit dem Sie die Eule beschleunigen können. :-)
Beim Erzeugen der Views (innerhalb der zuvor genannten For-Schleife) verwenden Sie dann jeweils die im Model-Objekt beschriebene Textur:


===Aufgabe 10===
<source lang="javascript">
let l_ball_view = new p_pixi.Sprite(p_resources[v_balls[i].img].texture);
</source>


Machen Sie eigene Experimente. Bringen Sie {{zB}} die Eule dazu, wie ein Flummi
===Aufgaben 4 bis 10===
zu springen, indem sie ihre Geschwindigkeit in x- und y-Richtung bei einem Bodenkontakt
um einen gewissen Prozentsatz reduzieren, aber nicht gleich auf null setzen.
(Die Beschleunigung, die die Erdanziehung simuliert, bleibt die ganze Zeit über gleich).
Achtung: Bei einem Bodenkontakt, dreht sich das Vorzeichen der y-Geschwindigkeit um,
das der x-Geschwindigkeit bleibt gleich.


Nervig ist, dass die Eule zum Schluss immer noch ganz leicht hüpft und gar nicht
Nun können und sollten Sie zur Übung die Aufgaben 4 bis 10 der [[MMProg: Praktikum: WiSe 2017/18: GameLoop01#Aufgabe 2|ersten Praktikumsaufgabe]]
zur Ruhe kommt. Vielleicht können Sie auch dieses Problem beheben.
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, {{zB}} indem Sie mehrere Eulen hintereinander die Wand entlang fliegen lassen.  


Oder probieren Sie etwas ganz anderes aus.
-->
==Quellen==
==Quellen==
<references/>
<references/>
<ol>
<ol>
<li value="1"> {{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</li>
<li value="11"> {{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</li>
</ol>
</ol>

Version vom 13. Dezember 2017, 12:47 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)

MMProg-Praktikum

Inhalt | Game Loop 01 | Ball 02 | Ball 03 | Ball 03b | Pong 01

Musterlösung: SVN-Repository

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

Laden Sie das leere Projekt WK_Ball02_empty auf Ihren Rechner. Installieren Sie aber nicht die Node.js-Module, das machen Sie später. Sie finden das leere Projekt im Repository-Pfad https://glossar.hs-augsburg.de/beispiel/tutorium/es6 im Unterordner empty.

Erstellen Sie ein neues Projekt praktikum02 und kopieren Sie die Ordner src und web (samt Inhalt) sowie alle Dateien, die Sie im Wurzelverzeichnis des Projektes WK_Ball02_Empty finden, mittels Ctrl-/Apfel-C Ctrl-/Apfel-V in Ihr eigenes Projekt. (Die Frage, ob WebStorm seinen eigenen File Watcher zum Übersetzen von ES6-Code in ES5-Code verwenden soll, beantworten Sie bitte mit „No“. Das erledigt Webpack für Sie.)

Sie können Ihr Projekt zur Übung auch im Subversion-Repository speichern. Das ist aber nicht so wichtig.

Nun können Sie in Ihrem eigenen Projekt die benötigten Node.js-Module installieren: npm i.

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.

app1.js

  • Es wird zusätzlich die JavaScript-Bibliothek pixi.js importiert.
  • Anstelle von GameLoop.js wird GameLoopPixi.js importiert. Dieses Modul verwendet die PixiJS-eigene GameLoop, stellt aber weiterhin sicher, dass Modell-Updates nach Möglichkeit stets genau 60 mal pro Sekunde erfolgen.[5]. Außerdem verzögert dieses Modul den Start der Modellberechnung um ein paar Frames, da sich PixiJS hier anscheinend teilweise etwas „warmläuft“ und diese Frames deutlich länger als 16,7 Millisekunden dauern können. Ohne die Verzögerung der Modellberechnung würde die Web-Anwendung etwas „ruckelig“ starten.
  • Ein rudimentäres Modell der Bühne (stage) wird bereits in app1.js und nicht erst in game1.js definert, da es bereits bei der Definition des PixiJS-Applikations-Objekts benötigt wird.
  • 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.[6]
  • Das Spiel wird mit Hilfe einer Methode init initialisiert, bevor die Game Loop gestartet weren 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.

Anmerkung: Das Modul pixi.js wird wie alle anderen Module auch mittels npm i geladen und im Ordner node_modules abgelegt. Allerdings ist WebStorm derzeit etwas buggy. Wenn ein Modulname auf .js endet, findet Webstorm das Modul im Ordner node_modules nicht. Das hat zur Folge, dass on WebStorm für jede PixiJS-Methode eine Warnung ausgegeben wird, dass diese (angeblich) nicht exsitiere. Als Workaround habe ich einfach die Source-Datei pixi.sj dupliziert und in den Ordner src/js gelegt. Schön ist das nicht, aber die Anzahl der Warnungen wird dadurch drastisch reduziert (außer beim ersten Speichern dieser Datei im Repository). Eine andere Aufgabe hat die Datei src/js/pixi.js nicht.

game1.js

  • Das Modell der Bühne (v_stage) wird nur teilweise initialisiert. Die Breite und Höhe wird erst später von der Methode init festgelegt.
  • Die Variable v_owl wurde in v_ball 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.
  • Das Objekt v_ball 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 (width) durch den Radius (r) ersetzt.
  • Der Ankerpunkt des Balls wurde von der linken obren Ecke der Eule ins Zentrum des Balls verschoben. Dies hat Auswirkungen auf die Implementierung der Kollissionserkennung und -behandlung. Beachten Sie bitte, dass in der Update-Methode der Parameter p_frac_s durch die üblichere Bezeichnung dt (für delta time) ersetzt wurde.
  • 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, z. B. 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 i. Allg. 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[7]) 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 (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 (sofern die PixiJS-Game-Loop aktiv ist).

Aufgabe 2

Lösen Sie Aufgabe 2 der ersten Praktikumsaufgabe mit Hilfe von PixiJS: 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.

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

Und denken Sie daran, grunt oder besser noch grunt watch zu verwenden. :-)

Aufgabe 2a

Ä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:

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

oder

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

(Genaugenommen weisen Sie Webpack mit dieser Anweisung an, das Grafikobjket im Web-Bereich zu erzeugen und die zugehörige URL in der Variablen sprite zu speichern.)

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

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

p_stage_view.addChild(v_ball_view);

Aufgabe 3

Lösen Sie Aufgabe 3 der 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). (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 p_pixi.Sprite.fromImage synchron 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.

Sie sollten die Aufgabe zunächst einmal auf Basis Ihrer Erkenntnisse von Aufgabe 2a lösen.

Solbald diese Lösung funktioniert, sollten Sie folgene Änderung vornehmen: 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)[8][9].

Eine wesentliche Änderung, die sich wegen der Asynchronität ergibt, is, 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 eine Callback-Funktion, die vom PixiJS-Loader aufgerufen wird, sobald der Ladevorgang beendet ist. Stellen Sie in der Datei app3.js eine Callback-Funktion bereit, deren einzige Aufgabe es ist, die Game Loop zu starten.

Ersetzen Sie in der Datei app3.js den Code

game.init(PIXI, c_stage, c_pixi_app.stage);
new GameLoopPixi(c_pixi_app.ticker, game.update, game.render).start();

durch

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(); }
);

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.

Nun muss noch die Initialisierungsfunktion in der Datei game3.js entsprechend angepasst werden.

Zunächst wird sie um einen Callback-Parameter f_ready erweitert:

function init(p_pixi, p_stage, p_stage_view, f_ready)

In diesem Parameter wird der Initialisierungsmethode die zuvor definierte Callback-Funktion übergeben, die aufgerufen weren muss, sobald der Initalisierungsvorgang 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 v_ball_view zu tun haben.

Die benötigten Medien (hier: das Bild der Eule) werden künftig mit Hilfe eines PixiJS-Loaders asynchron geladen:

p_pixi
    .loader
    .add('sprite', sprite)
    .load(function(p_loader, p_resources)
          {
            
            f_ready();
          }
    );

Der Pixi-Loader p_pixi.loader wird zunächst angewiesen, das Medium, dessen URL in der Variablen sprite steht, zu laden und unter dem (sehr einfaltsreichen) Namen 'sprite' in einem JavaScript-Objekt zu speichern. Es ist durchaus üblich, eine Vielzahl von Medien auf einmal (d. h. parallel) zu laden:

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

oder auch

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

Zu guter Letzt wird die Methode p_pixi.loader.load(...) 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. Fügen Sie daher vor dem Befehl f_ready(); folgende Befehle ein:

v_ball_view = new p_pixi.Sprite(p_resources['sprite'].texture);
v_ball_view.anchor.set(0.5); // center the anchor

p_stage_view.addChild(v_ball_view);

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, ohne dass die zugehörige Textur mehrfach vom Server geladen werden muss.

Ganz wichtig ist es, als letzten Befehl der soeben definierten Callback-Funktion die Callback-Funktion f_ready auszuführen (also mittels f_ready(); aufzurufen). Anderenfalls wird die Game Loop nie gestartet und die Web-Anwendung verharrt im Initialzustand.

Wenn Sie schon dabei sind, sollten Sie gleich noch eine zweite Unsauberkeit eliminieren. Die Konfiguration der Modell-Objekte v_stage und v_ball sollte mittels einer JSON-Datei erfolgen. Im leeren Projekt finden Sie die JSON-Datei src/js/init3.json, die diese beiden Objekte enthält. Importieren Sie diese Datei in die Datei game3.js (vgl. HTML5-Tutorium: JavaScript: Hello World 05) und weisen Sie die beiden importierten Objekte den Variablen v_stage bzw. v_ball zu.

Tipp: Um die Anzahl der WebStorm-Warnings zu reduzieren, können Sie direkt vor den JSON-Import-Befehl zusätzlich folgenden Import-Befehl einfügen:

import '../../json/init3.js';

Die Datei init3.js enthält keinerlei ausführbaren Code, sondern nur einen Kommentar, der von Webpack spätestens dann entfernt wird, wenn die Ausgabedatei app3.bundle.js mittels grunt compress komprimiert wird. Der Kommentar hat es allerdings in sich. Er beschreit im JSDoc-Format[10] den Inhalt der JSON-Datei. Dieser Kommentar wird von WebStorm interpretiert, um die Anzahl der Warnings zu reduzieren.

Aufgabe 3a

Erweitern Sie Ihre Lösung von Aufgabe 3 so, dass 5 unterschiedliche Vögel auf unterschiedlichen Routen über die Bühne fliegen. In der Datei src/json/init3a.json finden Sie die zuanimierenden Objekte. Anstelle eines Objektes ball ist hier ein Array balls definiert, das fünf Ball-Objekte enthält. Jedes dieser Objekte hat neben den bereits bekannten Attributen ein weiteres Attribut img, das beschreibt, mit welcher Textur das Objekt visualisiert werden soll.

Importieren Sie diese JSON-Datei wieder und speichern Sie die darin enthaltenen Objekte in den Variablen v_stage bzw. v_balls (nicht v_ball!). Definieren Sie außerdem eine Variable v_ball_views und initialisieren Sie diese mit einem leeren Array. In diesem werden anschließend von der Initialisierungsmethode die zugehörigen View-Objeke abgelegt.

Alls nächstes müssen Sie die Methode update umbenennen und um den Parameter p_ball erweitern:

function update_ball(p_ball, dt)

Bislang hat die Update-Methode auf das global definierte Ball-Modell v_ball zugegriffen, um die neue Postiion zuberechnen und die Kollissionserkennung und -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 p_ball ein anderer Ball zur Bearbeitung übergeben.

Die eigentliche Update-Methode code>update existiert weiterhin, doch ihre Aufgabe hat sich geändert: Sie muss mit Hilfe einer For-Schleife[11] das Array v_balls durchlaufen und für jedes Element die (Hilfs-)Methode update_ball aufrufen.

Die Renderfunktion muss analog angepasst werden: Sie muss ebenfalls das Array v_balls durchlaufen. Für jedes Ball-Modell-Objekt muss dessen aktuelle Position dem zugehörigen Ball-View-Objekt zuweisen werden. Das aktuelle View-Objekt findet sie jeweils im Array v_ball_views an derselben Position. (Sie könnten auch das Array v_ball_views durchlaufen. Das macht keinen Unterschied, weil beide Arrays gleichviele Elemente enthalten.)

Zu guter Letzt muss noch die Callback-Funktion des Pixi-Loaders angepasst werden. Diese muss ebenfalls das Array v_balls durchlaufen und für jedes Modell-Objekt ein zugehöriges View-Objekt erzeugen. Jedes neu erzeugte View-Objekt wird abschließend mittels

v_ball_views.push(l_ball_view);
p_stage_view.addChild(l_ball_view);

sowohl ans Ende des Arrays v_ball_views als auch in die PixiJS-Bühne eingefügt.

Eine Sache ist bei der Erzeugung der Views noch zu beachten. Es gibt zwei Texturen und in jedem Modell-Objekt ist vermerkt, welche Textur verwendet werden soll.

Importieren Sie also die URLs beider Texturen:

import owl from '../../img/owl-50.png';
import tux from '../../img/tux-50.png';

und laden sie die zugehörigen Bilder anschließend mit Hilfe des Pixi-Loaders:

p_pixi.loader.add('owl', owl).add('tux', tux)...

Beim Erzeugen der Views (innerhalb der zuvor genannten For-Schleife) verwenden Sie dann jeweils die im Model-Objekt beschriebene Textur:

let l_ball_view = new p_pixi.Sprite(p_resources[v_balls[i].img].texture);

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)