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

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 58: Zeile 58:
* 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>
* 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.  
* 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.  
'''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.
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.
Das hat zur Folge, dass für jede PixiJS-Methode eine Warning 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 Warnings 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.


'''<code>game1.js</code>'''
'''<code>game1.js</code>'''

Version vom 2. November 2017, 16:00 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] „“ 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. Die ist insbesondere dann von Bedeutung, wenn Animationen erstellt werden, d. h., wenn 60 mal pro Sekunde (also alle 16,7 ms) eine neue Grafik im 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 Crtl-/Apfel-C Crtl-/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.)

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

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: 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. 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.vgl. Isaac Sukin: A Detailed Explanation of JavaScript Game Loops and Timing. Außerdem verzögert dieses Modul den Start der Modellberechnung um ein paar Frames, da 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.
  • Das Modell der Bühne (stage) wird 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.[5]
  • 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 Ordnernode_modules nicht. Das hat zur Folge, dass für jede PixiJS-Methode eine Warning 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 Warnings 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 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 eine 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. In den Folgeaufgaben wird die Initialisierungsfunktion auch in dieser Hinsicht aktiv werden müssen.
    • 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[6]) wird zunächst ein Grafikobjekt erzeugt. Diesees Grafikobjekt wird mit Inhalten gefüllt (Kreis von besimmter Farbe mit einem Rand von bestimmter Breite 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 Festerrand fliegen. Anstelle einer Eule verwenden Sie zunächst bitte einen farbigen „Ball“ mit Rand, wie sie in der ersten Aufgabe kennengelernt haben.

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 frabigen Kreises stellen Sie bitte eine Eule oder einen Pinguin dar. Importieren Sie dazu 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 im 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)[7][8].

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. In der Datei app3.js wird eine Callback-Funktion bereit gestellt, deren einzige Aufgabe es ist, die Game Loop zu starten. 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.

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

Nun muss noch die Initialisierungsfunktion 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 eine Callbackfunktion übergeben, die aufgerufen weren muss, sobald der Initalisierungsvorgang vollständig abgearbeitet wurde.

Als nächstes werden alle benötigten Medien mit Hilfe eines PixiJS-Loaders geladen:

p_pixi
    .loader
    .add('sprite', sprite)
    .load(function(p_loader, p_resources)
          {
            &lt;initialize the ball view&gt;
            f_ready();
          }
    );

Der Pixi-Loader p_pixi.loader wird zunächst angewiesen, das Medium, dessen URL in der Variablen sprite asynchron 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(...)...

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

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.

Quellen

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