Leightweight Java Games Library
Transcription
Leightweight Java Games Library
Hochschule Fulda [LOGO] Ausarbeitung im Rahmen der Lehrveranstaltung „Grafische Interaktive Systeme“ von Daniel Bogdan 07.01.09 Daniel Bogdan Grafische Interaktive Systeme Seite 1 Inhaltsverzeichnis 1. Einleitung 2. Unterschied von LWJGL 1.1.4 zu 2.0 3. Benutzung der Zusatzbibliothek von [POTATO] 4. Entwicklungsdokumentation 4.1. Praktisches Beispiel (Tutorial) 4.1.1. Grundgerüst 4.1.2. Klassendiagramm 4.1.3. Kamera 4.1.4. Wetter erzeugen 4.1.5. Objekte erstellen 4.1.6. Texturen auf Objekte 4.1.7. Fahrzeugmodell erstellen 4.1.8. 3D-Modelle importieren 4.1.9. Screenshots 4.2. Fazit 4.3. Quellen 5. Anhang 5.1. Anwenderdokumentation 5.2. Eingesetzte Werkzeuge 5.3. Darstellung des Aufwands Daniel Bogdan Grafische Interaktive Systeme Seite 2 1. Einleitung Ich habe dieses Thema gewählt, da mir es Freude bereitet Spiele zu programmieren. Dazu kommt noch die Kombination OpenGL mit der Programmiersprache Java. Java ist im Allgemeinen übersichtlich und strukturiert aufgebaut und verfügt über eine gute Dokumentation. Ein weiterer Vorteil ist die mächtige Entwicklungsumgebung Eclipse. Der einzige Nachteil ist nur, dass Java vergleichsweise sehr langsam ist. Hier kommt nun die Lightweight Java Game Library ins Spiel, die eine Schnittstelle zwischen Java und OpenGL bietet. Die OpenGl-Befehle sind schnell und machen den Nachteil, dass Java von Grund auf langsamer ist, wieder wett. Die Performance war mehr als nur akzeptabel. Es muss aber auch gesagt werden, dass sich die Komplexität des Programms in Grenzen hält. Wie es in einem richtig großen Programm aussieht, kann ich schlecht beurteilen. Als weiteren Vorteil sehe ich, das die Kombination plattformunabhängig ist. Sowohl Java, als auch OpenGL funktionieren nicht nur unter Windows. Meine Ausarbeitung baut auf der von Daniel Türpitz auf. Daher gehe ich nicht mehr auf die Grundlagen ein, sondern beginne relativ zügig mit dem praktischen Beispiel. Des weiteren möchte ich erwähnen, dass ich im Quellcode englische Variablen verwendet, den Kommentar aber auf Deutsch geschrieben habe. Dies hat einen einfachen Grund; die englischen Wörter sind im Durchschnitt kürzer. Somit macht das den Quellcode wieder etwas übersichtlicher. In der deutschen Sprache ist jedoch die Redundanz größer und man kann Probleme besser erklären. Im Englischen führt das oft zu einer Zweideutigkeit, daher die Kommentare auf Deutsch. 2. Unterschied von LWJGL 1.1.2 zu 2.0 Mittlerweile ist die aktuellste Version 2.0.1 von LWJGL. Mit Beginn meiner Ausarbeitung, existierte aber nur LWJGL 2.0. Deshalb beachte ich die Version 2.0.1 hier auch nicht weiter. Da meine Ausarbeitung auf die von Daniel Türpitz aufbaut, möchte ich kurz die wichtigste Änderung erwähnen. Alle weiteren Unterschiede aufzuzählen sprenge den Rahmen. Sollte diesbezüglich Interesse bestehen, gibt es auf der folgenden Seite ausführlichere Informationen: [LWJGL_C] In der Version 1.1.2 war die Klasse GLU noch im Paket org.lwjgl.opengl. Ab Version 2.0 liegt sie im Paket org.lwjgl.util. Dies ist zu beachten und ggf. zu ändern. Das Einrichten der LWJGL 2.0 funktioniert genauso unter der Version 1.1.2. Daniel Bogdan Grafische Interaktive Systeme Seite 3 3. Benutzung der Zusatzbibliothek von [POTATO] Meine erste Aufgabe bestand darin, Beispiel-Programme und Demos im Internet zu suchen, die die LWJGL benutzen. Ich fand aber nur zwei nennenswerte Seiten. Einmal die von [LWJGL] selbst und des weiteren von [POTATO] Bei der letzteren bemühte sich der Entwickler sehr und erstellte einige Klassen, die man als Zusatzbibliothek zählen kann. Diese bietet die einfache Möglichkeit Texturen auf Objektoberflächen zu bringen, OBJ- oder 3DS-Modelle zu importieren und Audio-Dateien einzufügen. Auf diese Bereiche werde ich auch nicht weiter eingehen; ich verwende nur die Schnittstellen und werde auf einige Probleme aufmerksam machen. Das Hauptproblem war, dass bisher nur mit LWJGL 1.1.4 gearbeitet und somit die Klasse GLU im falschen Paket eingebunden wurde. Ein weiteres Problem war, dass in der Klasse GLApp die Methode GLU.gluProject() und GLU.gluUnProject() als letzten Parameter einen Datentyp float[] verlangt und in der LWJGL 2.0 einen Datentyp von FloatBuffer. Da ich diese Methode aber nicht anwenden werde, habe ich sie einfach auskommentiert. Daniel Bogdan Grafische Interaktive Systeme Seite 4 4. Entwicklungsdokumentation 4.1. Praktisches Beispiel (Tutorial) Der Schwerpunkt meiner Ausarbeitung liegt im praktischen Teil. Mein Ziel ist es nicht, ein fertiges 3D-Spiel zu programmieren, sondern viele Ansätze darzustellen, diese aber nicht bis ins kleinste Detail zu diskutieren. 4.1.1 Grundgerüst Das Grundgerüst von meinem Projekt ist die Klasse GameMain, die von der Klasse GLApp abgeleitet ist. GLApp ist eine voll funktionsfähige Klasse. Das ausführen des Programms an dieser Stelle würde eine Demo starte, in dem sich zwei Würfel drehen. Da ich aber meine eigene Szene zeichnen will, habe ich die GameMain geschrieben und überschreibe nur die Methoden init(), render(), keyDown() und keyUp(). Auf die restlichen Methoden von GLApp kann ich aber trotzdem noch zugreifen, denn die werden vererbt. Die init() - Methode wird nur einmal zum Start des Programms aufgerufen und wichtige Funktionen initialisiert. Die render() - Methode wird in einer Schleife ausgeführt, die sich so lange wiederholt, bis die ESC-Taste gedrückt wird. Hier wird auch das zeichnen der Szene realisiert. Die keyDown() - Methode wird aufgerufen, wenn auf der Tastatur eine Taste gedrückt wird. Wenn die Taste gedrückt gehalten wird, wird diese Methode aber nicht ständig aufgerufen. Wenn die Taste losgelassen wird, wird die Methode keyUp() aufgerufen. Dies sollte erstmal als Grundgerüst reichen. package spiel; import org.lwjgl.opengl.GL11; import org.lwjgl.util.glu.GLU; import lib.*; public class GameMain extends GLApp { public void init () { initDisplay (); initInput (); initOpenGL (); Daniel Bogdan /* Initialisierung des Fenster */ /* Initialisierung der Tastatur und Maus */ /* initialisierung der OpenGL Umgebung */ Grafische Interaktive Systeme Seite 5 } public void render () { GL11.glClear (GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); } public void keyDown (int keycode) { } public void keyUp (int keycode) { } public static void main (String args[]) { GameMain demo = new GameMain (); demo.run (); } } 4.1.2 Klassendiagramm Im Klassendiagramm sieht man sehr deutlich, was ich selbst geschrieben habe und was vorgefertigt verwendet wurde. Das Paket game kommt von mir und das Paket lib von [POTATO]. Daniel Bogdan Grafische Interaktive Systeme Seite 6 4.1.3 Kamera Ich habe zwei Kameraeinstellungen implementiert. Erstens das Verfolgen des Fahrzeugs und zweitens das freie Bewegen in der 3-D Welt. Dazu erstellen wir erst einmal die Klasse Camera mit sechs Attribute, die später für das gluLookAt() benötigt werden. Ich habe sie wie folgt genannt: xPos, yPos, zPos. Die drei Koordinaten repräsentieren einen Punkt in der Welt, an dem die Kamera steht. Die anderen drei Attribute: xView, yView, zView repräsentieren einen Punkt in der Welt, auf den die Kamera schaut. Des weiteren benötigen wir noch einige boolische Variable, die den Wert true besitzen, wenn eine bestimmte Taste gedrückt wird. /* Kamera um das Objekt nach rechts oder links drehen */ private boolean rotaCCW, rotaCW; /* Kamera nach oben oder unten bewegen */ private boolean yVecUp, yVecDown; /* Kamera nach vorne oder hinten bewegen */ private boolean xVecForw, xVecBackw; /* Kamera nach links oder rechts bewegen */ private boolean zVecLeft, zVecRight; /* View-Punkt nach oben oder unten bewegen */ private boolean xRotaUp, xRotaDown; /* View-Punkt nach links oder rechts drehen */ private boolean yRotaLeft, yRotaRight; Die action()-Methode ist das Herzstück der Klasse. Sie wird von der Methode render() in der Klasse GameMain für jeden Durchlauf einmal ausgerufen. Dabei wird nur überprüft, ob einer der boolischen Variablen den Wert true besitzt. Wenn keine Taste gedrückt wird, verändert die Methode auch nichts. Ich beschreibe hier nur die Kamera, die sich im Raum frei bewegen kann. Damit dies der Kamera möglich ist, muss die xPos und zPos Variable geändert werden. Damit der Blick immer gleich bleibt, ändere ich parallel auch die xView und zView Variable. if (xVecForw) { } /* Kamera bewegt sich mit View- /* Punkt nach vorne xPos -= Math.cos (camAngle); xView -= Math.cos (camAngle); zPos -= Math.sin (camAngle); zView -= Math.sin (camAngle); */ */ Das Rückwärtsbewegen funktioniert analog; nur alle vier Vorzeichen sind umgekehrt. Beim Linksbewegen dreht sich das Vorzeichen der z-Koordinate, im Gegensatz zum Daniel Bogdan Grafische Interaktive Systeme Seite 7 Vorwärtsbewegen. Ansonsten sieht es identisch aus. if (zVecLeft) /* Kamera bewegt sich mit View{ /* Punkt nach links xPos -= Math.sin (camAngle); xView -= Math.sin (camAngle); zPos += Math.cos (camAngle); zView += Math.cos (camAngle); } */ */ Das Rechtsbewegen funktioniert wieder analog, nur sind wieder alle Vorzeichen umgekehrt. z camAngle = 30° xPos = zPos = 0 grün: xPos += cos (30°) = 0,866 zPos += sin (30°) = 0,5 x rot: xPos -= cos (30°) = -0,866 zPos -= sin (30°) = -0,5 blau: xPos -= cos (30°) = -0,866 zPos += sin (30°) = 0,5 magenta: xPos += cos (30°) = 0,866 zPos -= sin (30°) = -0,5 An dieser Skizze ist dies deutlicher erkennbar. Die Kamera befindet sich im Koordinatenursprung. Der Kamerawinkel beträgt 30°. Wenn ich mich in einer der vier Richtungen bewegen möchte, kommen für xPos und zPos die Ergebnisse auf der rechten Seite neben der Skizze heraus. Bei den Variable xView und zView sieht dies analog aus. Dies war ein kurzer Einblick in die Klasse Camera; weitere Informationen stehen im Quellcode selbst. 4.1.4 Wetter erzeugen Dieser Punkt hört sich vielleicht spannend an, aber ich spreche hier nicht von Gewitter und Regen, sondern lediglich von einem Tag-/Nachtzyklus und Nebel. Daniel Bogdan Grafische Interaktive Systeme Seite 8 Das Wichtigste in der Klasse Weather ist das Licht. Ich verwende einmal als Lichtquelle paralleles Licht, das die Sonne darstellen soll sowie ein Punktlicht, das eine Lampe vergegenwärtigt. Wieder ist die Methode action() das Herzstück der Klasse. Sie wird von der Methode render() in der Klasse GameMain für jeden Durchlauf einmal ausgerufen. Mit jedem Aufruf erhöht sich die Zeit. Die Lichtverhältnisse werden mit Hilfe der Sinus- Übergang von Tag in Nacht Mittag ½ * PI Funktion berechnet. 0 * PI Bei ½ PI ist der Sinus eins und somit sind die 1 * PI Lichtverhältnisse am stärksten ausgeprägt. Ist Aktuelle Zeit die Zeit größer als PI, ist der Sinus davon negativ Mitternacht und die Nacht wird ¾ * PI Übergang von Nacht in Tag simuliert. Wenn der Nebel aktiv ist, bleibt die Hintergrundfarbe in der Nacht schwarz und der Nebel wird ebenfalls dunkel. Am Tag ist der Nebel grau, genau wie die Hintergrundfarbe. Je näher die Zeit am Mittag, desto heller dieses Grau. Ansonsten wird es wieder dunkler, bis es ins Schwarz übergeht. Den Zeitpunkt der Aktivierung des Nebels kann der Benutzer mit Hilfe einer Taste beeinflussen. Das Punktlicht leuchtet in der Garage und der Benutzer kann es auch mit Hilfe einer Taste an- und ausschalten. Mehr Informationen zu der Tastenbelegung gibt es in der Anwenderdokumentation. Ein Problem, das mir aber erst sehr spät aufgefallen ist, war, dass das Punktlicht durch die Wände scheinen konnte und auch den Boden beleuchtete, obwohl ja ein Objekt dazwischen stand. Für die Berechnung des Schattens konnte ich mich aus zeitlichen Gründen nicht mehr informieren und habe es daher einfach hingenommen. 4.1.5 Objekte erstellen Die Klasse GLApp bietet mit den Methoden renderCube() und renderSphere() die Möglichkeit einen Würfel bzw. eine Kugel zu zeichnen. Wenn man sich aber die Methode renderCube() Daniel Bogdan ansieht, fällt ganz schnell auf, dass sich die Vertex-Punkte noch in dem Grafische Interaktive Systeme Seite 9 glBegin() und glEnd() glDrawArrays() – Block befinden. Heutzutage benutzt man nur noch oder glDrawElements(). Da dies vom Aussehen her keinen Unterschied macht, sondern nur die Performance verbessert, empfand ich es als nicht notwendig, dies umzuschreiben. Als Erstes erstellen wir einen Boden mit Hilfe der renderCube()-Methode. Dann skalieren wir den Würfel in x- und z-Richtung um das Hundertfache und in y-Richtung um ein Zehntel. Zuletzt geben wir den Objekten noch eine Materialeigenschaft für ambientes und diffuses Licht, beispielsweise grau. Die GL11.glMaterial()-Funktion verlangt als letzten Parameter einen Datentyp FloatBuffer. Daher die Methode allocFloats(), die in der Oberklasse GLApp definiert ist. Sie wandelt das float-Array in einen FloatBuffer um und gibt dieses als Rückgabewert zurück. public void init () { ... gray = new float[] {0.5f, 0.5f, 0.5f, 1.0f}; ... } public void render () { ... GL11.glPushMatrix (); { GL11.glMaterial (GL11.GL_FRONT, GL11.GL_AMBIENT_AND_DIFFUSE, allocFloats (gray)); GL11.glScalef (100f, 0.1f, 100f); renderCube (); /* Soll den Boden darstellen */ } } GL11.glPopMatrix (); ... Im nächsten Unterpunkt erkläre ich, wie man Texturen auf diesen Objekten erhält, damit sie etwas echter aussehen. Mit diesem Prinzip habe ich jetzt eine Garage gebaut, die aus drei Wänden und einem Dach besteht. Das ist nicht die schönste Variante, aber eine schnelle. Schöner wäre es, wenn man ein Objekt mit 3D-Modellierungswerkzeugen erstellt und sie hier lediglich einbindet. Dies funktioniert auch, das ich aber erst im Unterpunkt „3D-Modelle importieren“ erkläre. Daniel Bogdan Grafische Interaktive Systeme Seite 10 4.1.6 Texturen auf Objekte Texturen auf Objekten zu erhalten ist mit der Zusatzbibliothek von [POTATO] relativ einfach. Die Klasse GLApp erledigt die meiste Arbeit; zusätzlich benötigt man die Klasse GLImage. In der init() - Methode ruft man als erstes die Funktion GL11.glEnable(GL11.GL_TEXTURE_2D); auf, damit die Texturen eingeschaltet werden. Als Nächstes lädt man die Bild-Datei, dazu die Methode loadImage() und als Parameter übergibt man den Pfad zur Bild-Datei als String. Der Rückgabetyp ist ein GLImage. Wichtig ist, dass man weiß, in welchen Paket die Klasse GLApp liegt. Der Pfad zählt nämlich ab dem Ordner und ist somit relativ. Die Klasse GLApp liegt bei mir im Paket lib. Mein Ordner mit den Bild-Dateien liegt aber in dem Ordner „res“, der wiederum auf der selben Höhe von dem Paket lib liegt. Ich muss mich also in der Hierarchie einen Ordner höher bewegen und könnte dann in „res“ wechseln. Das Zurückspringen funktioniert mit dem „../“. Der Aufruf würde dann wie folgt heißen: GLImage textureImg = loadImage ("../res/images/ground.png"); Als Nächstes verwendet man die Methode makeTexture() und übergibt als Parameter das GLImage, das zuvor erzeugt wurde. Als Rückgabetyp gibt es einen Integer-Wert. Der Aufruf sieht dann folgendermaßen aus: groundTexHandle = makeTexture (textureImg); Als Letztes noch die Methode makeTextureMipMap(), die man auch in der Klasse GLApp vorfindet. Als Parameter übergibt man wieder das zu Beginn erzeugte GLImage. Rückgabetyp gibt es diesmal keinen. Der Aufruf ist auch nicht unbedingt erforderlich, lässt das Bild aber schöner aussehen. Der Aufruf würde wie folgt aussehen: makeTextureMipMap (textureImg); Das war die Initialisierungsphase, die nur einmal zum Programmstart aufgerufen wird. Die render() - Methode sieht jetzt folgendermaßen aus: public void render () { ... GL11.glPushMatrix (); { GL11.glMaterial (GL11.GL_FRONT, GL11.GL_AMBIENT_AND_DIFFUSE, allocFloats (gray)); Daniel Bogdan Grafische Interaktive Systeme Seite 11 GL11.glBindTexture (GL11.GL_TEXTURE_2D, groundTexHandle); GL11.glScalef (100f, 0.1f, 100f); renderCube (); /* Soll den Boden darstellen */ } GL11.glPopMatrix (); ... } Möchte man nun für ein anderes Objekt eine andere Textur verwenden, führt man die Initialisierungsphase noch einmal durch, diesmal nur mit dem anderen Bild als Datei-Pfad, und speichert dies in eine andere Variable. Mit diesen Variablen hat man dann Zugriff auf die unterschiedlichen Texturen. Die Variable ist eigentlich nur eine Integer-Zahl, die hochgezählt wird. Ein Problem tritt auf, wenn man für ein Objekt keine Textur haben möchte. Denn selbst wenn man glBindTexture() nicht aufruft, wird automatisch die zuletzt verwendete Textur benutzt. Daher ist es notwendig, vor dem Zeichnen des bestimmten Objekts die Texturen mit dem Befehl GL11.glDisable(GL11.GL_TEXTURE_2D); zu deaktivieren und nach dem Zeichnen wieder zu aktivieren. 4.1.7 Fahrzeugmodell erstellen Das Fahrzeugmodell ist nichts anderes, als die Berechnung der Bewegung des Fahrzeuges. Die wichtigsten Instanzvariablen sind die x- und z-Koordinate, sowie der Drehwinkel des Fahrzeuges. Diese Berechnung geschieht mit Hilfe von zwei Vektoren. Zum Einen der Geschwindigkeitsvektor velocity und zum Anderen der Lenkvektor steer. Da der Wendekreis hauptsächlich abhängig von der Geschwindigkeit und dem Lenkeinschlagswinkel ist, sollten diese beiden Vektoren reichen. Daniel Bogdan Grafische Interaktive Systeme Seite 12 z angle = arctan (steer / velocity) angle = arctan (3 / 4) angle = 36,87° steer = 3 angle velocity = 4 x In der Skizze sieht man deutlich, dass man für den Winkel nur die beiden Vektoren als Eingabewerte benötigt. Da wir in dem rechtwinkligen Dreieck nur die beiden Katheten vorgegeben haben, müssen wir den Winkel mittels Tangens berechnen. Also der Tangens vom Winkel angle ist gleich Gegenkathete durch Ankathete. Da wir den Winkel aber als Ergebnis erhalten möchten, brauchen wir die Umkehrfunktion vom Tangens. Also die Formel wie sie oben in der Skizze steht. Des weiteren sollte aufgefallen sein, dass man den Quotienten steer durch velocity nicht berechnen kann, wenn die Geschwindigkeit gleich Null ist. Dies ist aber auch nicht schlimm, da das Fahrzeug, wenn es nicht rollt, sich auch nicht drehen können muss. Es ist nur darauf zu achten, das mit einer if-Bedingung die Division durch Null verhindert. Dies ist mit if (velocity != 0) einfach getan. Der alte Winkel war 0° und der neue ist jetzt ca. 36,87°. Wenn das Fahrzeug weiter lenkt, dann wird der wiederum neu berechnete Wert auf den alten aufaddiert. Damit der Winkel immer zwischen 0° und 360° liegt, benötigt man noch zwei weitere Bedingungen. Einmal, falls der Winkel negativ werden sollte (in der Skizze oben z.B. genau in die andere Richtung lenkt, also steer = -3), wird zu dem negativen Winkel 360° aufaddiert. Die zweite Bedingung ist, falls der Winkel größer als 360° wird, wird 360° von dem zu großen Winkel subtrahiert. Wenn man jetzt noch präziser vorgehen möchte, dann ist der Wendekreis auch noch abhängig vom Radstand. Je größer der Radstand (das bedeutet der Abstand zwischen Vorderachse und Hinterachse), desto größer auch der Wendekreis. In meiner Skizze habe ich darauf verzichtet um sie nicht zu kompliziert zu machen, im Quellcode aber berücksichtigt und mit einem Kommentar auch kurz erklärt. Daniel Bogdan Grafische Interaktive Systeme Seite 13 Nun haben wir den neuen Winkel und benötigen nur noch die neue Position des Fahrzeuges. z radius = sqrt (velocity² + steer²) radius = sqrt (4² + 3²) radius = 5 x = raduis * cos (angle) z = radius * sin (angle) steer = 3 velocity = 4 x Dazu berechnen wir erst einmal die Länge des Vektors radius. Da wir ein rechtwinkliges Dreieck haben, können wir den Satz des Pythagoras anwenden. Die beiden Katheten quadrieren, dann zusammenaddieren und daraus die Quadratwurzel gezogen. Schon haben wir den Betrag des Vektors radius. Die letzte Aufgabe ist es noch, die genaue Position zu ermitteln. Der Cosinus von dem Winkel angle mal des eben errechneten radius ergibt die x-Koordinate und der Sinus von dem Winkel angle mal dem radius ergibt die z-Koordinate. Die neu errechneten Koordinaten werden dann noch auf die alten addiert, analog wie beim Winkel. Dies war die Kernberechnung des Fahrzeugmodells. Die anderen Berechnungen empfand ich als nicht so wichtig, daher führe ich sie hier auch nicht weiter auf. Sie sind aber im Quellcode kommentiert. 4.1.8 3D-Modelle importieren Daniel Bogdan Grafische Interaktive Systeme Seite 14 Um 3D-Modelle vom Typ 3DS oder OBJ zu importieren, benötigt man eine Variable vom Typ GLModel. Diese Klasse wird auch von der Zusatzbibliothek von [POTATO] bereitgestellt. In der init() - Methode erzeugt man sich dann ein neues Objekt von der Klasse GLModel und gibt als Parameter den Pfad zu dieser 3DS- oder OBJ-Datei. Hierbei ist zu beachten, dass der Pfad diesmal von dem src-Ordner ausgeht und nicht von dem Paket „lib“. Obwohl die Klasse GLModel genauso wie die Klasse GLImage im Paket „lib“ liegt. Der Aufruf sehe bei mir dann wie folgt aus: private GLModel car; ... public void init () { ... car = new GLModel ("res/models/AUDI_5.2_FSI/R8.obj"); ... } Obwohl das Fahrzeug [AUDI] eigene Texturen mitgeliefert hat, werden diese nicht mit eingebunden. Ich habe versucht den Grund dafür herauszufinden, aber dies wurde mir dann zu komplex und die vorgegebene Zeit knapp. Man kann zwar eine Textur auf das Modell legen, das aber dann nur ein Bild ist und dementsprechend nicht schön aussieht. Der Aufruf hieße aber car.setTexture (carTexHandle); und stünde in der init() - Methode direkt nach dem Erzeugen des Objektes. Was jetzt noch fehlt, ist in der render() - Methode das Objekt zu zeichnen. Da ich schon Texturen verwendet habe und die für das Objekt dann auch benutzt werden, obwohl ich das nicht will (siehe Problem vom Unterpunkt Texturen auf Objekte), muss ich für diesen Moment die Texturen deaktivieren. Der Quellcode sehe dann folgendermaßen aus: public void render () { GL11.glDisable(GL11.GL_TEXTURE_2D); GL11.glPushMatrix (); { ... /* Evtl hier die Materialeigenschaften */ ... /* Hier stehen dann die Translations-, */ ... /* Rotations-, und Skalierungsmatrizen */ car.render (); } GL11.glPopMatrix (); GL11.glEnable(GL11.GL_TEXTURE_2D); } Die Transformationsmatrix, die als erstes ausgeführt werden soll, steht den Geometrie- Daniel Bogdan Grafische Interaktive Systeme Seite 15 Daten am nächsten, also am weitesten unten. Da ich dem Objekt keine Textur gebe, heißt das noch lange nicht, dass es lediglich weiß aussehen muss. Mit der Materialeigenschaft kann ich dem Objekt auch eine andere Farbe verpassen. 4.1.9 Screenshots Daniel Bogdan Grafische Interaktive Systeme Seite 16 4.2 Fazit Das ist nun das fertige Programm. Dies ist zwar kein eigenständiges Spiel, aber es wird deutlich, was alles mit der Grafik-Bibliothek machbar ist. Theoretisch kann auf das Programm noch weiter aufgebaut und es in allen Bereichen erweitert werden. Während der Semesterferien werde ich sicher weiter daran arbeiten. Jedenfalls weiß ich jetzt wie sich Spieleentwickler fühlen. Am Anfang nimmt man sich so viel vor und am Ende reicht die Zeit nicht. Da ich die vorgegebene Zeit nicht verlängern konnte, war ich eben gezwungen einige Sachen streichen. Ich wollte zudem SoundDateien einbinden, die man an einer bestimmten Stelle in der 3D Welt hören kann und immer leiser werden, wenn man sich weiter entfernt. Des weiteren wollte ich auch Text auf dem Bildschirm ausgeben lassen, wie schnell das Fahrzeug beispielsweise fährt. Also eine Art Tachometer. Dies hatte mich schon ein wenig verärgert, aber ließ sich leider nicht ändern. Spaß hat es trotzdem bereitet und ich würde mich auch immer wieder freiwillig daran setzen. Ich hoffe, ich konnte mit meiner Ausarbeitung verdeutlichen, dass es gar nicht mal so schwer ist, eine 3D Welt zu erzeugen und in dieser auch interaktiv zu agieren. Vorausgesetzt, man arbeitet mit den vorgefertigten Bibliotheken. Das Rad neu zu erfinden ist anstrengend und macht auch keinen Sinn. Als ich Anfang des Jahres meine Quellen überprüfte, habe ich festgestellt, dass diese Zusatzbibliothek von [POTATO] enorm überarbeitet wurde. Sie wurde auch auf die Version 2.0 von der LWJGL angepasst. Als ich mir ein paar Demos angesehen hatte, war ich fast schon enttäuscht. Nicht, weil die Weiterentwicklung schlecht war, sondern eher im Gegenteil. Der erste Eindruck von dem Quellcode hat mir gezeigt, dass man mit weniger Aufwand noch mehr erreichen konnte. Zum Beispiel funktionierte in der einen Demo das korrekte Laden der Textur für ein 3D-Modell oder die Schatten-Berechnung wurde mit integriert. Ich habe mich dann gefragt, warum dies nicht schon vor drei Monaten heraus gekommen ist. Das hätte mir jede Menge Zeit erspart. Aber nun gut, ich denke, dass es dadurch auch für die nachfolgenden Studierenden ein Anreiz ist, in diese Richtung weiter zu entwickeln. Denn den Vorteil wird es weiterhin besitzen, dass Java und OpenGL plattformunabhängig und nicht nur auf Windows ausführbar sind. Daniel Bogdan Grafische Interaktive Systeme Seite 17 4.3 Quellen [LOGO] Das Logo von LWJGL auf der Titelseite http://upload.wikimedia.org/wikipedia/de/6/6c/LWJGL.png [LWJGL] Die Seite, von der ich die eigentliche Grafik-Bibliothek runter laden konnte. Zu finden gibt es da auch noch viel Informationen und abspielbare Demos http://lwjgl.org/ [LWJGL_C] Auf dieser Seite sieht man die gesamten Änderungen von allen LWJGL Versionen. http://lwjgl.org/changelog.php [POTATO] Die Seite, von der ich die Zusatzbibliothek bekommen habe und jede Menge Beispielcode. Leider existiert die Art von Version nicht mehr, die ich hier benutzt habe, aber ich habe die Zip-Datei mit auf den Datenträger gepackt. http://www.potatoland.com/code/gl/ [AUDI] Das Fahrzeugmodell Audi, das ich in meinem praktischen Beispiel benutze http://gfx-3d-model.blogspot.com/2008/12/audi-r8-52-fsi-car-model.html [TEXTUR] Jede Menge gute und kostenlose Texturen http://www.mediacastle.de/texturen/metalltexturen/index.html [3D_MOD] Eine Seite, auf der es verschieden 3D Modell kostenlos zum runter laden gibt http://www.blogcatalog.com/blogs/free-3d-models-for-maya-andmax/posts/tag/vehicles/ Daniel Bogdan Grafische Interaktive Systeme Seite 18 5. Anhang 5.1 Anwenderdokumentation Das Programm findet man im Unterordner Programm und startet man über die run.bat Datei. Folgende Befehle sind enthalten: Taste Beschreibung A Beschleunigt das Fahrzeug Y Bremst das Fahrzeug , Lenkt nach links . Lenkt nach rechts NUM7 Kamera nach oben blicken (nur bei Frei-Sicht-Modus (F2)) NUM9 Kamera nach unten blicken (nur bei Frei-Sicht-Modus (F2)) NUM4 Kamera nach links um das Fahrzeug schwenken NUM6 Kamera nach rechts um das Fahrzeug schwenken NUM1 Kamera dreht sich nach links um die eigene Achse (nur bei Frei-Sicht-Modus (F2)) NUM3 Kamera dreht sich nach rechts um die eigene Achse (nur bei Frei-SichtModus (F2)) NUM8 Kamera bewegt sich nach oben NUM2 Kamera bewegt sich nach unten NUM- Kamera entfernt sich vom Objekt (nur bei Verfolgungs-Modus (F2)) NUM+ Kamera nähert sich dem Objekt (nur bei Verfolgungs-Modus (F2)) F2 Umschalten zwischen Verfolgungs-Modus und Frei-Sicht-Modus Pfeil oben Kamera bewegt sich nach vorne Pfeil unten Kamera bewegt sich nach hinten Pfeil links Kamera bewegt sich nach links Pfeil rechts Kamera bewegt sich nach rechts N Nebel wird eingeschaltet L Licht in der Garage wird eingeschaltet Um die Tastaturbelegung zu ändern, muss man in der Klasse Control die Methode setKey() finden. Ich wollte es eigentlich über das Programm einstellen lassen können, aber die Zeit hat nicht mehr gereicht. Daniel Bogdan Grafische Interaktive Systeme Seite 19 5.2 Eingesetzte Werkzeuge Eingesetztes Betriebssytem: Windows XP Pro Service Pack 3 Wurde für alle Arbeiten eingesetzt. Eingesetzte Hardware: Prozessor: AMD Athlon 3200+ Arbeitsspeicher: 1536 MB Grafikkarte: NVIDIA GeForce 6800 mit 128 MB Speicher Eingesetzte Software: Eclipse Version 3.3.0 mit JRE 1.6.0_07 Wurde für die ganze Programmierarbeit benutzt. LWJGL Version 2.0 Als Grafik-Bibliothek, um mit Java auf OpenGL Befehle zugreifen zu können. Ein paar Klassen von [email protected] In der Ausarbeitung als Zusatzbibliothek benannt. Mozilla Firefox Version 3.0.5 Wurde zum suchen nach Beispielcode benutzt. Und für sonstige Informationen zu beschaffen. Open Office Version 2.4 Wurde benutzt um die Ausarbeitung zu schreiben und die Präsentation zu erstellen. UMLet Version 9.03 Wurde benutzt um das Klassendiagramm zu zeichnen. Daniel Bogdan Grafische Interaktive Systeme Seite 20 5.3 Darstellung des Aufwands 1% 19% 49% Prototypen Programmierarbeit Fehlersuche Ausarbeitung Anwenderdokumentation 10% 21% Bezeichnung Dauer Prototypen Programmierarbeit Fehlersuche Ausarbeitung Anwenderdokumentation Zusammen: Daniel Bogdan 70 Stunden 30 Stunden 15 Stunden 27 Stunden 2 Stunden Zeitraum Oktober – Dezember Dezember – Januar Oktober – Januar Dezember – Januar Januar 144 Grafische Interaktive Systeme Seite 21