Spiele-Entwicklung in Java - Online

Transcription

Spiele-Entwicklung in Java - Online
Spiele-Entwicklung in Java
Wolfgang Hochleitner
DIPLOMARBEIT
eingereicht am
Fachhochschul-Masterstudiengang
Digitale Medien
in Hagenberg
im Juni 2006
Copyright 2006 Wolfgang Hochleitner
Alle Rechte vorbehalten
ii
Erklärung
Hiermit erkläre ich an Eides statt, dass ich die vorliegende Arbeit selbstständig und ohne fremde Hilfe verfasst, andere als die angegebenen Quellen
und Hilfsmittel nicht benutzt und die aus anderen Quellen entnommenen
Stellen als solche gekennzeichnet habe.
Hagenberg, am 22. Juni 2006
Wolfgang Hochleitner
iii
Inhaltsverzeichnis
Erklärung
iii
Vorwort
vii
Kurzfassung
viii
Abstract
ix
1 Einleitung
1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Problemstellung . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3 Aufbau dieser Arbeit . . . . . . . . . . . . . . . . . . . . . . .
2 Spiele-Entwicklung allgemein
2.1 Was ist Spiele-Entwicklung? . . . . . . .
2.1.1 Game-Design . . . . . . . . . . .
2.1.2 Game-Programming . . . . . . .
2.2 Grafik-Schnittstellen und Engines . . . .
2.2.1 OpenGL . . . . . . . . . . . . . .
2.2.2 Direct3D . . . . . . . . . . . . .
2.2.3 Middleware . . . . . . . . . . . .
2.3 Wichtige Konzepte . . . . . . . . . . . .
2.3.1 Szenengraphen . . . . . . . . . .
2.3.2 Scene-Management . . . . . . . .
2.3.3 BSP-Bäume . . . . . . . . . . . .
2.4 Existierende Arbeiten auf diesem Gebiet
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3 Ansätze in Java
3.1 Grafik-Schnittstellen und Engines . . . . . . . . . . . . . . .
3.1.1 Java 3D . . . . . . . . . . . . . . . . . . . . . . . . .
3.1.2 OpenGL-Anbindungen für Java . . . . . . . . . . . .
3.1.3 Middleware-Anbindungen und Szenengraphen-APIs
3.2 Existierende 3D-Java-Spiele . . . . . . . . . . . . . . . . . .
iv
1
1
2
3
.
.
.
.
.
.
.
.
.
.
.
.
5
5
5
7
9
9
10
12
13
13
16
18
20
.
.
.
.
.
22
22
22
27
29
30
INHALTSVERZEICHNIS
v
4 JSceneViewer3D
4.1 Architektur . . . . . . . . . . . . . .
4.2 Basis-Applikation . . . . . . . . . . .
4.3 Level-Loader . . . . . . . . . . . . .
4.3.1 Auslesen der Lump-Daten . .
4.4 Rendering . . . . . . . . . . . . . . .
4.4.1 Erstellung des Szenengraphen
4.4.2 Darstellung der Szene . . . .
4.5 Logger . . . . . . . . . . . . . . . . .
4.5.1 log4j . . . . . . . . . . . . . .
4.5.2 Implementierung des Loggers
4.5.3 Aufrufe des Loggers . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33
33
35
39
42
47
48
52
55
55
56
57
5 Auswertung der Performance-Daten
5.1 Test-Voraussetzungen und Ablauf . .
5.1.1 Die getesteten Levels . . . . .
5.2 Tests mit JSceneViewer3D . . . . . .
5.3 Tests mit Quake III Arena . . . . . .
5.4 Vergleich und Fazit . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
58
58
60
60
65
68
6 Evaluierung und Schlussbemerkungen
6.1 Evaluierung . . . . . . . . . . . . . . .
6.1.1 Verfügbarkeit von Software . .
6.1.2 Produktivität . . . . . . . . . .
6.1.3 Performance . . . . . . . . . .
6.2 Fazit . . . . . . . . . . . . . . . . . . .
6.3 Ausblick . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
71
71
71
73
74
74
75
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
76
76
77
77
78
78
78
78
79
79
80
80
80
80
81
81
A Quake III Level Format
A.1 Dateistruktur . . . . . . . . . .
A.2 Datentypen . . . . . . . . . . .
A.3 Header und Verzeichniseinträge
A.4 Lumps . . . . . . . . . . . . . .
A.4.1 Entities (Entitäten) . .
A.4.2 Textures (Texturen) . .
A.4.3 Planes (Ebenen) . . . .
A.4.4 Nodes (Knoten) . . . .
A.4.5 Leafs (Blätter) . . . . .
A.4.6 Leaffaces . . . . . . . .
A.4.7 Leafbrushes . . . . . . .
A.4.8 Models (Modelle) . . . .
A.4.9 Brushes . . . . . . . . .
A.4.10 Brushsides . . . . . . .
A.4.11 Vertexes (Vertices) . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
INHALTSVERZEICHNIS
A.4.12
A.4.13
A.4.14
A.4.15
A.4.16
A.4.17
vi
Meshverts . . . . . . . . . . . . . . .
Effects (Effekte) . . . . . . . . . . .
Faces . . . . . . . . . . . . . . . . .
Lightmaps . . . . . . . . . . . . . . .
Lightvols (Beleuchtungsdaten) . . .
Visdata (Sichtbarkeitsinformationen)
B Inhalt der DVD
B.1 Diplomarbeit . . .
B.2 Literatur . . . . . .
B.3 Performance-Daten
B.3.1 Log-Dateien
B.3.2 Videos . . .
B.4 Quellcode . . . . .
Literaturverzeichnis
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
81
82
82
83
83
84
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
85
85
85
86
86
87
87
88
Vorwort
Diese Diplomarbeit entstand im vierten Semester des Studiengangs Digitale
Medien (Schwerpunkt Computer Games) an der Fachhochschule Oberösterreich, Campus Hagenberg. Sie stellt den Abschluss des Magisterstudiums
dar und steht am Ende einer insgesamt 5-jährigen Ausbildung, die ich an
dieser Hochschule absolvieren konnte.
Ich möchte mich an dieser Stelle bei jenen Personen bedanken, die mich
beim Schreiben dieser Arbeit und auch während meines gesamten Studiums
unterstützt haben.
Mein Dank geht hierbei vor allem an meine Eltern Fritz und Maria, die
mir zu jeder Zeit den nötigen Rückhalt gegeben haben. Weiters danke ich
meiner Freundin Christina Köffel, die in all der Zeit immer zu mir gestanden
ist und meinen Blick immer nach vorne gerichtet hat.
Mein abschließender Dank ergeht an meinen Betreuer Roman Divotkey,
der mir bei der Umsetzung des Projekts und der Aufarbeitung des in der
Arbeit beschriebenen Themas stets hilfreich zur Seite gestanden ist.
vii
Kurzfassung
Die vorliegende Arbeit befasst sich mit dem Thema Spiele-Entwicklung unter Verwendung der Programmiersprache Java. Zu Beginn wird eine allgemeine Definition des Begriffs Spiele-Entwicklung“ gegeben. Dabei wird vor
”
allem auf die wichtigen Teilbereiche Game-Design und Game-Programming
sowie die entstehenden Rollenbilder eingegangen. In weiterer Folge wird der
technische Stand der Dinge bei der Umsetzung von Spielen erklärt. Das
Hauptaugenmerk liegt auf den Grafik-Schnittstellen OpenGL und Direct3D
sowie diverser Middleware. Im Anschluss werden bestehende Arbeiten zum
gewählten Thema aufgelistet.
Es wird ein Überblick über existierende Grafik-Anbindungen und Spiele
in Java gegeben, bevor die Umsetzung einer Applikation zum Laden von
3D-Spiele-Levels (JSceneViewer3D) beschrieben wird. JSceneViewer3D lädt
beliebige Welten des Spiels Quake III Arena und stellt diese mittels Java 3D
dar. Performance-Daten wie die Framerate oder die Polygonanzahl werden
vom Programm gespeichert. Die Arbeit präsentiert die Ergebnisse diverser
Testläufe der Applikation und vergleicht sie mit Werten des Original-Spiels.
viii
Abstract
The present work describes the process of game development using the programming language Java. A general definition of the term “game development” is given by mainly focussing on the areas game design and game
programming in addition to describing the resulting roles. In what follows
the technical state of the art of realizing games is described. The particular emphasis is placed on the graphical interfaces OpenGL and Direct3D as
well as on middleware. In the subsequent section existing work on the chosen
topic is presented.
A review of existing Java graphic-bindings and Java games is given prior
to the implementation process of JSceneViewer3D–an application for loading
3D game levels. JSceneViewer3D loads maps of the computer game Quake
III Arena and displays them by using Java 3D technology. The program also
stores performance data such as the frame rate or number of polygons. The
results of various test runs are presented and compared with data from the
original game.
ix
Kapitel 1
Einleitung
1.1
Motivation
Für den Endanwender und somit Käufer zählen bei einem Computerspiel
grob unterteilt zwei Dinge: Der gute Inhalt und die gute Umsetzung. Blickt
man nun hinter die Kulissen und wirft einen Blick auf eben jene Umsetzung, so ist in den Bereichen Konsole, PC und Automaten eindeutig eine
Dominanz von Spielen, die in den Programmiersprachen C oder C++ entwickelt wurden, zu erkennen. In diversen Artikeln rund um das Thema
Spiele-Entwicklung finden sich immer wieder Werte um 99,5 Prozent zugunsten der zuvor genannten Sprachen. Genaue statistische Daten gibt es
keine, dennoch dürfte der Wert durchaus der Realität entsprechen. Lediglich
in den Bereichen Mobile- und Online-Gaming sind Spiele, die auf der JavaTechnologie basieren, sehr weit verbreitet. Diese Prozentzahlen betreffen übrigens einen Markt, der allein in den USA im Jahr 2005 einen Gesamtumsatz
von 248 Millionen Dollar bei Computer- und Videospielen erzielt hat [3].
Nun drängt sich berechtigterweise die Frage auf, warum sich Java in
den klassischen Bereichen der Spiele-Entwicklung nicht durchzusetzen vermag. Eine unter Buch-Autoren und Java-Experten verbreitete Meinung ist
der schlechte Ruf von Java im Bezug auf Geschwindigkeit und Performance
(Performanz, Leistungsmerkmale – siehe dazu z. B. [2] oder [21]). Weitere
gern genannte Argumente sind eine oftmals falsche Auffassung von Java als
Sprache ausschließlich für mobile Anwendungen und das Web oder die Tatsache, dass Java nicht für Konsolen verfügbar ist. Oft ist auch zu lesen, dass
Java im Bereich der Spiele-Entwicklung ohnehin bereits tot“ sei.
”
Das Ziel dieser Diplomarbeit ist es, anhand eines praktisch umgesetzten Beispiels herauszufinden, wie sehr sich Java – ungeachtet der soeben
genannten Vorurteile – für den Einsatz im Spielebereich eignet.
1
KAPITEL 1. EINLEITUNG
1.2
2
Problemstellung
Um den kompletten Prozess der Spiele-Entwicklung zu durchlaufen, wäre
die Erstellung eines völlig neuen Spiels in Java sicherlich die beste Option.
Da es sich bei dieser Diplomarbeit und dem dazugehörigen Projekt jedoch
um eine Einzelarbeit handelt, wäre die daraus resultierende Menge an Arbeit
nicht in einem bzw. zwei Semestern zu bewältigen gewesen. Um jedoch trotzdem aussagekräftige Ergebnisse zu bekommen, wurde ein Teil eines Spiels
herausgegriffen und realisiert.
Dabei fiel die Auswahl auf das Laden und Darstellen eines 3D-SpieleLevels. Nach einer Evaluierung der gängigsten Spiele wurde Quake III Arena
ausgewählt. Gründe dafür waren vor allem die gute Verfügbarkeit von Informationen über das Level-Format (siehe Anhang A) und das Vorhandensein
von existierenden Lösungen in diesem Bereich, an denen eine Orientierung
möglich war. Keine dieser Lösungen realisierte allerdings den hier im Endeffekt gewählten Ansatz.
Die Geometrie dieser Levels ist bereits beträchtlich komplexer als die
simpler geometrischer Primitive. Neben der Darstellung von Polygonen (ebenen Flächen) ist auch die Berechnung von gekrümmten Flächen im Raum
(Bézier Patches) erforderlich. Zusätzlich werden diese Flächen texturiert und
Lightmaps kommen zum Einsatz. Zieht man dies in Betracht, so spielt die
Performance durchaus eine Rolle, denn schließlich ist eine hohe Framerate
(Bilder pro Sekunde) eines der Hauptkriterien für eine zufriedenstellende
Echtzeit-Darstellung von 3D-Szenen.
Als daraus resultierendes Ziel ergibt sich nun, beliebige Levels dieses
Spieles in Java zu laden und mit einem Java API (Java 3D oder eine Java
OpenGL Anbindung) performant darzustellen. Als API wurde Java 3D gewählt, da es sehr stark die Java-Philosophie propagiert: Einfachheit, Sicherheit, allerdings in manchen Fällen auch nicht volle bzw. direkte Kontrolle.
Eine genauere Erläuterung zur Funktionsweise von Java 3D findet sich in
Abschnitt 3.1.1. Java OpenGL Anbindungen werden im Abschnitt 3.1.2 näher erläutert.
Eine weitere Grundanforderung an die Applikation ist eine einfache Erweiterungsmöglichkeit. Es soll darauf geachtet werden, dass es ohne viel Aufwand möglich ist, auch andere Level-Formate zu unterstützen, d. h. zu laden
und darzustellen. Für die vorliegende Version wurde jedoch aufgrund der
am Anfang dieses Abschnitts angeführten zeitlichen Begrenzung nur Quake
III mit Java 3D Darstellung realisiert.
So stand am Beginn des Projektes die Einarbeitung in das Java 3D API
sowie in das Quake III Level-Format. Da für Java 3D kein Quake III LevelLoader vorhanden war, musste dieser von Grund auf konzipiert und realisiert
werden. Zwar gab es bereits bestehende Loader, diese waren jedoch alle für
C++ oder Java OpenGL Anbindungen vorhanden. Daher konnten sie nur
bedingt als Ausgangsbasis bzw. Vorlage dienen.
KAPITEL 1. EINLEITUNG
3
Schließlich soll die fertige Applikation auch Auskunft darüber geben können, wie performant die von ihr erzeugte Darstellung auch tatsächlich ist.
Dazu misst sie verschiedene Daten und fasst diese in einer Log-Datei zusammen, welche im Anschluss ausgewertet werden kann.
Die genaue Umsetzung des Projektes wird im Laufe dieser Arbeit behandelt und dient als Veranschaulichung, wie die Entwicklung eines Spieles
in Java gehandhabt werden kann.
1.3
Aufbau dieser Arbeit
Die vorliegende Arbeit soll den Leser schrittweise an den Spiele-Entwicklungsprozess in Java heranführen.
Kapitel 2 dient als Einführungskapitel und beschreibt einige allgemeine
Aspekte zum Thema Spiele-Entwicklung. Zunächst erfolgt eine Erklärung
des Begriffs an sich, da dieser im Sprachgebrauch zwar durchaus oft, aber
auch immer wieder falsch verwendet wird. Anschließend erfolgt eine Erläuterung zu den gängigsten Grafik Schnittstellen, um den so genannten
State of the Art“ in der Spiele-Entwicklung aufzuzeigen. Nachdem die tech”
nischen Details geklärt wurden, wird noch auf einige wichtige Konzepte bei
der Umsetzung von Spielen eingegangen. Diese sind Szenegraphen, SzenenManagement und im Besonderen BSP-Bäume. Diese Konzepte sind vor allem für das Verständnis vom Aufbau von Quake III Levels von besonderer
Bedeutung. Ebenfalls in diesem Kapitel enthalten ist eine Übersicht über
bereits bestehende Arbeiten auf diesem Gebiet, die sich auch mit SpieleEntwicklung in Java beschäftigen.
Kapitel 3 dient als Fortführung von Kapitel 2, es wird jedoch spezifisch auf Java und seine Konzepte eingegangen. Hierbei werden wiederum
die gängigsten Schnittstellen zur Darstellung vorgestellt, auch so genannte
Java-Middleware wird hierbei angesprochen. Abgeschlossen wird dieses Kapitel mit einer Übersicht über aktuelle Spiele in Java, um zu zeigen, welche
praktisch umgesetzten und auch kommerziell verwertbaren Ergebnisse auf
dem Gebiet dieser Diplomarbeit bereits erzielt wurden.
In Kapitel 4 wird dann genau auf die Realisierung der eigenen Applikation eingegangen. Hierbei wird der gesamte Entwicklungsprozess beschrieben
und erläutert, wie von einem vorgegebenen Quake III Level die endgültige
Darstellung am Bildschirm erreicht wurde. Dieses Kapitel setzt die vorigen
Teile der Diplomarbeit voraus und nimmt immer wieder Bezug auf darin
enthaltene Konzepte.
Die von der Applikation generierten Performance-Daten werden in Kapitel 5 analysiert und mit Daten des Originalspiels verglichen. Dieses Kapitel
soll durch Zahlen und Fakten genaueren Aufschluss darüber geben, ob Java
für einen Einsatz im Spielebereich geeignet ist oder nicht.
Schließlich soll über die gewonnenen Erkenntnisse in Kapitel 6 resümiert
KAPITEL 1. EINLEITUNG
4
werden und eine konkrete Antwort auf die hier in der Einleitung gestellten
Fragen gegeben werden. Abgeschlossen wird dieses Kapitel durch Ausblicke
auf zukünftige Arbeiten oder Weiterentwicklungen am bestehenden Projekt.
Kapitel 2
Spiele-Entwicklung allgemein
2.1
Was ist Spiele-Entwicklung?
Befasst man sich mit dem Thema Spiele-Entwicklung – egal ob in kreativer
oder technischer Hinsicht – ist es wichtig, immer den Gesamtprozess vor Augen zu haben. Ein Computerspiel entsteht nicht allein am Papier, genauso
wenig aber wird es entwickelt, in dem man sich mit einer Idee vor den Computer setzt und zu programmieren beginnt. Vielmehr ist Spiele-Entwicklung
ein durchgehender und mitunter auch langwieriger Prozess, der mit einer
Idee beginnt, sich in der genauen Formulierung der selben fortsetzt und mit
der Implementierung endet.
2.1.1
Game-Design
Spiel ist eine freiwillige Handlung oder Beschäftigung, die innerhalb gewisser Grenzen von Zeit und Raum nach freiwillig angenommenen, aber unbedingt bindenden Regeln verrichtet wird,
ihr Ziel in sich selber hat und begleitet wird von einem Gefühl der
Spannung und Freude und einem Bewusstsein des Andersseins“
”
als das gewöhnliche Leben“.
”
Diese Definition nach Huizinga [9] war Ende der 1930er Jahre, als sie entstand, natürlich noch nicht auf Computerspiele ausgerichtet. Dennoch gelten
diese Richtlinien hier ebenso, da lediglich das Medium, das als Grundlage
für das Spiel dient, ein anderes bzw. neues ist.
Bevor jedoch Regeln und Ziele eines Spieles festgelegt werden können,
muss eine Idee vorhanden sein. Diese ist im Normalfall noch vage formuliert
und existiert zumeist auch nur im Kopf. Bis zum fertigen Produkt ist es
von hier an noch ein sehr weiter Weg. Doch je genauer jener Weg bereits
im Anfangsstadium geplant wird, desto schneller kann er bewältigt werden.
Diese Planung ist das Game-Design. Grob gesagt beinhaltet es alles, das
nicht technisch ist, also nichts mit der Umsetzung (d. h. Programmierung) zu
5
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
6
tun hat. Genauer gesagt bedeutet dies, die Grundidee des Spiels so detailliert
auszuführen und nieder zu schreiben, dass auf der Basis des so entstandenen
Dokuments die technische Umsetzung erfolgen kann. Rollings und Adams
[18] definieren Game-Design als Prozess, der aus folgenden Teilen aufgebaut
ist:
Ein Spiel ausdenken.
Definieren, wie es funktioniert.
Die Elemente, die das Spiel bestimmen (konzeptuelle, funktionale,
künstlerische und andere), beschreiben.
Diese Informationen dem Team, welches das Spiel umsetzen wird, weiterleiten.
Aus dieser Definition ist erkennbar, dass die Entwicklung eines Spieles
kein Prozess ist, der von einer Person alleine abgewickelt wird. Vielmehr
arbeiten an einem modernen Computerspiel unzählige Personen in einem
Team zusammen. Damit alle diese Personen wissen, wie das gemeinsame
Ziel aussieht, ist eine lückenlose Definition und Dokumentation der Spielidee unabdingbar. Dafür haben sich im Game-Design typischerweise drei
Typen von Design-Dokumenten etabliert, die das Spiel in unterschiedlichen
Detailgraden beschreiben.
Das High-Concept beschreibt auf etwa zwei bis vier Seiten die Grundzüge des Spiels. Dazu gehören primär die Handlung und das Gameplay (was
muss der Spieler tun, wie wird sich das Spiel entwickeln), aber auch eher
nüchterne Details wie das Genre, das Zielpublikum oder die Plattform, für
die das Spiel entwickelt werden soll. Am wichtigsten im High-Concept ist
jedoch das Verdeutlichen von so genannten Unique Selling Propositions“,
”
also Dingen, die das Spiel einzigartig machen. Diese sollen ins Auge stechen
und klar machen, warum es sich lohnt, dieses Spiel zu entwickeln und zu verkaufen. Somit dient das High-Concept Dokument auch als erster Überblick
für potentielle Produzenten oder Publisher.
Eine wesentlich genauere Spezifizierung des Spiels stellt in weiterer Folge
dann das Game-Treatment dar. Es umfasst in der Regel bereits zehn bis
zwanzig Seiten, beschreibt das Spiel jedoch immer noch nicht bis in alle Einzelheiten. Es enthält bereits genug Details, um als Verkaufswerkzeug zu dienen. Am Anfang steht eine Zusammenfassung der wichtigsten Dinge, gefolgt
von den Inhalten des High-Concept Dokuments – jedoch wesentlich genauer
ausgeführt. Die Hintergrund-Geschichte zum Spiel wird ebenso genau erklärt
wie die Handlung im Spiel, Charakter-Entwicklung und das zu erreichende
Spielziel. Spezielles Augenmerk soll wiederum auf die Höhepunkte (Unique
Selling Propositions) gelegt werden. Weiters legt das Game-Treatment das
geplante Budget sowie einen Zeitplan für die Umsetzung dar. Auch das Entwicklungsteam wird beschrieben. Alles in allem kann und soll das Game-
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
7
Treatment das Spiel in einem möglichst positiven Licht darstellen, um die
Aufmerksamkeit potentieller Geldgeber zu erlangen.
Das dritte und auch wichtigste Game-Design Dokument ist das GameDesign-Script. Auf 50 bis 200 Seiten wird jeder Aspekt des Spiels bis ins
kleinste Detail definiert und beschrieben. Vorrangig dabei sind der genaue
Programmablauf und das Gameplay. Alles, was der Spieler im Spiel erfährt
und tun kann, muss erfasst werden. Alle möglichen Wege zum Ziel, müssen beschrieben werden. Alle Charaktere, alle Orte und Szenerien, alle Gegenstände und sonstigen Elemente der Spielwelt werden niedergeschrieben.
Auch das komplette Regelwerk des Spiels muss darin enthalten sein. Ein gutes Design-Script ermöglicht es, das Spiel mit Papier und Bleistift zu spielen,
ohne auf Ungereimtheiten zu stoßen.
Details zum Inhalt der drei Dokumente und Informationen zum Thema
Game-Design siehe [18].
2.1.2
Game-Programming
Nachdem die Design-Phase abgeschlossen ist und die Idee des Spiels in umfangreichen und detaillierten Dokumenten niedergeschrieben wurde, erfolgt
die Umsetzung dieser Aufgaben. Bei der Entwicklung eines modernen Computerspiels bedeutet dies jedoch nicht, dass auf dieser Basis sofort Code
geschrieben wird. Vielmehr ist noch ein weiterer Planungsschritt notwendig,
bevor mit der richtigen Umsetzung begonnen werden kann: Das ArchitekturDesign.
Es beschreibt, aus welchen Teilen das gesamte Spiel besteht und wie
diese zusammenhängen und -arbeiten. Dabei werden die Elemente aus dem
Game-Design auf technische Gegebenheiten abgebildet. Das Ergebnis dieser Abbildung ist eine Hierarchie, die sich immer feiner aufteilen lässt, je
tiefer man hinabsteigt. So steht an ihrer Spitze etwa das Spiel als großes
Ganzes. Die nächste Ebene wird durch die Blöcke Grafik, Sound, Netzwerk,
Physik und künstliche Intelligenz gebildet. Grafik etwa kann weiter in statische Objekte und bewegte Objekte unterteilt werden. Wie die Hierarchie
eines Architektur-Dokuments aufgebaut wird, obliegt dem jeweiligen Team,
welches diese konzipiert und auch umsetzt. Es gibt hierfür keine allgemein
gültigen Regeln. Viele nützliche Tipps und Erfahrungen in diesem Bereich
finden sich jedoch in [19].
Nachdem die Architektur des Spiels definiert wurde, muss noch die Technologie zur Umsetzung festgelegt werden. Dies betrifft die sehr hohen und
abstrakten Schichten der Architektur-Hierarchie wie Grafik, Sound, usw. In
großen Projekten werden diese Teile oft selbst umgesetzt, d. h., etwa GrafikEngines selbst entwickelt, um sie genauestens auf die Anforderungen des
Spiels ausrichten zu können. Für andere, meist kleinere Projekte, wird zu
diesem Zeitpunkt Research betrieben, um die bestmöglichen Komponenten für das Spiel zu finden. Die Auswahl der Technologie hängt dabei vor
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
8
allem vom Typ des Spiels und seinem Gameplay ab. Ein klassischer FirstPerson-Shooter benötigt eine Grafik-Engine, die möglichst effizient Levels
in geschlossenen Räumen darstellen kann. Für ein Spiel im freien Weltraum
hingegen gelten natürlich völlig andere Anforderungen. Schließlich kommen
noch finanzielle Aspekte hinzu, wenn es um die Auswahl und den Zukauf
von externen Produkten geht.
Ist die Architektur festgelegt und sind die verwendeten Technologien
ausgewählt, kann die eigentliche Umsetzung beginnen. Da diese bei größeren Projekten fast ausnahmslos in einem Team abläuft, sind auch hier
gewisse Regeln und Voraussetzungen notwendig, um einen effizienten Ablauf zu ermöglichen. Vorweg sei angenommen, dass alle Mitglieder des Entwicklungsteams das Programmieren, also die Fähigkeit, Probleme und Aufgabenstellungen algorithmisch zu lösen, beherrschen. Weiters sei angenommen, dass auch die verwendete Programmiersprache (Java, C++, usw.) beherrscht wird. Zu den wichtigsten Dingen beim Game-Programming zählt
die Sourcecode-Qualität. Nur sauber geschriebener und einheitlicher Code
garantiert ein qualitativ hochwertiges Endprodukt. Um dies zu erreichen
sollten Code-Richtlinien verwendet werden, die eine durchgehende Formatierung festlegen. Neben der Qualität des Codes ist es auch wichtig, bestimmte
Prioritäten für diesen festzulegen. Solche können Geschwindigkeit, Größe,
Flexibilität, Portabilität oder Wartbarkeit sein. Je nach Projekt werden bestimmte Prioritäten vorausgesetzt bzw. forciert. Sie können sich aber auch
je nach Projektphase ändern.
Trotz bester Architektur und umfangreicher Richtlinien passieren in der
Programmierung natürlich noch Fehler. Um jene frühzeitig zu erkennen, ist
ein ausgewogener Zyklus bestehend aus Code-Schreiben und Code-Testen
notwendig. Um einzelne Module auf Fehler zu testen, eignen sich so genannte
Unit-Tests. Diese sind zwar aufwändig zu erstellen, zahlen sich trotzdem oft
aus, da dem Entwickler eine langwierige Fehlersuche erspart bleibt. Grundsätzlich sollten fertige Code-Stücke sofort getestet werden. Oft stehen diese
aber in unzähligen Abhängigkeiten, die ein solches Vorhaben erschweren.
Wirft man nun einen Blick auf den aktuellen Abschnitt, so erkennt man
sehr schnell, dass der Bereich des Game-Programmings nichts anderes ist
als konventionelle Software-Entwicklung. Es erfordert viel Disziplin und ist
meist nicht so spektakulär, wie es der Ausdruck Game-Programming“ zu”
nächst vermuten lässt. Dennoch ist es der Teil der Spiele-Entwicklung, der
die meisten Ressourcen (Zeit, Personen, Kapital) verschlingt und deswegen keinesfalls auf die leichte Schulter genommen werden sollte. In diesem
Zusammenhang sei erneut auf [19] verwiesen, aus dem gute Tipps und Anregungen für den gegenwärtigen Abschnitt stammen.
Am Ende dieses Abschnitts sei noch erwähnt, dass die Überblicke über
Game-Design und Game-Programming natürlich nicht die einzigen zwei Teilbereiche der Spiele-Entwicklung sind. Auch besteht das Entwicklungsteam
nicht nur aus Designern und Entwicklern. Vielmehr sind an der Entwicklung
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
9
eines Spiels wesentlich mehr Personen beteiligt, als hier erwähnt wurden. So
gibt es etwa Studio Manager, Producer, Project Manager, Product Manager, Quality Assurance Manager, Tester oder Sound Artists, um nur einige
typische Berufsbilder zu nennen.
2.2
Grafik-Schnittstellen und Engines
Das Ziel dieser Arbeit ist es, einen kurzen Überblick über Spiele-Entwicklung
im Allgemeinen und detailliertere, implementierungsspezifische Ausführungen für die Umsetzung mittels Java zu geben. Wie bereits in den beiden vorhergehenden Abschnitten erklärt, ist Spiele-Entwicklung nicht ein einfaches
Herunterprogrammieren“ von Ideen aus dem Kopf. Genauso wenig besteht
”
Spiele-Entwicklung nur aus der grafischen Darstellung eines Spiels. Ein Spiel
entsteht vielmehr erst durch das Zusammenspiel von Grafik, Sound, Physik
und auch künstlicher Intelligenz. Dennoch liegt im Zuge dieser Arbeit und
auch des hier ab Kapitel 4 vorgestellten Projekts der Schwerpunkt auf der
grafischen Umsetzung. Die Gründe hierfür finden sich ebenfalls in einem späteren Kapitel dieser Arbeit. Die folgenden beiden Abschnitte über die zwei
wichtigsten Grafik APIs (Application Programming Interfaces) und einige
so genannte Middleware-Produkte sollen dazu dienen, vorab zu erklären, wie
die grafische Darstellung von 3D Daten vonstatten geht.
2.2.1
OpenGL
Eine sehr nüchterne und allgemeine Definition von OpenGL (kurz für Open
Graphics Library), die jedoch auch im Zusammenhang mit Direct3D verwendet werden kann, lautet Software Schnittstelle für Grafik-Hardware“.
”
Obwohl diese Definition zu generell ist, um sinnvoll verwendet werden zu
können, zeigt sie jedoch gleich auf, was OpenGL nicht ist: Eine eigene (Programmier) Sprache. Vielmehr es eine Ansammlung aus über 250 Befehlen,
die es erlauben, komplexe Szenen aus einfachen Grundprimitiven zu erstellen. OpenGL ist im eigentlichen Sinne nur eine Spezifikation jener Befehle,
die genau definiert, welches Verhalten diese auszulösen haben. Auf der Basis
dieser Spezifikation gibt es für viele verschiedene Plattformen Bibliotheken,
die diese so hardwarenahe wie möglich implementieren. OpenGL ist also ein
prozedurales API, im Gegensatz zu einem beschreibenden (wie etwa Java
3D). Das bedeutet, dass der Befehlssatz benützt wird, um die Schritte zu
beschreiben, die notwendig sind, um eine Szene oder einen bestimmten Effekt
darzustellen. Ein deskriptives API hingegen beschreibt direkt, wie die fertige
Szene aussehen soll, und erledigt die dazu notwendigen Schritte im Hintergrund. Aus diesem Grund spricht man im Zusammenhang mit OpenGL auch
von einem so genannten low-level API, da jeder einzelne Schritt des Renderings (der Darstellung) selbst definiert werden muss.
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
10
Die OpenGL Bibliothek ist als Zustandsautomat konzipiert. Das heißt,
dass eine bestimmte Eigenschaft einmal aktiviert wird und danach so lange
gilt, bis sie explizit wieder deaktiviert wird. Auf diese Art und Weise ist es
etwa möglich, eine große Anzahl von Vertices in der gleichen Farbe darzustellen, ohne bei jedem Zeichenaufruf für einen Vertex diese explizit mitgeben
zu müssen.
Die Versionspolitik von OpenGL hat gezeigt, dass neue Versionen oft
in mehrjährigen Abschnitten veröffentlicht werden. Seine Aktualität behält
das API durch die Möglichkeit der OpenGL Extensions (Erweiterungen).
Diese werden in der Regel von Hardware-Herstellern veröffentlicht und ermöglichen, stets die neuesten Funktionalitäten diverser Grafik-Hardware
zu verwenden. Die Erweiterungen sind zunächst meist proprietär, d. h. für
das Produkt eines Grafikkarten-Herstellers zugeschnitten. Im weiteren Verlauf werden sie jedoch oft vom Architecture Review Board (ARB), einem
Konsortium bestehend aus bedeutenden Firmen der Branche, standardisiert
und schließlich im Zuge von neuen OpenGL Versionen in den StandardFunktionssatz übernommen. Die aktuell verfügbare Version ist OpenGL 2.0.
Sie wurde Ende 2004 veröffentlicht und gilt als großer Schritt durch die Integrierung der OpenGL Shading Language.
OpenGL ist rein funktional aufgebaut und für die Verwendung mit den
Programmiersprachen C und C++ ausgelegt. Aufgrund der großen Verbreitung gibt es allerdings eine große Anzahl an Anbindungen für andere Sprachen wie etwa auch Java. Siehe dazu Abschnitt 3.1.2.
OpenGL findet vor allem im professionellen 3D Grafik-Bereich Anwendung. Hauptgründe hierfür sind die offene, firmenungebundene Spezifikation und die Plattformunabhängigkeit. Im Spielebereich jedoch dominiert
Direct3D den Markt. Viele Spiele verwenden ausnahmslos Direct3D als Renderer. Dies ist hauptsächlich damit zu begründen, dass Plattformunabhängigkeit im Spiele-Sektor nur eine untergeordnete Rolle spielt, da fast das
gesamte Geschäftsergebnis auf der Windows-Plattform erzielt wird. Selbstverständlich gibt es auch Spiele bzw. Grafik-Engines, die beide APIs verwenden. Reine OpenGL basierte Produkte sind selten. Eine Ausnahme liefert
die Firma id Software, deren Quake II Engine die erste war, die neben einem
traditionellen Software-Renderer die Möglichkeit der hardwareunterstützten
Darstellung bot. id Software setzte dabei ausschließlich auf OpenGL.
2.2.2
Direct3D
Direct3D ist eine Schnittstelle (API) zur Programmierung von 3D-Grafiken.
Es ist Bestandteil von DirectX Graphics, welches als Überbegriff für die
2D- und 3D-Grafik-Bibliotheken innerhalb des gesamten DirectX Frameworks gilt. Direct3D baut, ähnlich wie OpenGL, komplexe Szenen durch
einfache Primitive auf. Wie auch bei OpenGL wird ein prozeduraler Ansatz
verwendet, weswegen auch hier von einem low-level API gesprochen wird.
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
11
Zusätzlich gibt es jedoch mit Direct3DX ein high-level API, welches auf Direct3D aufbaut und es ermöglicht, simple 3D-Szenerien schnell und einfach
zu erstellen.
Die Organisation von Direct3D (und auch DirectX im Allgemeinen) wird
durch das Component Object Model (abgekürzt COM, eine Technologie zur
Wiederverwendung von Software-Komponenten) vorgegeben. Während unter OpenGL alle Funktionen mehr oder weniger lose organisiert sind, erfolgt
in Direct3D eine Aufteilung in verschiedene Schnittstellen (Interfaces). Diese
sind von einem Basis-Interface (IUnknown) abgeleitet und fügen sich somit in
eine hierarchische Einteilung. Die Schnittstellen und Datentypen in Direct3D
sind also Klassen, deren Methoden die API-Funktionalitäten bereitstellen.
Somit unterscheidet sich Direct3D durch seinen objektorientierten Ansatz
erheblich von OpenGL.
Die Voraussetzung dafür, dass Grafik-Hardware in Direct3D angesprochen werden kann, ist, dass der Treiber Direct3D kompatibel ist. Die verwendete Hardware wird explizit als Gerät im Code angelegt und unterstützte Funktionalitäten können darauf abgefragt werden. Erfolgt nun ein
Aufruf, der von der Grafikkarte nicht unterstützt wird, so wird ein Fehler
zurück geliefert. Hier unterscheidet sich Direct3D wiederum von OpenGL,
das die darunter liegende Hardware völlig abstrahiert und im Falle von nicht
unterstützten Funktionalitäten auch versucht, diese in Software nachzubilden. Direct3D überlässt diesen so genannten Fallback“-Mechanismus dem
”
Applikations-Entwickler.
Neue Funktionen werden in Direct3D nur in neuen Versionen integriert.
Ein Extension-System, wie es in OpenGL zu finden ist, gibt es nicht. Für
die Spiele-Entwicklung bedeutet dies, dass immer mit der neuesten Version gearbeitet werden sollte, um alle Möglichkeiten des APIs ausnützen zu
können. Probleme können entstehen, falls während des Entwicklungsprozesses eine neue Version des Direct3D APIs veröffentlicht wird. Da das API,
wie sich über die vergangenen Versionen gezeigt hat, immer wieder gröberen Änderungen unterzogen wird, sind bei der Spiele-Entwicklung mitunter
große Modifikationen am Quellcode nötig, um eine Anpassung an eine neue
Direct3D Version vornehmen zu können. Zum gegenwärtigen Zeitpunkt ist
Version 9.0c aktuell, Version 10.0 soll 2007 mit der Veröffentlichung von
Windows Vista erscheinen.
Wie bereits erwähnt, ist Direct3D mittlerweile am Spiele-Sektor vorherrschend. Zu der großen Verbreitung auf der Windows Plattform kommt noch
der Einsatz auf der XBox und der XBox 360 hinzu. Die dort verwendeten Direct3D Versionen unterscheiden sich zwar etwas von der Windows-Version,
tragen jedoch nichtsdestotrotz zur weiten Verbreitung dieser Schnittstelle
bei.
Für die Entwicklung von Computerspielen mit Direct3D bzw. DirectX
sei auf [20] verwiesen.
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
2.2.3
12
Middleware
Der Begriff Middleware wird in der Spiele-Branche für Produkte verwendet,
die bestimmte Elemente des Spiele-Entwicklungsprozesses kapseln und fertig
zur Verfügung stellen. So gibt es etwa Grafik-Middleware, die es erlaubt,
3D-Szenen auf höherer Ebene (z. B. deskriptiv in einem Szenengraphen) zu
erstellen, anstatt mit einem prozeduralen low-level API wie OpenGL oder
Direct3D zu arbeiten. Der Entwickler verwendet dabei die Funktionen der
Middleware, welche dann intern die Darstellung wiederum mit OpenGL oder
Direct3D vornimmt. Wie dies genau geschieht, ist für den Entwickler meist
gar nicht von Bedeutung.
Middleware gibt es aber nicht nur im grafischen Bereich. Es existieren
Lösungen für alle Teilbereiche der Spiele-Entwicklung wie Physik, Netzwerk,
künstliche Intelligenz oder Audio. Oft wird der Begriff auch mit dem Ausdruck Game-Engine“ gleichgesetzt. In diesem Fall bietet die Middleware
”
alle zuerst genannten Funktionalitäten an. Typische Vertreter im grafischen
Middleware-Bereich sind etwa OGRE 3D 1 oder Gamebryo 2 . Vollständige
Middleware-Pakete (also Game Engines) sind z. B. RenderWare 3 oder Crystal Space 3D 4 . Auch abseits dieser klassischen Bereiche hat sich Middleware
etabliert. So gibt es mit SpeedTree 5 ein Software-Paket, das sich nur um die
Berechnung von möglichst realistischen Wäldern in Echtzeit kümmert.
Die Aufgabe von Middleware ist es also, die Realisierung von Teilen eines
Spiels zu vereinfachen und zu beschleunigen. Ein oft genanntes Argument
für deren Verwendung ist, dass nicht bei jeder Spiele-Entwicklung eine Neuerfindung des Rades notwendig sei. Weiters sind Middleware-Produkte oft
mehrere Jahre bereits am Markt und daher robust und ausgiebig getestet.
Durch Middleware können also im Entwicklungsprozess deutlich Ressourcen
gespart werden.
Java 3D wird, um dies bereits hier vorweg zu nehmen, im Allgemeinen
nicht als Middleware bezeichnet. Zwar handelt es sich um ein high-level
API, dessen grafische Darstellung auf low-level APIs basiert, jedoch fehlt
Java 3D eine durchgehende Tool-Chain (Werkzeug-Kette) zur Integrierung
von diversen Medien. So gibt es etwa diverse Zusatztools wie Exporter aus
3D-Programmen, die bei typischen Middleware-Produkten stets vorhanden
sind, nicht.
1
http://www.ogre3d.org/
http://www.emergent.net/index.php?source=gamebryo
3
http://www.renderware.com/
4
http://www.crystalspace3d.org/
5
http://www.speedtree.com/
2
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
2.3
13
Wichtige Konzepte
Kaum ein anderer Wirtschaftszweig, vielleicht abgesehen vom Militär, beeinflusst die Computerhardware-Industrie so stark bezüglich seiner Anforderungen wie die Spiele-Branche. Obwohl jene immer schneller mit neuen
Generationen von Prozessoren und Grafikbeschleunigern aufwartet, gibt es
immer wieder Spiele, für die es zum Zeitpunkt ihrer Veröffentlichung keine
adäquate Hardware gibt. Gründe dafür sind neben einer immer detaillierteren Grafik auch die Vielzahl von Effekten. Solche kommen heutzutage in
Computerspielen zum Einsatz, um eine grafische Darstellung zu ermöglichen,
die nicht nur am neuesten Stand ist, sondern vor allem potentielle Käufer
beeindruckt. Benötigt diese jedoch Hardware, die ein Großteil dieser Käufer nicht zur Verfügung hat, so hat das wiederum negative Auswirkungen.
Wie also bringt man eine hohe Polygonanzahl mit einer Vielzahl an Effekten
so unter, dass das Ergebnis sowohl performant funktioniert, als auch intern
noch strukturiert und überschaubar bleibt?
Im Laufe der Zeit wurden auf diesem Gebiet einige Konzepte entwickelt,
die sich mittlerweile als Standards etabliert haben und in vielen Spielen zum
Einsatz kommen.
2.3.1
Szenengraphen
Wie bereits in Abschnitt 2.2 erwähnt, gibt es bei der Beschreibung einer
3D-Szenerie zwei verschiedene Ansätze: den prozeduralen und den deskriptiven. Ersterer beschreibt die notwendigen Schritte, die gebraucht werden,
um die Szene darzustellen. Diese Schritte werden dann mit Funktionen des
verwendeten 3D-APIs ausgeführt. Der zweite Ansatz beschreibt direkt, wie
die Szene aussieht, also wo welche Objekte positioniert sind und welche Eigenschaften sie haben.
Wird der beschreibende Ansatz gewählt, um eine 3D-Szene zu erzeugen,
so wird bei steigender Komplexität der Szene die Notwendigkeit entstehen,
diese in irgend einer Form zu organisieren, um die Daten überschaubar zu
halten.
Ein einfaches Beispiel: Ziel sei es, unser Sonnensystem für ein kleines
Spiel in 3D nachzubauen. Benötigt wird dafür Geometrie für die Sonne, die
neun Planeten und deren Monde. Einfache Kugeln in den entsprechenden
Größen sind dafür ausreichend. Weiters haben die Sonne und die jeweiligen
Planeten ihre unterschiedlichen Texturen. Für die Monde wird lediglich eine
Textur verwendet. Die Planeten sollen nun in Umlaufbahnen um die Sonne
kreisen, die Monde kreisen wiederum um die jeweiligen Planeten.
Ein Ansatz, diese Aufgabenstellung zu lösen, wäre, alle Objekte eigenständig und voneinander unabhängig in der Szene anzuordnen. So würde
man die Sonne im Mittelpunkt platzieren und die Planeten durch eine Rotation um jenen Mittelpunkt und eine anschließende Translation jeweils pro
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
14
dargestelltem Frame an die richtige Position setzen. Dies könnte noch ohne
großen Aufwand geschehen. Komplizierter würde es, wenn man nun die Position der Monde, die um einen der Planeten kreisen, bestimmen wollte. Da
sie relativ zu den Positionen der Planeten wäre, müssten jene zuerst bekannt
sein. Ginge man aber, wie zuerst angenommen, davon aus, dass alle Objekte
völlig unabhängig voneinander existieren, würde man spätestens jetzt an
einem Punkt ankommen, wo man diesen Ansatz schnell wieder verwerfen
würde. Würde man die Vorbedingungen lockern und davon ausgehen, dass
die Objekte zwar nach wie vor getrennt voneinander zu betrachten sind, Informationen über ihre Position, Rotation und Skalierung jedoch verfügbar
sind, so bedeutete dies, dass es zwar möglich wäre, die durch die Rotation
entstandene Position der Monde zu berechnen, dennoch wäre auch diese Lösung immer noch nicht optimal: Man nehme etwa an, dass nun auch auch
Satelliten in das Modell des Sonnensystems einbezogen würden. Ein solcher
könnte die Erde etwa in einer geosynchronen Umlaufbahn umkreisen, das
hieße, er würde die Erde in der selben Zeitdauer umlaufen, die die Erde
für eine Drehung um ihre eigene Achse benötigt. Als eigenständiges Objekt
müsste man diesen Satelliten jeweils separat rotieren, obwohl er im Bezug
auf die Erde seine Position nicht verändert.
Spätestens an diesem Beispiel ist erkennbar, dass eine hierarchische Gliederung einer solchen Szene durchaus Sinn macht. Man spricht hierbei von
einem Szenengraphen. Dabei handelt es sich um eine Baumstruktur mit
einem Wurzelknoten, an dem so genannte Kindknoten hängen. Diese Kindknoten sind die Objekte einer Szene. Die einzelnen Kindknoten können sich
nun wieder weiter verzweigen und bilden so die gewünschte Hierarchie. Diese
stellt die logische Repräsentation der Szene dar. Der große Vorteil einer solchen Darstellung ist, dass die Änderung einer bestimmten Eigenschaft eines
Knotens Auswirkungen auf alle darunter liegenden Kindknoten hat. So muss
sie nur einmal ausgeführt werden und nicht für jedes Objekt separat.
Verwendete man im zuvor vorgestellten Beispiel einen Szenengraphen, so
ergäbe sich folgendes Szenario: Der Wurzelknoten des Szenengraphen wäre
das Universum. Innerhalb dieses Universums würde die Sonne an einer bestimmten Stelle positioniert werden. Sie wäre somit der erste Kindknoten
des Universums. Die Sonne ihrerseits hätte wiederum neun Kindknoten –
die Planeten. Jeder der Planeten hätte seinerseits als Kindknoten die jeweiligen Monde und eventuelle Satelliten, die durch Translationen auf ihre
Umlaufbahnen gebracht würden. Rotierte man einen Planeten um die Sonne,
so würden seine Monde und Satelliten automatisch diese Rotation mitmachen. Im Falle des zuvor angesprochenen geosynchronen Satelliten wäre die
Arbeit jetzt schon erledigt. Im Falle der Monde müsste lediglich noch eine
Rotation hinzugefügt werden, damit sie um ihren jeweiligen Planeten kreisten (Abbildung 2.1).
Der Szenengraph in diesem Beispiel dient nicht nur zur logischen sondern
auch zur räumlichen Repräsentation der Objekte. Dies ist jedoch nicht zwin-
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
15
Abbildung 2.1: Der Szenengraph des Planeten-Beispiels. Lediglich bei der
Erde wurden die weiteren Kindknoten berücksichtigt. Die roten Knoten stellen die Transformationen dar, die auf die darunterliegenden Elemente wirken.
R steht dabei für Rotation, T für Translation.
gend notwendig. Wie das Konzept des Szenengraphen verwendet wird, ist
im Regelfall von der Anwendung abhängig, die einen solchen implementiert.
Es gibt daher auch keine strengen oder fixen Regeln, wie ein Szenengraph
genau auszusehen hat und welche Daten er verwaltet.
Die Vorteile des Szenengraphen liegen jedoch nicht nur in seiner Struktur, er kann auch zur Speicherersparnis in Spielen beitragen. Man denke
etwa an ein Rollenspiel mit ausgedehnten Wäldern: In diesen Wäldern soll
sich eine große Anzahl Wölfe befinden. Da diese in der Regel alle mit dem
selben Modell und den selben Texturen dargestellt werden, ist es möglich,
ein Objekt eines Wolfs anzulegen und wann immer ein Wolf in der Szene
gebraucht wird, nur eine Referenz auf das bereits vorhandene Tier in den
Szenengraphen zu integrieren. So wird die unnötige Duplizierung von Daten
verhindert, was einen positiven Effekt auf die Speicherlast und die Geschwindigkeit hat.
Bekannte Szenengraphen-Systeme sind Open Inventor 6 und OpenSG7 .
Auch Java 3D verwendet Szenengraphen.
6
7
http://oss.sgi.com/projects/inventor/
http://www.opensg.org/
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
2.3.2
16
Scene-Management
Performance und Geschwindigkeit sind wichtige Erfolgskriterien bei der Entwicklung eines Computerspiels. Ein langsames und schlecht lauffähiges Produkt wird am Markt genauso versagen wie ein Spiel mit veralteter Grafik.
Performance und neueste Grafik-Funktionalitäten sind jedoch im Grunde betrachtet zwei sich ausschließende Dinge. Je detaillierter eine Szene ist (d. h.
aus je mehr Polygonen sie besteht), desto mehr wird der Grafik-Hardware
abverlangt. Würde man eine komplexe Szene mit allen Details komplett darstellen (d. h. jedes einzelne Polygon berechnen), würden auch Grafikkarten
der aktuellsten Generation versagen. Ganz zu schweigen von älteren Modellen. Aus diesem Grund ist es unerlässlich, bereits vor der eigentlichen
Darstellung zu bestimmen, welche Teile der Szene dargestellt, bzw. berechnet werden müssen und welche nicht. Diesen Vorgang nennt man SceneManagement.
Es gibt eine Reihe von Techniken, die unter Scene-Management fallen.
Im Folgenden sollen die wichtigsten und bedeutendsten unter ihnen kurz erläutert werden. Eine umfassende Erklärung zum Thema Scene-Management
findet sich in [25]. Das Thema BSP-Bäume wird explizit in Abschnitt 2.3.3
erläutert.
Verschiedene Techniken des Scene-Managements beschäftigen sich zunächst mit der Entfernung von Polygonen, die in einer 3D-Szene nicht sichtbar sind. Ein Polygon wird als nicht sichtbar bezeichnet, wenn es nach der
Projektion auf die zweidimensionale Bildfläche nicht erkennbar ist. Dabei
gibt es drei verschiedene Anwendungsfälle:
Polygone, die durch andere verdeckt werden: Dieser Fall wird
Hidden Surface Removal oder auch Occlusion-Culling genannt. Hierbei wird getestet, ob ganze Objekte oder einzelne Polygone durch andere vom derzeitigen Betrachterstandpunkt verdeckt werden. Alle Polygone, die als verdeckt markiert werden, werden in der Darstellung
nicht berechnet.
Polygone, deren Rückseite sichtbar wäre: Da die Rückseiten von
Polygonen immer von der Richtung des Betrachters abgewandt sind,
gelten sie als nicht sichtbar und werden nicht dargestellt. Diesen Fall
bezeichnet man auch als Backface-Culling oder Backface-Removal.
Polygone, die nicht im Blickfeld liegen: Jeder Betrachter sieht
Dinge innerhalb eines bestimmten Blickwinkels. Alle Objekte, die nicht
innerhalb dieses Blickfeldes (engl. View-Frustum) liegen, sind nicht
sichtbar und können im Vorhinein aussortiert werden. Man spricht in
diesem Falle von View-Frustum-Culling.
Werden diese Methoden der Geometrie-Reduzierung so wie hier beschrieben angewandt, bedeutet dies, dass jedes Objekt in der Szene vor dem Rendern durchgetestet werden muss. Sind genauere Ergebnisse gewünscht, muss
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
17
Abbildung 2.2: Eine Fläche, die mit einem Quadtree aufgeteilt wurde.
Die roten Punkte repräsentieren Objekte auf dieser Fläche. Die Aufteilung
erfolgte so, dass pro Unterteilung (Knoten) nur ein Objekt bzw. ein Teil
dessen enthalten ist.
von Objektbasis auf Polygonbasis gewechselt werden und die obigen Tests
müssen auf dieser Ebene durchgeführt werden. Dies erfüllt zwar im Endeffekt seinen Zweck, jedoch ist es wenig performant. Darum eignet sich auch
hier, ähnlich wie in Abschnitt 2.3.1, ein hierarchischer Ansatz.
Dieser hierarchische Ansatz wird durch Quadtrees bzw. Octrees verkörpert. Beide Verfahren funktionieren äquivalent. Der Quadtree wird für
eine zweidimensionale Sichtbarkeitsüberprüfung verwendet, der Octree für
eine dreidimensionale. In weiterer Folge beziehen sich alle Ausführungen aus
Gründen der besseren Vorstellbarkeit auf den Quadtree und somit auf die
zweidimensionale Repräsentation.
Ein Quadtree ist eine hierarchische Baumstruktur, bei der ein Knoten
immer genau vier Kinder hat. Auf dieser Basis kann eine Fläche rekursiv
in jeweils vier gleich große Teile aufgespaltet werden. Die vier Flächen bilden dann die Kindsknoten des übergeordneten Wurzelknotens (der gesamten
Fläche). So kann eine Fläche immer weiter und feiner unterteilt werden. Befinden sich auf der Fläche nun etwa Objekte, so kann man die Erstellung des
Quadtrees so steuern, dass in jedem Knoten nur genau eines dieser Objekte
vorhanden sein darf. Ist nur mehr ein Objekt vorhanden, erfolgt keine weitere
Aufteilung, ansonsten wird so lange geteilt, bis die definierte Abbruchbedingung gilt. Während der Erstellung des Quadtrees wird in den Knoten gleich
die Information mitgespeichert, welches Objekt sich in der dem Knoten zugeteilten Fläche befindet. Am Ende existiert eine Datenstruktur, die genaue
Auskunft über die Position der darin enthaltenen Objekte gibt. Abbildung
2.2 zeigt die Aufteilung einer Fläche mit Hilfe eines Quadtrees.
Somit kann etwa View-Frustum-Culling wesentlich effizienter ausgeführt
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
18
werden. Anstatt jedes Mal alle Objekte in der Szene zu testen, überprüft
man den Quadtree. Wenn etwa eine der vier obersten“ Flächen bzw. das
”
darin enthaltene Objekt nicht mehr im Sichtfeld liegt, so müssen alle Objekte, die an Kindsknoten hängen, nicht mehr getestet werden. Neben dem
Sichtbarkeitstest kann der existierende Quadtree zusätzlich auch zur Kollisionserkennung eingesetzt werden. Dazu muss lediglich überprüft werden, in
welchem Quadranten (bzw. Knoten) sich der Betrachter befindet. In Folge
muss dann nur getestet werden, ob dieser mit dem Objekt, das sich in diesem
Quadranten befindet, kollidiert. Alle anderen Objekte in der Szene können
außer Acht gelassen werden, bis der Betrachter in einen anderen Quadranten wechselt. Quadtrees dienen somit also vor allem zur Effizienzsteigerung
der Sichtbarkeitsüberprüfung, werden jedoch auch auf anderen Gebieten wie
z. B. der dynamischen Terrain-Generierung eingesetzt (siehe dazu [25]).
Besinnt man sich noch einmal zurück auf die zuvor gegebene Definition des Scene-Managements, so geht es darum, nur Dinge zu berechnen,
die unbedingt notwendig sind. Dies betrifft nicht nur die Behandlung von
nicht sichtbaren Objekten, sondern auch von schlecht sichtbaren. Damit
sind Objekte gemeint, die weit von der Position des Betrachters entfernt
sind. Diese müssen im Allgemeinen nicht mit der vollen Anzahl an Polygonen dargestellt werden, da sie ohnehin nicht sichtbar wären. Daher kommt
in diesem Fall das so genannte Level of Detail (LOD) zum Einsatz. Die
einfachste Ausführung davon ist das so genannte Discrete Level of Detail.
Hierbei wird ein 3D-Modell in mehreren Auflösungen erstellt. In nächster
Nähe wird dabei das Modell mit der höchsten Polygon-Anzahl verwendet,
um eine bestmögliche grafische Darstellung zu erreichen. Ab einer gewissen,
voreingestellten Distanz wird auf das Modell mit der nächst geringeren Auflösung umgeschaltet usw. Dieser Ansatz ist leicht zu implementieren, hat
jedoch den Nachteil, dass ein Mehraufwand bei der Generierung von Modellen entsteht und dass es beim Wechsel der Modelle zu so genanntem Popping
kommt, welches im Allgemeinen als störend wahrgenommen wird. Um dies
zu vermeiden, gibt es den Ansatz des Continuous Level of Detail, bei dem
von einem hochauflösenden Modell ausgegangen und je nach Bedarf die Detailstufe algorithmisch verringert wird. Dieser Ansatz ist rechenaufwändiger
und wird eher für großflächige Objekte verwendet. Continuous Level of Detail findet etwa Anwendung bei der Terraingenerierung mittels Quadtrees
(siehe wiederum [25]).
2.3.3
BSP-Bäume
Von allen Scene-Management-Techniken hat wohl der BSP-Baum (Binary
Space Partitioning Tree) die größte Bedeutung für die Entwicklung von 3DSpielen. Durch den Einsatz eines BSP-Baumes gelang es der Firma id Software Anfang der 90er Jahre, Spiele wie Doom und in weiterer Folge Quake
zu veröffentlichen, die für die damalige Zeit beinahe revolutionär waren, vor
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
(a)
19
(b)
Abbildung 2.3: 3D-Geometrie, wie sie von einem BSP-Baum aufgeteilt
wird. Bei der ersten Teilung (a). Nach der vollständigen Teilung (b). Entnommen aus [25].
allem was die Grafik betraf.
Bei BSP-Bäumen handelt es sich erneut um eine hierarchische Baumstruktur. Jeder Knoten in dieser Struktur kann genau zwei Kinder haben.
Das Besondere am BSP-Baum, verglichen etwa zum Quad- oder Octree ist
die Möglichkeit, an den einzelnen Knoten jeweils die Geometrie mitzuspeichern. Der Wurzelknoten repräsentiert damit die gesamte Geometrie der
Szene. Um die Szene in zwei Hälften aufzuteilen, wird ein Polygon gewählt,
das eine Ebene erzeugt, welche die Geometrie so teilt, dass zwei konvexe
Geometriehälften entstehen (Splitter-Polygon). Der Ast mit der Geometrie,
die vor der Teilungsebene liegt, wird als Frontlist bezeichnet, die andere
Hälfte als Backlist. Diese Teilung wird nun so lange rekursiv fortgesetzt,
bis kein Polygon mehr gefunden werden kann, das eine Teilung in Frontund Backlist ermöglicht. Die so erhaltene Struktur enthält nun Geometrieteile, von denen bekannt ist, dass sie sich nicht gegenseitig verdecken können
(da sie alle konvex sind). Da der BSP-Baum gleichzeitig auch nach Distanz
sortiert ist, kann der Baum ohne Probleme durchlaufen werden und die einzelnen Teile von hinten nach vorne gezeichnet werden. Abbildung 2.3 zeigt
Beispiel-Geometrie, die von einem BSP-Baum aufgeteilt wurde.
Neben der Aufgabe, ein fehlerfreies Rendering zu ermöglichen, können
BSP-Bäume auch für andere Zwecke verwendet werden. Beispiele sind Kollisionserkennung oder Sichtbarkeitsüberprüfung. Zerbst erklärt einige dieser
Variationen in [25].
Die ersten Schritte in Richtung BSP-Baum wurden in [4] gemacht. Fuchs
et. al. beschreiben darin eine Möglichkeit zur Prioritätsbestimmung von Polygonen in einer 3D-Szene. Dieser Algorithmus erlaubt es, für jede beliebige
Geometrie eine Reihung der Polygone vorzunehmen. Je höher ein Polygon
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
20
gereiht wird, desto geringer ist die Wahrscheinlichkeit, dass es von einem
anderen verdeckt wird.
In [5] stellen Fuchs et. al. schließlich das bereits oben beschriebene Konzept des BSP-Baumes vor. Besonderes Augenmerk wird hierbei vor allem
auf die Phase der Bildgenerierung gelegt. Der BSP-Baum wird dabei so
durchlaufen, dass wiederum eine Sortierung nach Prioritäten erfolgt und
die Polygone mit der höchsten Priorität zuletzt gezeichnet werden. Dazu
muss jeweils zuerst die Backlist besucht werden, dann der Knoten mit dem
Splitter-Polygon und zuletzt die Frontlist.
BSP-Bäume galten lange als das Maß der Dinge in der Spiele-Entwicklung. Die Entwicklung bei Grafik-Hardware, insbesondere die Einführung eines Tiefenbuffers haben jedoch Algorithmen zur Prioritätssortierung wie den
BSP-Baum entbehrlich gemacht. In heutigen, zeitgemäßen Spielen kommt
dieser Algorithmus daher immer seltener zur Anwendung.
2.4
Existierende Arbeiten auf diesem Gebiet
Marner evaluiert in [15] die Tauglichkeit von Java für die Spiele-Entwicklung.
Er beginnt nach einer kurzen Einleitung mit einem Kapitel über SpieleEntwicklung. Darin erläutert er die Definition von Computerspielen und gibt
einen Überblick über die verschiedenen Plattformen. Er unterscheidet dabei
zwischen PC, Konsolen, Arcade-Geräten, Web-basierten Spielen, tragbaren
Konsolen, interaktivem Fernsehen, Set-top Boxen, Mobiltelefonen, tragbaren
Geräten wie etwa Palm-Tops, Unterhaltungs-Kiosks und Slot-Maschinen. In
weiterer Folge geht Marner auf die Geschäftsmodelle von PC-Spielen und
Konsolen ein.
Das nächste Kapitel widmet sich komplett der Programmiersprache Java.
Zunächst wird der Ausdruck Java erläutert und eine Übersicht über die
Funktionsweise des Java-Compilers und Interpreters gegeben. In weiterer
Folge erläutert er das Konzept der Virtual Machine und geht dabei auch auf
Just-in-Time Compiler und Hotspot-Kompilierung ein. Der restliche Teil des
Kapitels widmet sich eher kurz den verschiedenen Java-Plattform-Editionen,
Applets, der Standardbibliothek und dem Development Kit.
Kapitel 5 enthält einen Vergleich zwischen Java und C++. Zuerst wird
auf den Unterschied zwischen Applikations-Programmiersprache (Java) und
System-Programmiersprache (C++) hingewiesen, im weiteren Verlauf werden die Themen Objektorientiertheit, Portabilität und Garbage-Collection
(Aufräumen von nicht mehr benötigten Objekten im Speicher) behandelt.
Ebenso werden die Unterschiede bei der Behandlung von Arrays, Referenzen
und Pointern, der Typumwandlung und der Standardbibliothek und noch einige andere hervorgehoben.
Das darauf folgende Kapitel widmet sich voll und ganz der Performance
von Java und erklärt gleich vorweg, in welchen Situationen Java beson-
KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN
21
ders langsam ist und wie sich die Geschwindigkeit über die verschiedenen
Versionen hinweg verbessert hat. Dabei wird auch auf die Unterschiede
zwischen den verschiedenen Virtual Machines (von Sun, IBM, Microsoft,
usw.) eingegangen. Marner erläutert in diesem Kapitel nochmal detailliert
das Prinzip der Hotspot Technologie und geht auch auf Dinge wie statische Java-Compiler ein. Besonders hebt er die Notwendigkeit des so genannten Code-Tweakings hervor. Tweaking bedeutet im übertragenen Sinn an
”
etwas basteln oder schrauben“, also alle möglichen Optimierungen durchzuführen. Im Falle des Code-Tweakings werden also vor allem jene Stellen optimiert, die oft ausgeführt werden und daher zeitkritisch sind. Mögliche einfache Tweaking-Methoden sind etwa die Vermeidung von unnötigen
Objekt-Allokierungen (d. h. Wiederverwendung von bestehenden Objekten)
oder die Vermeidung von unnötigen Methodenaufrufen. Methoden dieser Art
reduzieren jedoch in sehr vielen Fällen die Lesbarkeit des Codes und sollten
daher bedacht angewandt werden. Marner ist dennoch der Meinung, dass
dies unbedingt notwendig sei, um schnellen Code zu erzeugen.
In Kapitel 7 werden die drei Beispiel-Applikationen getestet, die im Rahmen dieses Berichtes entstanden: ein Partikelsystem, eine Implementierung
des Game of Life, wo sich Zellen aufgrund bestimmter Verhältnisse entwickeln und eine OpenGL Grafik-Demo. Alle drei Applikationen wurden
sowohl in Java als auch in C++ entwickelt und unter verschiedenen Bedingungen miteinander verglichen. Es zeigte sich, dass Java-Code im Normalfall
langsamer als C++ Code war, teilweise konnte jedoch speziell verbesserter
Java-Code eine optimalere Leistung erzielen.
Marner schließt seinen Report mit einer Evaluierung von Java für den
Spiele-Sektor ab. Er stellt darin fest, dass Programmieren in Java wesentlich produktiver sei und zählt noch weitere Vorteile von Java auf. Solche sind
etwa Stabilität, gute Dokumentation, guter Support oder geringe Migrationskosten. Als Nachteile listet er geringe Code-Sicherheit und Probleme bei
der Auslieferung von Java-Applikationen auf, bietet jedoch adäquate Lösungen an. Er geht weiters auf die problemlose Einbindung von OpenGL, DirectX sowie die Verwendung von Java 3D ein. Im letzten Teil des Kapitels
listet Marner Möglichkeiten für die Verwendung von Java in Spielen auf.
Diese erstrecken sich seiner Ansicht nach von der puren Java-Applikation
bis hin zur Möglichkeit, Java als Skriptsprache einzusetzen. Weiters zählt er
noch einige Java-Spiele auf und beschäftigt sich mit Portabilität und Performance.
In seinem Fazit stellt Marner Java generell ein gutes Zeugnis für die
Spiele-Entwicklung aus, empfiehlt es jedoch nicht für perfomancekritische
Spiele.
Kapitel 3
Ansätze in Java
3.1
Grafik-Schnittstellen und Engines
Aufbauend auf Abschnitt 2.2 sollen in diesem Kapitel einige Möglichkeiten
vorgestellt werden, 3D-Szenen nun in Java umzusetzen. Dabei gibt es wiederum zwei verschiedene Ansätze: High-level Java APIs oder auch Middleware erlauben eine Erstellung der Szene mit einem Szenengraphen, identisch
zu den im letzten Kapitel vorgestellten Methoden. Das Rendering wird intern von OpenGL oder Direct3D vorgenommen. So genannte Java-Bindings
(Anbindungen) hingegen ermöglichen ein Arbeiten mit den low-level APIs
OpenGL und Direct3D genau so wie in deren nativer Sprache C bzw. C++.
Dies bedeutet, dass alle C-Funktionen als Java-Methoden zur Verfügung
gestellt werden und auch komplett identisch verwendet werden können.
3.1.1
Java 3D
Java 3D ist ein high-level API, das die Erstellung von komplexen 3D-Szenen
mit Hilfe eines Szenengraphen ermöglicht. Die zur Zeit stabile Version ist
1.4, Version 1.5 ist bereits in Entwicklung und kann in regelmäßigen Abständen als so genannte Nightly-Builds (Versionen, die am letzten Stand der
Entwicklung, jedoch meist ungetestet sind) bezogen werden.
Die Entwicklung von Java 3D wird seit Version 1.3 nicht mehr von Sun
Microsystems sondern durch den Java Community Process (JCP) vorangetrieben. Der JCP ist ein Standardisierungskomitee, das sich der Weiterentwicklung der Programmiersprache Java widmet. Neue Erweiterungen werden in so genannten Java Specification Requests (JSR) erfasst, erhalten eine
fortlaufende Nummer und sind auf einer zentralen Website1 einsehbar. Auf
diese Art und Weise ist es möglich, die Entwicklung neuer Funktionalitäten bereits in der Planungsphase auf eine breite öffentliche Basis zu stellen.
Dieses Konzept wird dann durch eine Open-Source Realisierung fortgeführt.
1
http://www.jcp.org/
22
KAPITEL 3. ANSÄTZE IN JAVA
23
Die Konzipierung von Java 3D 1.3 wurde im JSR 912 festgelegt, Java 3D
1.4 wird in JSR 926 behandelt.
Wie bereits anfangs erwähnt, verwendet Java 3D einen Szenengraphen
zur Organisation der darzustellenden Objekte. Dieser Graph kann neben
3D-Geometrie auch Materialien, Beleuchtung, Sounds (akustische Effekte)
und das Betrachtermodell beinhalten. Die darunter liegende Grafik-Pipeline
(der Weg vom logisch definierten Objekt bis zu seiner 2D-Projektion am
Bildschirm) bleibt völlig verborgen. Die Darstellung (das Rendering) erfolgt wahlweise mittels OpenGL oder Direct3D. Ältere Versionen von Java
3D (vor Version 1.3.2) wurden noch getrennt als OpenGL- bzw. Direct3DVersion ausgeliefert. Mittlerweile sind beide Renderer integriert.
Der Java 3D Szenengraph ist aus einzelnen Knoten aufgebaut. Diese werden durch die abstrakte Klasse javax.media.j3d.Node repräsentiert. Von
ihr sind die zwei eigentlichen Knoten-Haupttypen, javax.media.j3d.Group
und javax.media.j3d.Leaf, abgeleitet.
Erstere dienen ganz allgemein zur Gruppierung von Elementen. Der Subtyp javax.media.j3d.BranchGroup fasst dabei Objekte im Szenengraph
logisch zusammen und bildet somit eine eigene Verzweigung (einen so genannten Branch) in der Baumstruktur. Als Transformationsknoten dient
der Typ javax.media.j3d.TransformGroup. D. h. alle Kinderknoten, die
an ihm hängen, erfahren die auf den Knoten ausgeführten Transformationen (Translation, Rotation, Skalierung). So ist es möglich, mit der Angabe lediglich einer Transformation gleich eine größere Menge von Objekten zu beeinflussen. Neben diesen zwei wichtigen Gruppenknoten gibt es
noch einige weitere wie etwa javax.media.j3d.OrderedGroup (alle Objekte an diesem Knoten werden in vorgegebener Reihenfolge gerendert),
javax.media.j3d.Switch (erlaubt das selektive Rendern von Kindknoten)
oder auch com.sun.j3d.utils.geometry.Primitive (die Basisklasse für
alle Geometrieprimitive in Java 3D, deren Geometrie dadurch wiederverwendet wird).
Leaf-Knoten hingegen haben keine Kinder und sind somit Endpunkte
einer Verzweigung im Graphen. Sie sind die Knoten, die Szeneninformation beinhalten. Leaf-Knoten sind etwa javax.media.j3d.Shape3D (repräsentiert alle geometrischen Objekte in der Szene), javax.media.j3d.Light
(stellt eine Lichtquelle dar), javax.media.j3d.Sound (Musik/Geräusche),
javax.media.j3d.Behavior (zur Manipulation des Graphen zur Laufzeit
aufgrund bestimmter Ereignisse wie z. B. Benutzer-Input), oder auch javax.media.j3d.ViewPlatform (repräsentiert die Position und Orientierung
des Betrachters). Eine vollständige Auflistung aller Knotentypen findet sich
in der API-Dokumentation zu Java 3D 2 .
Jeder Java 3D Szenengraph ist in einer umgebenden Superstruktur, einem so genannten virtuellen Universum (repräsentiert durch die Java-Klasse
2
http://download.java.net/media/java3d/javadoc/1.4.0/
KAPITEL 3. ANSÄTZE IN JAVA
24
javax.media.j3d.VirtualUniverse) eingebunden. Dieses Universum enthält die ganze Szene und ist in seiner Ausdehnung prinzipiell unendlich. Um
nun einen exakteren Raum für die Positionierung des Szenengraphen zu bestimmen, hat ein virtuelles Universum eines oder mehrere Objecte des Typs
javax.media.j3d.Locale (zu Deutsch Schauplätze“). Dieser Raum defi”
niert ein hochpräzises Koordinatensystem, repräsentiert durch vorzeichenbehaftete, zweierkomplementäre 256 Bit Fixkommawerte. An diesem LocaleKnoten teilt sich dann der Szenengraph in zwei Teile auf: In View-BranchGraph (Betrachterzweig) und Content-Branch-Graph (Inhaltszweig).
Der View-Branch-Graph enthält die zuvor bei den Leaf Knoten bereits
erwähnte Klasse javax.media.j3d.ViewPlatform. In Interaktion mit dieser
steht javax.media.j3d.View, welche sozusagen die Art der Ausgabe definiert. Standardmäßig handelt es sich dabei um einen normalen Bildschirm.
Java 3D unterstützt jedoch auch Head Mounted Displays (HMD oder auch
3D-Brille“ genannt). Da in den meisten Fällen der View-Branch für Betrach”
ter, die einen Monitor verwenden, benötigt wird (d. h. Virtual Universe,
Locale und View sind identisch oder zumindest ähnlich), gibt es eine existierende Klasse namens com.sun.j3d.utils.universe.SimpleUniverse, die
all diese Dinge bereits beinhaltet. Im Regelfall kann auf diese Klasse zurückgegriffen werden. Auch die in Kapitel 4 beschriebene Applikation verwendet
SimpleUniverse als Ausgangspunkt für die Erstellung des View-BranchGraphs.
Der Content-Branch-Graph enthält schließlich noch die eigentliche Szene,
die dargestellt werden soll: 3D-Geometrie, Lichter, Sound usw. Die Komplexität dieses Teils des Graphen hängt von der jeweiligen Applikation ab. Der
Aufbau des Content-Branch der im Zuge dieser Arbeit entwickelten JSceneViewer3D Applikation wird in Kapitel 4 näher erklärt. Den prinzipiellen
Aufbau eines einfachen Java 3D Anwendung zeigt Abbildung 3.1.
Arten des Renderings
Java 3D unterstützt drei unterschiedliche Arten des Renderings: Den Immediate Mode (direkter Modus), den Retained Mode (zurückhaltender Modus)
und den Compiled Retained Mode (kompiliert zurückhaltender Modus). Alle
drei Modi operieren auf der Klasse Canvas3D. Diese stellt die Zeichenfläche
dar, in der die Darstellung der 3D-Szene erfolgt. Die Klasse Canvas3D ist
eine AWT Component, d. h. sie kann problemlos in jedem AWT oder Swing
GUI (Graphical User Interface) eingebettet werden. In diesem Falle spricht
man dann von einer on-screen ( auf den Bildschirm“) Rendering-Klasse.
”
Die Klasse kann dann entweder mit Single-Buffering (einem Zeichenpuffer)
oder Double-Buffering (zwei Zeichenpuffern) bzw. monoskopisch (Einzelbilder werden erzeugt) oder stereoskopisch (Raumbilder werden erzeugt) verwendet werden. Wird Canvas3D nicht zusammen mit einem GUI verwendet,
so agiert die Klasse als off-screen Puffer. Diese ermöglicht ein Rendering im
KAPITEL 3. ANSÄTZE IN JAVA
25
Abbildung 3.1: Der Aufbau eines einfachen Java 3D Szenengraphen, Die
Grafik wurde der in [2] nachemfpunden.
Hintergrund. Die Daten können dann bei Bedarf an einen on-screen Puffer
geschickt werden – so lässt sich etwa Double-Buffering selbst implementieren. Auch für Effekte wie Shadow-Mapping (ein Verfahren zur Erzeugung
von Schatten) ist ein off-screen Puffer nötig. Dieser kann in Java 3D per
Definition nur monoskopisch und mit Single-Buffering verwendet werden.
Wirft man nun einen detaillierteren Blick auf die drei Rendering-Modi,
so überlässt der Immediate Mode dem Entwickler die meisten Freiheiten,
verlangt von ihm aber auch die Angabe von mehren Details bei der Realisierung der Applikation. So braucht der Entwickler zwar nicht unbedingt die
Java 3D Szenengraphen-Struktur zu verwenden (lediglich der View-BranchGraph muss existieren), muss jedoch auch die Rendering-Schleife selbst starten und verwalten. Diese Art der Java 3D Darstellung geschieht also auf einer sehr niedrigen Ebene. Um den Immediate Mode zu verwenden, müssen
die diversen Render-Methoden der Klasse Canvas3D vom Benutzer entsprechend überschrieben werden. Ein Beispiel in Pseudocode für die aufzubauende Rendering-Schleife gibt Abbildung 3.2. Details zu den zu überschreibenden Methoden finden sich in der Java 3D API-Dokumentation.
Der Retained Mode erfordert die Verwendung des Szenengraphen. Alle
Objekte werden genau in ihren Eigenschaften spezifiziert und sind dem Java
3D Renderer bekannt. Dieser kann dadurch beim Rendering Optimierungen
durchführen, die im Immediate Mode nicht möglich sind. Ein wichtiger Teil
dieser Optimierungen besteht darin, zu wissen, welche Elemente im Graphen
sich ändern können und welche nicht. Jeder Knoten besitzt zu diesem Zweck
KAPITEL 3. ANSÄTZE IN JAVA
26
clear canvas
call preRender()
set view branch
render opaque scene graph objects
call renderField(FIELD_ALL)
render transparent scene graph objects
call postRender()
synchronize and swap buffers
call postSwap()
Abbildung 3.2: Der prinzipielle Aufbau des Renderings im Java 3D Immediate Mode.
so genannte Capability-Bits (Fähigkeits-Bits), die festlegen, ob bestimmte
Eigenschaften abgefragt und verändert werden können. Standardmäßig sind
alle Bits, die das Lesen von Attributen bestimmen, auf true gesetzt. Alle
Bits, die verantwortlich sind, ob eine Eigenschaft zur Laufzeit verändert
werden darf, sind im Normalfall auf false gesetzt. Will man nun explizit
eine Veränderung erlauben, so muss das Bit umgesetzt werden. Alle Dinge
im Graphen, die sich nicht verändern, können somit leicht für Optimierungen
herangezogen werden.
De Compiled Retained Mode arbeitet nach den selben Prinzipien wie
der Compiled Mode. Der Unterschied besteht lediglich darin, dass am Ende
der Szenen-Erzeugung vom Benutzer die Methode compile() auf die oberste BranchGroup aufgerufen wird. Dadurch wird der Szenengraph in eine
interne, möglichst performante Struktur umgewandelt. Es ist auch möglich,
dass mehrere einzelne Objekte im Szenengraphen zu einem neuen Objekt
vereint werden oder dass Geometrie komprimiert wird.
Generell gibt es für die meisten Anwendungen keinen Grund, nicht den
Compiled Retained Mode zu verwenden. Er bietet aufgrund des Szenengraphen die komfortabelste Art, eine Szene zu erstellen und zu verwalten. Durch
die von Java 3D vorgenommenen Optimierungen ist es auch für unerfahrenere Entwickler möglich, Performance-Steigerungen zu erhalten.
KAPITEL 3. ANSÄTZE IN JAVA
3.1.2
27
OpenGL-Anbindungen für Java
GL4Java
GL4Java3 ist eine der ersten vollständigen OpenGL Anbindungen für Java.
Zwar gab es vor 1997, als das Projekt startete, schon erste Ansätze, die
sich dem Thema widmeten, diese waren jedoch im Funktionsumfang nicht
komplett. GL4Java bildete als erste Anbindung den vollen OpenGL und
GLU (OpenGL Utility Library) Befehlssatz in Java ab. Mittlerweile wird
an dem Projekt nicht mehr aktiv gearbeitet. Die letzte verfügbare Version
ist 2.8.0.8. Diese bildet OpenGL und GLU Version 1.2 auf Java ab.
GL4Java besteht aus zwei Teilen: Den Java-Klassen, die als JAR (Java
Archive) Dateien vorliegen und alle OpenGL und GLU Befehle beinhalten,
sowie den Systembibliotheken. Während die Java-Klassen systemunabhängig
sind, werden die Systembibliotheken für das jeweilige Zielsystem benötigt.
Unterstützt werden Windows und Linux Plattformen. Methodenaufrufe in
den GL4Java-Klassen werden über das Java Native Interface (JNI) an die
Systembibliotheken weitergeleitet. Diese wiederum rufen die jeweiligen im
System ebenfalls durch Bibliotheken verankerten OpenGL Funktionen auf.
Der Aufbau einer GL4Java-Applikation beginnt mit der Erstellung der
Hauptklasse, welche entweder von der Klasse gl4java.awt.GLCanvas oder
von gl4java.awt.GLAnimCanvas (letztere bietet Unterstützung für die Verwendung Threads) abgeleitet werden muss. Diese Basisklasse beinhaltet die
von OpenGL bekannten Rendering Methoden display() (zum Rendern der
Szene), reshape() (zur Anpassung der Anzeige, falls das Fenster verändert
wird) und auch init() (zur Initialisierung der Szene). Eine Verwendung des
GLCanvas mit AWT oder auch Swing bietet sich an. Die Basisklasse stellt
Objekte der Schnittstellen-Typen GLFunc und GLUFunc zur Verfügung. Über
diese Objekte können alle OpenGL- und GLU -Funktionen aufgerufen werden. Eine weitere Hilfsklasse bietet alle Enumerations-Typen bzw. OpenGLDatentypen an.
JOGL
JOGL4 ist eine aktuelle, in Entwicklung befindliche OpenGL Anbindung für
Java. Das Projekt unterliegt gleich wie Java 3D dem Java Community Process und ist im JSR 231 erfasst. Dies ist die formale Spezifikation für eine
Java OpenGL Anbindung. JOGL ist dafür die Referenz-Implementierung.
Im Folgenden wird der Begriff JOGL aus Einfachheitsgründen umfassend
sowohl für die Spezifikation als auch für die existierende Implementierung
verwendet. Aufbau und Versionierung von JOGL wurden anfänglich noch
nicht gemäß JSR 231 vorgenommen, weshalb die ersten JOGL Versionen
noch unter dem Namen JOGL 1.0“ veröffentlicht wurden. Ab Oktober 2005
”
3
4
http://gl4java.sourceforge.net/
https://jogl.dev.java.net/
KAPITEL 3. ANSÄTZE IN JAVA
28
wurde die JOGL API an die Erfordernisse der JSR 231 angepasst. Aktuelle
Versionen werden daher als JSR 231 Beta“ herausgegeben. Zum gegebe”
nen Zeitpunkt ist Beta 4 die aktuellste Implementierung. Sie unterstützt
OpenGL 2.0, CG (eine Shader Sprache), GLU 1.3 und bietet auch Funktionen des GLUT (OpenGL Utility Toolkit).
JOGL verfolgt die Struktur betreffend den selben Ansatz wie GL4Java.
Java-Klassen werden in Form einer JAR-Datei in den Klassenpfad eingebunden, wodurch die JOGL Klassen zur Verfügung stehen. Diese Klassen
kommunizieren wiederum per JNI mit systemabhängigen Bibliotheken, die
sich für die Umsetzung der OpenGL-Befehle verantwortlich zeichnen.
Beim Aufbau von Applikationen unterscheidet sich JOGL jedoch von
GL4Java. Die Anbindung stellt mit javax.media.opengl.GLCanvas und
javax.media.opengl.GLJPanel sowohl eine AWT als auch eine Swing GUI
Komponente bereit, in der die Darstellung erfolgen kann. Die eigentliche
Programmklasse wird nicht von einer der beiden abgeleitet, sondern muss
lediglich die Schnittstelle javax.media.opengl.GLEventListener implementieren. Diese stellt die Methoden init() (Initialisierung), display()
(Darstellung), reshape() (Anpassung der Anzeige) und displayChanged()
(wenn das Ziel des Renderings, etwa ein Puffer, verändert wird) zur Verfügung. Der Canvas-Instanz kann nun das Objekt, das den GLEventListener implementiert – also die Programmklasse – hinzugefügt werden. Somit
werden die Events, die durch die Rendering-Schleife erzeugt werden, ausgegeben. Um nun auf den OpenGL Befehlssatz zugreifen zu können, liefern alle Methoden der GLEventListener Schnittstelle ein Objekt vom Typ
javax.media.opengl.GLAutoDrawable als Parameter. Dieser Datentyp abstrahiert ein Rendering-Ziel (also z. B. einen on-screen Puffer) und liefert
mittels der Methode getGL() alle Befehle, die auf jenem Rendering-Ziel
ausgeführt werden können. Diese Befehle werden in einem Objekt vom Typ
GL gekapselt. JOGL stellt darüber hinaus noch Hilfsklassen zur Verfügung,
etwa zum Laden von TGA-Dateien, zum Anfertigen von Screenshots oder
für die Ausgabe von OpenGL Debug Informationen.
LWJGL
LWJGL5 – kurz für Light Weight Java Game Library – kann ebenfalls als
OpenGL-Anbindung für Java gesehen werden, obwohl das Projekt noch weitere Funktionalitäten bietet. So könnte LWJGL durchaus auch als Middleware bezeichnet werden, denn sie bietet neben der OpenGL-Anbindung noch
Anbindungen für OpenAL (Open Audio Library)6 , FMOD7 (eine weitere
Audiobibliothek) sowie DevIL8 (ein Textur Loader) und Hilfsklassen für die
5
http://www.lwjgl.org/
http://www.openal.org/
7
http://www.fmod.org/
8
http://openil.sourceforge.net/
6
KAPITEL 3. ANSÄTZE IN JAVA
29
Verarbeitung von Eingabegeräten (Tastatur, Maus, Joystick) und das Laden von 3D-Modellen. Die aktuelle Version 0.99 von LWJGL unterstützt
OpenGL 2.0. Auch der GLU Befehlssatz ist enthalten.
LWJGL macht im Unterschied zu GL4Java und JOGL nicht von Swing
oder AWT als GUI Gebrauch, sondern bietet selbst eine Lösung zur Erstellung des Programmfensters. Dies geschieht mittels der Klasse Display,
die über statische Methoden ein Fenster mit entsprechend gewählten Einstellungen öffnet. Auch die Rendering-Schleife muss selbst erzeugt werden,
d. h. es werden keine Methoden wie display() oder init() vorgegeben.
Um auf den OpenGL Befehlssatz zugreifen zu können, bietet LWJGL wiederum Klassen mit statischen Funktionen. Dabei gibt es für jede OpenGL
Version eine eigene Klasse, die den jeweiligen Befehlsumfang enthält. So können OpenGL 1.1 Befehle durch Aufrufe der Klasse GL11 abgesetzt werden.
Die neueste Version, OpenGL 2.0, wird durch die Klasse GL20 repräsentiert. Auch OpenGL Erweiterungen (siehe Abschnitt 2.2.1) sind als eigene
Klassen verfügbar. Die einzelnen Erweiterungen tragen den jeweiligen Typ
(ARB, EXT, NV oder ATI) im Namen und können so leicht erkannt werden.
3.1.3
Middleware-Anbindungen und Szenengraphen-APIs
Neben Java 3D und direkten Java-Anbindungen gibt es noch eine Reihe
anderer Software-Produkte, die nicht unerwähnt bleiben sollen. Sie fallen in
die Kategorie der schon in Abschnitt 2.2.3 erwähnten Middleware. Oft wird
auch von so genannten Szenengraphen-APIs gesprochen. Dies verdeutlicht,
dass der Fokus auf der Erstellung eines Szenengraphen liegt, während das
Rendering an eine darunterliegende Schicht (z. B. eine OpenGL Anbindung
für Java) weitergegeben wird.
Xith3D
Xith3D 9 ist ein Szenengraphen-API dessen Struktur sehr der von Java 3D
ähnelt. So erfolgt die Darstellung ebenfalls in einem Canvas3D und der Szenengraph besteht aus den Klassen Node, Group und Leaf. Auch die Behandlung von Ereignissen erfolgt mittels Behavior. Diese Ähnlichkeiten machen
eine Portierung von Applikationen zwischen den beiden Systemen in der
Regel einfach.
Unterschiede zeigen sich bei der Art des Renderings. Xith3D nutzt hierbei die zuvor erwähnten OpenGL Anbindungen JOGL (bzw. JSR 231) und
LWJGL. Anbindungen an Direct3D sind nicht vorhanden. Davison kritisiert
in [2] weiters, dass der Szenengraph nicht threadsicher sei, d. h. bei einer
Ausführung mit mehreren parallelen Threads könnten Probleme auftreten.
9
http://www.xith.org/
KAPITEL 3. ANSÄTZE IN JAVA
30
jME
jME (jMonkeyEngine)10 ist eine reine Java Game-Engine. Sie bietet Klassen
für das einfache Erstellen einer simplen Rendering-Schleife sowie die Organisation der Szene in einem Szenengraphen. Das Rendering erfolgt mittels
LWJGL, auch für OpenAL und FMOD sind Klassen in jME vorhanden.
Updates für die Engine erfolgen in regelmäßigen Abständen. Die aktuelle
Version ist 0.10.
ogre4j
ogre4j 11 ist eine Java-Anbindung für die frei verfügbare OGRE 3D GrafikEngine. Das Ziel des Projekts ist es, ähnlich wie bei OpenGL-Anbindungen,
die Funktionalitäten der Engine in Java eins zu eins verfügbar zu machen.
Die Anbindung erfolgt wiederum mittels JNI. Das Projekt hält zum gegenwärtigen Zeitpunkt bei Version 0.3. Diese ermöglicht noch nicht die komplette Nutzung aller OGRE-Funktionen in Java. Die Gründe dafür liegen
laut den Entwicklern vor allem bei Problemen der Umsetzung von C++
spezifischen Konstrukten wie etwa Templates.
3.2
Existierende 3D-Java-Spiele
Verglichen zu der Anzahl an kommerziellen C++ Spielen ist die Zahl an
bekannten und erfolgreichen Java-Spielen sehr gering. Der Großteil aller in
Java realisierten Spiele erscheint entweder auf Mobilen Plattformen oder
als Java-Applets (meist Browser-basiert). Es gibt jedoch einige Spiele, die
durch ihren Umfang und auch ihr Vertriebsmodell als klassische kommerzielle Spiele anzusehen und auch erfolgreich sind bzw. waren. Im Anschluss
folgt eine Auflistung und kurze Beschreibung einiger dieser Spiele. Erstere
enthält nicht nur rein in Java entwickelte Spiele, sondern auch solche, bei denen Java in einem großen Teilbereich (etwa für die Logik) verwendet wurde.
Puzzle Pirates: Entwickelt von Three Rings Design hat sich Puzzle
Pirates zu einem der bekanntesten Java-Spiele entwickelt. Es handelt sich dabei um ein Massen-Mehrspieler-Online-Rollenspiel (englisch Massive Multiplayer Online Role Playing Game, auch MMORPG
abgekürzt), welches in einem typischen Piraten-Szenario angesiedelt
ist. Der Spieler verkörpert dabei einen Piraten, der auf Schiffen anheuern und Schwertkämpfe austragen kann. Diese Tätigkeiten werden
jedoch nicht im klassischen Sinne sondern in Form von Puzzles gelöst.
Sowohl die Server-Software als auch die Client-Applikationen sind in
Java geschrieben. http://www.puzzlepirates.com/
10
11
http://jmonkeyengine.com/
http://www.ogre4j.org/
KAPITEL 3. ANSÄTZE IN JAVA
31
Façade: Das von Procedural Arts entwickelte Spiel ist ein interaktives
Drama. Der Spieler erstellt einen Charakter und erhält am Beginn eine
Einladung von einem befreundeten Paar. Er betritt deren Wohnung
und kann ab sofort völlig ungebunden mit ihnen kommunizieren. Der
Spieler tippt Fragen und Sätze per Tastatur ein und seine virtuellen
Gegenüber reagieren entsprechend darauf. Verlauf und Ausgang sind
somit bei jedem Spiel unterschiedlich. Façade wurde komplett mit Java
und OpenGL entwickelt. Für die dahinter liegende künstliche Intelligenz wurde eine eigene Sprache names ABL (A Behavior Language)
entwickelt. http://www.interactivestory.net/
Chrome: Das Spiel der Firma Techland ist ein First Person Shooter,
der sich vor allem durch seine weiten, begehbaren Außenareale auszeichnet. Der Spieler kann zwischen verschiedensten Waffen wählen,
weiters steht ihm auch eine Vielzahl an unterschiedlichen Fahrzeugen
zur Verfügung. Chrome ist völlig in Java realisiert und erhielt im Jahr
2004 den Duke’s Choice Award“, eine Auszeichnung der Firma Sun
”
Microsystems für das innovativste, auf Java-Technologie basierende
Produkt. http://www.chromethegame.com/
Law and Order: Die mittlerweile drei Spiele zur bekannten TV-Serie
sind klassische Detektiv-Adventures, in denen der Spieler verschiedene
Mordfälle lösen muss. Die ersten beiden Teile sind komplett mittels
Java realisiert, zur 3D-Darstellung werden Java 3D und QuickTime
für Java verwendet. http://www.lawandordergame.com/
Tribal Trouble: Das Wikinger-Aufbauspiel der Firma Oddlabs ver-
setzt den Spieler in eine bunte 3D-Welt, in der er seinen WikingerStamm im Stil von Die Siedler zum Aufstieg durch Expansion verhelfen
muss. Das Spiel setzt stark auf nicht völlig ernste Elemente, so gibt es
etwa nicht ganz alltägliche Waffen wie einen lebenden Hühnerpfeil. Das
Spiel wurde zur Gänze in Java realisiert und steht für Windows, Linux
und Mac-Plattformen zur Verfügung. http://www.tribaltrouble.com/
Wurm Online: Dabei handelt es sich um ein Fantasy MMORPG,
das sich zum Ziel gesetzt hat, dem Spieler möglichst viele Freiheiten
zu geben. Das Spiel wurde mit Java und OpenGL realisiert. Es wird
als Java-WebStart-Datei zum Download angeboten. Somit ist nur eine
Datei zur Ausführung notwendig. http://www.wurmonline.com/
Lux: Diese Strategiespiel-Reihe der Firma Sillysoft erlaubt es, große
Schlachten im Stil des Brettspieles Risiko zu spielen. Die Spiele sind im
mittleren Preissegment angesiedelt und komplett mit Java-Technologie
realisiert. http://sillysoft.net/
Star Wars Galaxies: Bei diesem MMORPG Großprojekt von Lu-
casArts wurde die Spiele-Logik komplett in der Programmiersprache
Java realisiert. http://starwarsgalaxies.station.sony.com/
KAPITEL 3. ANSÄTZE IN JAVA
32
IL-2 Sturmovik: Dieses bekannte Kampfflug-Simulationsspiel von
Ubisoft verwendet Java für die Spiele-Logik sowie für physikalische
Berechnungen und die Umsetzung der künstlichen Intelligenz. http:
//www.il2sturmovik.com/
Weitere zum Teil auch ältere Java-Spiele listet Davison in [2] auf. Er
geht dabei auch auf Freeware- und Shareware-Spiele ein, die hier bewusst
weggelassen wurden.
Kapitel 4
JSceneViewer3D – ein Quake
III Level Renderer
JSceneViewer3D ist eine Applikation zur Darstellung von komplexeren 3DSzenerien unter der Verwendung von Java-Komponenten. Die vorliegende
Version ermöglicht die Darstellung beliebiger Levels (Welten) des 3D-Computerspiels Quake III Arena (im weiteren Verlauf als Quake III bezeichnet).
Durch die Einbindung anderer Level-Loader (Erläuterung und Details siehe
Abschnitt 4.3) ist jedoch auch die Darstellung anderer 3D-Szenen möglich.
Die Applikation wurde mit dem Ziel entwickelt, die Umsetzung eines
performancekritischen Teils eines Spiels – in diesem Fall die grafische Darstellung – komplett unter Verwendung der Programmiersprache Java zu vollziehen. Die fertige Applikation soll im Stande sein, einen Level darzustellen
und Daten über die Effektivität der Darstellung während dieser zu sammeln. Jene Daten werden in Kapitel 5 mit Werten aus dem Spiel Quake III
verglichen. Neben dem Erhalt statistischer Informationen war es auch ein
Ziel, Erfahrungen während des Entwicklungsprozesses zu sammeln und diese
strukturiert wiederzugeben.
Dieses Kapitel beschreibt den Implementierungsvorgang der Applikation.
Abschließende Anmerkungen und ein Resümee finden sich in Kapitel 6.
4.1
Architektur
Bei der Konzipierung der Architektur von JSceneViewer3D wurde darauf
geachtet, eine möglichst modularisierte Struktur zu entwerfen. Die einzelnen Komponenten sollen weitestgehend ohne gegenseitige Abhängigkeiten
funktionieren, um den Einbau von Erweiterungen oder zusätzlichen Funktionalitäten so einfach wie möglich zu gestalten.
Der Aufbau von JSceneViewer3D kann in vier große Teile untergliedert
werden.
33
KAPITEL 4. JSCENEVIEWER3D
34
Abbildung 4.1: Die vier Komponenten von JSceneViewer3D und ihr Zusammenspiel.
Die Basis-Applikation mit der grafischen Benutzeroberfläche
Der Level-Loader
Der Renderer
Der Logger
Diese Teile befinden sich in einer sequentiellen Abarbeitungsreihenfolge,
die durch den Programmfluss vorgegeben wird. Am Beginn steht dabei die
Basis-Applikation, welche den Einstiegspunkt darstellt. Mit Hilfe der grafischen Benutzeroberfläche wird zunächst eine zu öffnende Level-Datei ausgewählt. Dadurch wird der zu der Datei passende Level-Loader initialisiert,
welcher nun die Daten aus der Datei ausliest und in für den Renderer verständliche Datenformate speichert. Im letzten Schritt werden die Daten danach auf dem Ausgabemedium (in der Regel dem Bildschirm) dargestellt.
Einzig und allein die Logger-Komponente unterliegt nicht der sequentiellen
Ausführungsreihenfolge. In jedem Stadium werden wichtige Informationen
von ihr gespeichert und abschließend zusammengefasst dargestellt.
Die Abhängigkeiten und das Zusammenspiel dieser Komponenten sind
in Abbildung 4.1 dargestellt.
Auch die Paket- und Klassenstruktur der Java-Applikation spiegelt das
soeben beschriebene Schema wider:
KAPITEL 4. JSCENEVIEWER3D
35
net.codingmonkey.jsceneviewer3d.gui enthält jene Klassen, die
zur Anzeige der grafischen Benutzeroberfläche benötigt werden. Die
in diesem Paket befindliche Hauptklasse JSceneViewer3D beinhaltet
ebenfalls die main() Methode, welche als Einstiegspunkt in das JavaProgramm dient.
net.codingmonkey.jsceneviewer3d.gui.images beinhaltet sämtli-
che Grafiken und Bilddaten, die in der grafischen Benutzeroberfläche
verwendet werden.
net.codingmonkey.jsceneviewer3d.io enthält Klassen, die für das
Lesen und Schreiben von Daten verwendet werden. Sie werden für das
Laden des Levels benötigt.
net.codingmonkey.jsceneviewer3d.io.q3 enthält jene Datenstrukturen, die den Inhalt einer Quake III Level Datei widerspiegeln.
net.codingmonkey.jsceneviewer3d.io.q3.entites beinhaltet Klassen, die so genannte Quake III Entitäten repräsentieren. Diese Entitäten verwalten etwa die Positionen der Lichter in der Szene.
net.codingmonkey.jsceneviewer3d.renderer.j3d enthält sämtli-
che Klassen, die benötigt werden, um die vom Level-Loader konstruierten Daten mittels Java 3D darzustellen.
net.codingmonkey.jsceneviewer3d.logging besteht aus Klassen,
die für die Aufzeichnung von Performance-Daten benötigt werden.
net.codingmonkey.jsceneviewer3d.misc beinhaltet diverse Hilfsklassen, die zu unterschiedlichen Zeitpunkten im Programm benötigt
werden.
4.2
Basis-Applikation
Um die Darstellung von komplexen 3D-Szenen mit Java zu realisieren, ist
eine grafische Benutzeroberfläche (Graphical User Interface oder kurz GUI)
nicht unbedingt notwendig. Viele der existierenden und im Vorfeld dieses
Projekts getesteten Loader für Quake II oder auch Quake III verzichten
zumeist komplett auf ein GUI. Die Applikation wird per Kommandozeile
gestartet und die Level-Dateien werden dabei als Argumente übergeben.
Ein anderer Ansatz, der ebenfalls verwendet wird ist, das Menü in die 3DDarstellung zu integrieren, d. h. es mittels OpenGL darzustellen.
Beispiele hierfür sind die Quake-Loader von New Dawn Software1 oder
Chapter 232 .
1
2
http://www.newdawnsoftware.com/
http://www11.brinkster.com/chapter23/
KAPITEL 4. JSCENEVIEWER3D
36
public void runApplication() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
Abbildung 4.2: Der Start der Programmschleife im Event Dispatching
Thread.
Bei der Umsetzung von JSceneViewer3D wurde bewusst die Realisierung
einer einfachen und leicht zu handhabenden Benutzeroberfläche angestrebt,
um die Verwendung des Programms so intuitiv wie möglich zu gestalten.
Zur Realisierung wurde ausschließlich das in der Java Standardbibliothek
enthaltene GUI API Swing (enthalten im Paket javax.swing) verwendet.
Wie in Abschnitt 4.1 beschrieben, sind alle für das GUI benötigten Ressourcen in den zwei Paketen net.codingmonkey.jsceneviewer3d.gui und
net.codingmonkey.jsceneviewer3d.gui.images enthalten. Die wichtigste
Klasse ist JSceneViewer3D. Sie enthält die main() Methode, welche als Einstiegspunkt für das Java Programm dient. In ihr wird zuerst ein Objekt der
Klasse JSceneViewer3D angelegt. Im Anschluss wird darauf die Methode
runApplication() aufgerufen. Sie setzt die Applikation in Gang und erzeugt die grafische Benutzeroberfläche mittels der Methode createAndShowGui(). Wichtig ist dabei, dass dieser Ablauf im so genannten Event Dispatching Thread passiert. Dies wird durch einen Aufruf, wie er in Abbildung
4.2 dargestellt ist, erreicht.
Threads sind Prozesse, die in einem Programm quasi-parallel ablaufen
können. Dies ermöglicht, dass etwa ein Teil einer Applikation nicht warten
muss, bis ein anderer mit einer Operation fertig ist, sondern seine Aufgaben
sofort erledigen kann. Gleichzeitig bedeutet es jedoch auch, dass dadurch
so genannte Deadlocks (auch Verklemmungen genannt) auftreten können,
wenn zwei Prozesse auf die gleichen Ressourcen zugreifen wollen und diese
nicht freigegeben sind. Die Swing Bibliotheken unterstützen die Arbeit mit
mehreren Threads, wobei der zuvor erwähnte Event Dispatching Thread
für die Abarbeitung von Ereignissen (Events) wie Mausklicks oder Tastatureingaben sowie für das Zeichnen der kompletten Oberfläche zuständig
ist. Werden nun Dinge an der Benutzeroberfläche verändert, etwa beim ersten Aufruf alle Elemente erstellt oder im späteren Verlauf Elemente zur
Laufzeit markiert, inaktiv oder unsichtbar gesetzt, so ist durch einen Aufruf im Event Dispatching Thread garantiert, dass es zu keinen Deadlocks
kommt.
Die Methode createAndShowGui() erzeugt zunächst ein Fenster der
KAPITEL 4. JSCENEVIEWER3D
37
Abbildung 4.3: JSceneViewer3D nach dem Start. Im nächsten Schritt muss
ein Level geladen werden.
Klasse javax.swing.JFrame. Dieses ist das Hauptfenster, in das in weiterer Folge alle Elemente platziert werden. In einem ersten Schritt wird das
Fenster in zwei Kartenreiter (auch als Tabs bezeichnet) unterteilt. Der erste Reiter wird dabei für die Informationen der geladenen Datei verwendet
(Viewer Tab), der zweite bietet Platz für den Logger (Logger Tab), der nach
erfolgtem Rendering statistische Daten darstellt. Im Anschluss folgt noch
die Erstellung des Hauptmenüs, welches zur effektiveren Verwendung mit
Tastenkürzeln für die wichtigsten Operationen ausgestattet wurde. Schließlich wird das Fenster mit der Methode setVisible(true) dargestellt. Die
Applikation ist gestartet und wird wie in Abbildung 4.3 dargestellt.
Der Benutzer kann nun über den Menü-Dialog File → Open eine LevelDatei öffnen. Um dies intuitiv zu gestalten, wird ihm eine erweiterte Dialogbox des Typs javax.swing.JFileChooser zur Verfügung gestellt. Mit ihrer
Hilfe kann der Benutzer eine beliebige Datei auswählen. Um nur das Öffnen
eines bestimmten Dateityps zu erlauben – in diesem Fall Quake III LevelDateien mit der Endung .pk3 – wird ein Dateifilter der Klasse LevelFilter
verwendet. Dieser von der Klasse javax.swing.filechooser.FileFilter
abgeleitete Filter blendet alle Dateien, deren Endung nicht .pk3 ist aus und
verhindert so bereits auf einfache Art und Weise, dass der Benutzer eine
falsche Datei zu öffnen versucht. Wurde eine Datei mit vorgegebener Endung ausgewählt, erfolgt im nächsten Schritt eine Gültigkeitsprüfung. Hierbei wird festgestellt, ob es sich tatsächlich um ein Quake III -Level, oder
um ein anderes Dateiformat handelt, das lediglich die selbe Datei-Endung
KAPITEL 4. JSCENEVIEWER3D
38
besitzt. Diese Aufgabe übernimmt die Klasse FileReader. Sie erhält als
Parameter die mittels javax.swing.JFileChooser geöffnete Datei – repräsentiert durch die Java-Klasse java.io.File. Jene stellt eine Datei (oder
auch ein Verzeichnis) auf abstrakte Art und Weise dar und erlaubt lesende
und schreibende Zugriffe darauf. Über den konkreten Inhalt ist vorerst noch
nichts bekannt. Dieses erste schnelle Auslesen von Dateiinformationen ist
noch nicht Bestandteil des eigentlichen Level-Ladens. Vielmehr werden neben der Validität der Datei auch noch Informationen wie der Name des
Levels und ein Vorschaubild ausgelesen. Diese Informationen werden im Anschluss in der Benutzeroberfläche angezeigt.
Das soeben angelegte Objekt der Klasse FileReader bestimmt in einem ersten Schritt zunächst erneut die Dateierweiterung. Darauf basierend
werden in weiterer Folge die Gültigkeitsprüfungen für das jeweilige Format
durchgeführt. Hat eine Datei etwa die Endung .pk3, so wird anhand des
Dateiinhalts überprüft, ob es sich wirklich um eine Quake III -Level-Datei
handelt. An dieser Stelle bietet die Klasse Platz für mögliche Erweiterungen.
Soll etwa neben dem Quake III Format auch noch das Unreal Tournament3
Format unterstützt werden, so muss hier lediglich für die Erweiterung .ut2
eine Überprüfung eingebaut werden. Im Falle von Quake III wird überprüft,
ob eine Datei im Zip-Format vorliegt, da PK3 ein Zip komprimiertes Containerformat ist. Anschließend wird in dem Archiv eine Datei mit der Erweiterung .arena gesucht. Sie enthält den Namen und den Spieltyp der Karte.
Schließlich wird im Verzeichnis levelshots ein JPEG- oder TGA-Bild mit
der Level-Vorschau ausgelesen. Wurden alle Daten erfolgreich gesammelt,
gilt die Datei als gültige Quake III -Datei. Kommt es zu Fehlern während
dieses Prozesses, werden Exceptions (Ausnahmen) geworfen, welche an das
GUI weitergeleitet und dort durch entsprechende Fehlermeldungen dem Benutzer mitgeteilt werden. Wurde die Datei erfolgreich geöffnet, zeigt JSceneViewer3D in der linken Spalte Dateiname, Name und Typ der Karte sowie
das Vorschaubild an (Abbildung 4.4).
In der rechten Spalte kann der Benutzer nun einige Optionen konfigurieren, bevor mit dem Laden des Levels und der Darstellung begonnen wird.
Mittels Radio-Buttons (Auswahlliste ohne die Möglichkeit der Mehrfachauswahl) wird zunächst der Renderer selektiert. In der momentan verfügbaren Implementierung ist nur Java 3D anwählbar. JSR 231/JOGL ist jedoch
als nicht anwählbare Option bereits aufgelistet, um Erweiterungsmöglichkeiten in diese Richtung zu signalisieren. Darunter befinden sich Optionen, die
die Darstellung beeinflussen. Es kann gewählt werden, ob Texturen geladen
und angezeigt werden, ob Lightmaps zum Einsatz kommen sollen (ebenfalls noch nicht verfügbar) oder ob der Szenengraph kompiliert werden soll.
Weiters kann der Benutzer die Anzahl der Tessellierungsschritte bestimmen,
die zu verwendende Auflösung einstellen und auswählen, ob die Darstellung
3
http://www.unrealtournament.com/
KAPITEL 4. JSCENEVIEWER3D
39
Abbildung 4.4: JSceneViewer3D mit geladenem Level. Das Vorschaubild
wird angezeigt, rechts können Optionen eingestellt und der Rendervorgang
gestartet werden.
im Vollbild-Modus erfolgen soll. Nachdem der Benutzer die gewünschten
Einstellungen getroffen hat, kann die Darstellung durch einen Klick auf den
Knopf Start Rendering“ gestartet werden. Durch einen Klick auf Defaults“
”
”
werden die Optionen auf die voreingestellten Standardwerte zurückgesetzt.
4.3
Level-Loader
Die Aufgabe eines Loaders (Laders) ist es, Daten, die in einem bestimmten
Format vorliegen, zu lesen und so zu verarbeiten, dass sie von einer Applikation weiter verwendet werden können. Ein Level-Loader ist eine Spezialform,
die Daten von Spiele-Leveln (Welten, Szenerien) lädt und so aufbereitet, dass
diese vom Renderer des Spiels (im Falle von JSceneViewer3D ist dies Java
3D) dargestellt werden können. Die Implementierung von JSceneViewer3D
sieht vor, dass für jedes zu verarbeitende Level-Format (Quake III usw.) ein
eigener Loader existiert. Für die Rendering-Einheit ist es daher nicht notwendig zu wissen, welches Format sie gerade darstellt, denn es obliegt dem
Loader, die Daten in ein für sie verwertbares Format zu konvertieren.
Die Auswahl des passenden Level-Loaders erfolgt in JSceneViewer3D bereits mit dem Öffnen der zu ladenden Datei. Ist die Datei als gültig empfunden worden (siehe dazu Abschnitt 4.2), speichert die Applikation den gegenwärtigen Typ des Levels. Startet nun der Benutzer den Rendervorgang, wird
KAPITEL 4. JSCENEVIEWER3D
40
zunächst der für den aktuellen Level-Typ zugeordnete Loader ausgewählt.
Diese Auswahl erfolgt mit Hilfe der Klasse LoaderFactory. Sie besitzt die
statische Methode createLevelLoader(LevelType type), welche den momentanen Typ des Levels als Parameter erhält. In dieser Methode wird dann
ein Objekt des passenden Level-Loaders erzeugt und zurückgegeben.
createLevelLoader(LevelType type) basiert dabei zum Teil auf dem
Entwurfsmuster Fabrikmethode“ (auch als Factory Method oder nur Fac”
tory bezeichnet), welches in [6] eingehend beschrieben wird. Alle LevelLoader müssen die Schnittstelle com.sun.j3d.loaders.Loader implementieren. Diese ist Teil des Java 3D API und sorgt dafür, dass ein Loader definierte Zugriffsmethoden besitzt und die geladenen Daten in einem ebenfalls
definierten Format ausgibt. Für den in JSceneViewer3D implementierten
Loader bedeutet dies, dass er auch in anderen Applikationen, die Quake III
Levels mittels Java 3D darstellen wollen, problemlos verwendet werden kann.
Die Fabriksmethode nützt dabei das Konzept der gemeinsamen Schnittstelle
aus und definiert, dass der Rückgabewert ein Objekt sein wird, welches diese
implementiert. Der konkrete Objekttyp ist nicht bekannt und wird erst zur
Laufzeit bestimmt. Der aktuelle Level-Typ wird herangezogen und der entsprechende Loader wird erzeugt und zurückgegeben. Im Falle eines Quake
III Levels wird ein Exemplar von Quake3LevelLoaderJ3D angelegt.
Entsprechend der implementierten Schnittstelle bietet der Loader drei
Methoden, um Daten zu laden. Diese sind: load(String file), load(URL
url) und load(Reader reader). Der Rückgabewert ist in allen drei Fällen das Interface com.sun.j3d.loaders.Scene, welches definierten Zugriff
auf einen, von einem Loader erstellten Szenengraphen bietet. In der momentan vorliegenden Version des Loaders sind jedoch nur die Methoden mit
java.lang.String und java.net.URL als Parametertypen implementiert.
Dies ist auf Probleme beim Auslesen der Daten aus einem java.io.Reader
Objekt zurückzuführen. Jenes bietet Zugriff auf die in ihm gekapselten Zeichen-Ströme (Character-Streams), also in Form von frei darstellbarem Text.
Für den Loader werden aber nicht konvertierte Binärdaten benötigt. Generell sind die Datentypen, die als Parameter für die load() Methode benötigt werden, schlecht gewählt. Zumeist dürften von einem Level-Loader
Daten aus einer Datei geladen werden. Der Datentyp java.io.File wäre
hierbei als Parameter sicher besser geeignet als etwa ein java.io.Reader.
Auch die Möglichkeit, den Pfad zu einer Datei mittels java.lang.String
und java.net.URL angeben zu können, erweist sich als nicht unbedingt notwendig, zumal sich Pfade aus beiden Datentypen mit Methoden der Standardbibliothek sehr leicht in den jeweils anderen konvertieren lassen.
Mit dem Aufruf einer load() Methode beginnt das eigentliche Laden
des Levels. Abbildung 4.5 stellt die Methode dar. Die Level-Datei wird nun
zunächst in ein Objekt der Klasse PK3File geladen. Sie besitzt eine Datenkomponente (Exemplarvariable) des Typs java.util.zip.ZipFile, welche
die Quake III Level-Datei intern repräsentiert. Dies ist möglich, da das PK3
KAPITEL 4. JSCENEVIEWER3D
41
public Scene load(String file) throws FileNotFoundException,
IncorrectFormatException,
ParsingErrorException {
Scene quakeScene = null;
try {
PK3File pakFile = new PK3File(file);
ZipEntry bspfile = pakFile.getBSPFile();
InputStream bspStream = pakFile.getInputStream(bspfile);
byte[] data = ByteArrayBuilder.fromStream(bspStream);
bspStream.close();
quakeScene = processData(data, pakFile);
} catch (IOException e) {
e.printStackTrace();
}
return quakeScene;
}
Abbildung 4.5: Die Methode load() des Quake III Level-Loaders.
Format wie bereits zuvor erwähnt ein Zip-Container ist. Neben der Repräsentation als Zip-Datei bietet die Klasse PK3File Zugriffsmethoden auf einzelne Dateien im Container und deren Inhalte. Diese Inhalte werden von
Java als Datenstrom (Stream) dargestellt. Mit Hilfe jener Datenströme können die Inhalte der Datei gelesen und auch verändert werden. Details zu
Datenströmen in Java können [23] entnommen werden.
Durch den Aufruf der Methode getBSPFile() wird aus der PK3-Datei
die eigentliche Level-Datei extrahiert. Sie hat die Erweiterung .bsp und
enthält alle Geometriedaten, Texturkoordinaten, Informationen über verwendete Lightmaps usw., die für eine korrekte Darstellung benötigt werden.
Im Folgenden wird für diese Datei der Name BSP-Datei verwendet (das
Kürzel BSP bezeichnet den in dieser Arbeit in Abschnitt 2.3.3 beschriebenen BSP-Baum, der in Quake III beim Rendering zum Einsatz kommt).
Um den Inhalt der BSP-Datei auslesen zu können, wird mittels der Methode
getInputStream() auf den zugehörigen Datenstrom zugegriffen. Die Daten
im Quake III Format sind sequentiell angeordnet, wobei die Startpunkte der
einzelnen Datenpakete durch Offsets ab Dateianfang festgelegt sind (siehe
dazu auch Anhang A). Diese Offsets sind in Byte spezifiziert, daher bietet es sich an, den Datenstrom in ein Byte-Array (Datenfeld bestehend aus
Bytes) zu konvertieren. Genau das erledigt die Methode fromStream() der
KAPITEL 4. JSCENEVIEWER3D
42
Klasse ByteArrayBuilder. Nach ihrem Aufruf stehen alle Level-Daten in
einem Byte-Array zur Verfügung, welches nun in weiterer Folge ausgelesen
und verarbeitet werden kann.
4.3.1
Auslesen der Lump-Daten
Die Verarbeitung und Aufbereitung der gesamten Level-Daten geschieht in
der Methode processData(byte[] data, PK3File pakFile) (Abbildung
4.6). Der zweite Parameter neben dem Byte-Array ist die PK3-Datei. Diese
wird nochmals benötigt, da in ihr alle Texturen, die in der Welt verwendet
werden, enthalten sind. Aus dem Array werden nun die Daten, die logisch
zu so genannten Lumps zusammengefasst sind, ausgelesen.
Der Begriff Lump (zu Deutsch Klumpen) bezeichnet ein Stück Information innerhalb der BSP-Datei. Das Quake III Format enthält insgesamt 17
solcher Lumps, die hintereinander angeordnet sind. Ein so genannter Header
am Beginn der Datei enthält Informationen über die Position und die Länge
der einzelnen Pakete. Anhang A enthält eine detaillierte Beschreibung aller
Lumps und deren Bedeutung. Die Informationen stammen zum größten Teil
aus [17].
Um das Byte-Array, das zuvor angelegt wurde, zu verarbeiten, wurde die
Struktur des Quake III Level-Formats mittels Java-Objekten nachempfunden. So werden der Header und alle benötigten Lumps durch eigene Objekte
repräsentiert, die die enthaltenen Informationen kapseln.
Header-Informationen
Zunächst wird das Header-Objekt, welches Informationen über den Aufbau
der Datei enthält, angelegt. Es wird durch die Klasse BSPHeader repräsentiert. Im Konstruktor wird zu Beginn das Byte-Array wieder in einen Datenstrom verwandelt. Hierbei muss die Anordnung der Bytes beachtet werden.
Die Programmiersprache Java verwendet das so genannte Big-Endian System, in dem das höchstwertige Byte an der niedrigsten Stelle im Speicherbereich geschrieben wird. Die Daten des Quake III Levels liegen jedoch
im Little-Endian System (höchstwertiges Byte an der höchsten Stelle im
Speicherbereich) vor. Somit würde bei einer direkten Verwendung der ByteDaten die Reihenfolge nicht mehr stimmen. Daher wird bei der Umwandlung in einen Datenstrom die Klasse LittleEndianDataInputStream verwendet, welche wie die in der Java-Standardbibliothek enthaltene Klasse
java.io.InputStream Methoden zum sequentiellen Auslesen von bestimmten Datentypen (readInt(), readFloat(), usw.) bereitstellt, jedoch eine
korrekte Verschiebung der Bytes vornimmt. Die ersten beiden Werte im
Header spezifizieren den Typ der Datei und die verwendete Version. Sie
werden allerdings nicht weiter benötigt. In weiterer Folge werden die Offsets
und Längen aller 17 Lumps ausgelesen und in einem Array gespeichert. Die
KAPITEL 4. JSCENEVIEWER3D
43
private Scene processData(byte[] data, PK3File pakFile
throws IOException {
SceneBase scene = new SceneBase();
BSPHeader header = new BSPHeader(new ByteArrayInputStream(data));
BSPEntityLump entities = new BSPEntityLump(header,
new ByteArrayInputStream(data));
BSPVertexLump vertices = new BSPVertexLump(header,
new ByteArrayInputStream(data));
BSPMeshvertLump meshverts = new BSPMeshvertLump(header,
new ByteArrayInputStream(data));
BSPTextureLump textures = new BSPTextureLump(header,
new ByteArrayInputStream(data), pakFile);
BSPLightmapLump lightmaps = new BSPLightmapLump(header,
new ByteArrayInputStream(data));
BSPFaceLump faces = new BSPFaceLump(header,
new ByteArrayInputStream(data));
BranchGroup branchGroup = new BranchGroup();
faces.render(vertices, meshverts, textures, branchGroup);
entities.process(branchGroup);
scene.setSceneGroup(branchGroup);
return scene;
}
Abbildung 4.6: Die Methode processData(). Für jeden Lump wird ein
Objekt angelegt, anschließend wird der Szenengraph erzeugt.
Anordnung der Lumps ist vom Quake III -Format vorgegeben. Zugriffsmethoden erlauben im weiteren Verlauf das Auslesen dieser Daten.
Entitäten
Der erste ausgelesene Lump ist der Entitäten-Lump. Er enthält keine Informationen zur Erzeugung des Levels sondern beinhaltet die Position von
so genannten Entitäten in der Szene. Diese bestimmen etwa die Startposition des Spielers in der Welt, Positionen für Bonusgegenstände oder auch
die Koordinaten der Lichtquellen. In JSceneViewer3D werden drei Entitäten
KAPITEL 4. JSCENEVIEWER3D
44
ausgelesen und gespeichert:
worldpawn: Enthält allgemeine Informationen zum aktuellen Level.
Das Attribut ambient beinhaltet die Farbe der ambienten Beleuchtung (Umgebungsbeleuchtung), im Attribut message kann ein beliebiger Text (meist eine Nachricht des Level-Designers) stehen.
light: Diese Entität repräsentiert eine Lichtquelle. Verschiedene Attri-
bute beschreiben die Lichtquelle näher. origin enthält die Position,
_color die Farbe der Lichtquelle. Das Attribut light bestimmt die
Intensität. Wird die Entität nur mit diesen drei Attributen spezifiziert,
handelt es sich um eine Punktlichtquelle. Werden zusätzlich noch target und radius angegeben, so wird das Licht als kegelförmiges SpotLicht kreiert, welches auf einen bestimmten Punkt ausgerichtet ist.
infoplayerdeathmatch: Dahinter verbirgt sich die Startposition des
Spielers in einer Quake III Welt. Das Attribut origin enthält die Koordinaten, angle die Rotation des Spielers. Der Eintrag arena enthält
die Nummer der Arena, in der der Spieler startet, falls ein Level mehrere Arenen (große Regionen) enthält.
Beim Anlegen des Objekts für den Entitäten-Lump wird zuerst erneut
LittleEndianDataInputStream aus dem Byte-Array erzeugt. Danach wird
im Datenstrom mittels des im Header angegebenen Offsets zu einer bestimmten Stelle gesprungen. Von dort aus wird entsprechend der Länge,
die im Header für diesen Lump gespeichert ist, eine bestimmte Anzahl
von Bytes ausgelesen. Die Bytes werden im Anschluss in eine Zeichenkette
(java.lang.String) konvertiert und der Klasse EntityParser übergeben.
Jene Klasse arbeitet die Zeichenkette ab und extrahiert die zuvor erläuterten Entitäten. Aus jeder Entität wird dabei ein Objekt erzeugt. Die entstehenden Objekte gehören den Klassen WorldSpawnEntity, LightEntity
und InfoPlayerDeathmatchEntity an. Sie sind alle von der abstrakten Basisklasse BSPEntity abgeleitet und befinden sich aus Gründen der besseren
Übersicht im Paket net.codingmonkey.jsceneviewer3d.io.q3.entites.
Die Klasse BSPEntityLump verwaltet alle neu kreierten Entitäten-Objekte.
Diese werden im späteren Verlauf in den Szenengraphen integriert (siehe
Abschnitt 4.4).
Vertices
Im nächsten Schritt liest der Level-Loader alle Vertices (Eckpunkte eines
Polygons) der Szene ein. Diese werden in der Klasse BSPVertexLump gespeichert. Ein einzelner Vertex wird dabei wiederum durch eine eigene Klasse
(BSPVertex) repräsentiert. Ein Vertex definiert sich durch seine Position,
seine Texturkoordinaten für Texturen und Lightmaps, seine Normale und
durch seine Farbe. Der Vorgang des Einlesens gleicht dem der anderen
KAPITEL 4. JSCENEVIEWER3D
45
Lumps. Zunächst wird ein Datenstrom angelegt, aus dem dann ab dem
im Header definierten Offset eine gewisse Anzahl an Bytes gelesen wird.
Während des Vorgangs werden pro Vertex zunächst zehn float Gleitkommawerte ausgelesen (drei Werte für die x, y und z Koordinaten der Position,
zwei mal zwei Werte für die u und v Texturkoordinaten und wiederum drei
Werte für die Normale). Anschließend werden noch vier Bytes für die Farbinformation (rot, grün, blau und alpha) gelesen. Der Vorgang wird für die
gesamte Anzahl an Vertices wiederholt. Diese lässt sich zuvor errechnen, in
dem die Länge des Vertex-Lumps in Bytes durch die Größe eines Verticis in
Bytes dividiert wird.
Mesh-Vertices
Der nächste vom Loader verarbeitete Lump ist der MeshVert-Lump. Das
Quake III Format beschreibt MeshVerts als Offsets zum ersten Vertex in
einem Polygon. Anders ausgedrückt handelt es sich dabei um Vertex-Indices,
die nicht absolut auf ein komplettes Vertex-Array, sondern lediglich relativ
auf die Vertices des aktuellen Polygons anwendbar sind. Das Auslesen dieser
Indices erfolgt analog zu den anderen Lumps. Die Daten werden in der Klasse
BSPMeshVertLump gespeichert.
Texturen
Die Verwendung von Texturen im Quake III Format ist genau definiert. Zum
Einsatz kommen Grafiken in den Formaten JPEG und TGA (Targa). Die
Seitenlängen jeder Textur müssen zudem einer Potenz zur Basis 2 (2, 4, 8, 16,
32, 64, 128, 256, usw.) entsprechen. Um Texturen laden und anzeigen zu können, müssen deren Namen bekannt sein. Der Texturen-Lump enthält genau
diese Informationen. Er definiert eine Textur über deren Namen, einen Parameter (so genannte Flags) und einen Zahlenwert, der für die Beschreibung
des Inhalts verwendet werden kann (Typennummer). Diese Informationen
werden in ein Array aus so genannten TextureInfo Objekten gelesen. Die
Anzahl wird dabei durch die Division der Länge des Lumps mit der Größe
eines TextureInfo-Objekts bestimmt. Nachdem die Namen aller Texturen
ausgelesen wurden, wird die Methode createTextures(PK3File pakFile)
aufgerufen. Sie dient dazu, auf Basis der soeben gesammelten Informationen, Java-Objekte von den Texturen anzulegen, die der Renderer dann zur
Darstellung verwenden kann. Die Java-Standardbibliothek stellt seit Version 1.4 mit dem Paket javax.imageio eine komfortable Möglichkeit zur
Verfügung, Bilder mit Hilfe eines einzigen Methodenaufrufs zu laden. Leider ist die Auswahl der verwendbaren Formate begrenzt, und so wird zwar
das JPEG Format unterstützt, TGA jedoch nicht. Dieser Umstand macht
die Verwendung des Java Image Management Interfaces (JIMI ) notwendig.
JIMI ist eine mittlerweile als veraltet angesehene Bibliothek zum Laden
KAPITEL 4. JSCENEVIEWER3D
46
von Bildern unter Java. Ihre Nachfolger sind die bereits erwähnten ImageIO
Klassen und das Java Advanced Imaging (JAI ) API. Diese bieten jedoch
allesamt keine Unterstützung für das TGA Format, weshalb schließlich doch
auf JIMI zurückgegriffen werden musste.
Nachdem aus der entsprechenden TextureInfo Klasse der Name der
Textur extrahiert wurde, wird versucht, die entsprechende Textur entweder mit .jpg oder .tga Dateierweiterung aus der PK3 Datei zu entpacken.
Glückt dies, wird auf den darunter liegenden Datenstrom zugegriffen und
mit ihm unter Verwendung des mit Java 3D mitgelieferten Textur-Laders
(Klasse com.sun.j3d.utils.image.TextureLoader) ein Objekt vom Typ
javax.media.j3d.Texture erzeugt. Alle so erstellten Texturen werden im
BSPTextureLump Objekt gespeichert und zu einem späteren Zeitpunkt verwendet.
Lightmaps
Lightmaps sind rein aus Farbwerten bestehende Texturen, die von Quake
III verwendet werden, um das Level beleuchten zu können, ohne rechenintensive Lichtquellen verwenden zu müssen. In der BSP-Datei werden diese
im Lightmap-Lump spezifiziert. Die Texturen sind 128 × 128 Pixel groß
und durch RGB (rot, grün, blau) Werte definiert. Die Farbwerte werden in
einzelne Lightmap Objekte verpackt und von der Klasse BSPLightmapLump
verwaltet. Die Lightmaps kommen während des Rendervorgangs zum Einsatz.
Faces
Als Faces bezeichnet die Quake III Spezifikation Polygone, also Flächen, die
aus mehreren Vertices bestehen. Im Regelfall sind dies Dreiecke, aus denen
in weiterer Folge beliebige Flächen erzeugt werden.
Die Informationen über Faces in einem Quake III Level sind im FaceLump enthalten. JSceneViewer3D verwaltet sie in der Klasse BSPFaceLump.
Der Face-Lump enthält eine bestimmte Anzahl von Faces (repräsentiert
durch die private innere Klasse Face), die sich durch die Division der Länge
des Lumps mit der Größe eines Faces errechnen lässt. Ein einzelnes Face
enthält eine Reihe von Attributen, die genau spezifizieren, wie es aufgebaut
ist. Die Attribute sind:
texture: Index auf die Textur, die auf das Face angewandt werden
soll. Index referenziert eine Textur aus der Klasse BSPTextureLump.
effects: Index auf den Effekt-Lump, falls für dieses Face ein Effekt
(wie etwa Nebel) verwendet werden soll. Andernfalls ist der Index 1. Das Attribut ist in der gegenwärtigen Implementierung nicht von
Bedeutung, da zur Zeit keine Effekte eingesetzt werden.
KAPITEL 4. JSCENEVIEWER3D
47
type: Spezifiziert, ob es sich bei dem Face um ein Mesh, ein Polygon,
einen Bézier-Patch oder ein Billboard handelt. Auf diese Subtypen
wird in Abschnitt 4.4 genauer eingegangen.
vertex: Index des ersten Verticis im Face.
n vertexes: Anzahl der Vertices im Face.
meshvert: Index des ersten Mesh-Verticis im Face.
n mehsverts: Anzahl der Mesh-Vertices (Indices) im Face.
lm index: Index der Lightmap. Er referenziert auf die Klasse BSPLightmapLump.
lm start: Spezifiziert die linke obere Ecke der Lightmap für dieses
Face in der gesamten Lightmap-Textur.
lm size: Beziffert die Ausdehnung der Lightmap-Textur für dieses
Face innerhalb der gesamten Lightmap.
lm origin: Weltkoordinaten der Lightmap.
lm vecs: Textur-Vektoren für die Lightmap.
normal: Oberflächen-Normale für dieses Face.
size: Größe eines Bézier-Patches (falls es sich bei dem Face um einen
solchen handelt).
Alle Attribute werden nun aus der BSP-Datei ausgelesen und in der
Klasse BSPFaceLump gespeichert. Nach dem Abschluss dieses Vorgangs ist
das eigentliche Laden des Levels abgeschlossen. Alle in der vorliegenden Version notwendigen Daten wurden aus der BSP-Datei gelesen und können nun
für die Darstellung verarbeitet werden.
4.4
Rendering
Um eine virtuelle Welt mit Java 3D darzustellen, bedarf es streng genommen gar nicht des Entwurfs eines eigenen Renderers. Java 3D übernimmt
diese Aufgabe selbst und verlangt lediglich einen zuvor korrekt konstruierten
Szenengraphen (wie in Abschnitt 3.1.1 beschrieben).
Da also der eigentliche Rendervorgang völlig von Java 3D übernommen
wird, soll an dieser Stelle der Aufbau des Szenengraphen beschrieben werden,
auch wenn der Vorgang ebenso zum Level-Loading gezählt werden könnte. In
der Struktur von JSceneViewer3D wird die Integration der Daten in den Szenengraphen auch vom Quake III Level-Loader vorgenommen. Dies geschieht
in der Klasse BSPFaceLump. Sie ist die wichtigste aller Lump-Klassen, da sie
die Informationen über die Zusammensetzung der Geometrie enthält und
auf die anderen Lump-Klassen zugreifen muss.
KAPITEL 4. JSCENEVIEWER3D
4.4.1
48
Erstellung des Szenengraphen
Zuständig für die Eingliederung der Daten in den Java 3D Szenengraph ist
die Methode render() der Klasse BSPFaceLump. In dieser Methode wird
über alle zuvor angelegten Objekte der inneren Klasse Face (also über alle zu
erstellenden Faces) iteriert und wiederum eine Methode render(), diesmal
jedoch die jedes einzelnen Faces, aufgerufen.
Wie in Abbildung 4.7 dargestellt, wird zunächst für jedes Face ein Objekt
der Klasse javax.media.j3d.Appearance, welches für das Erscheinungsbild
von Geometrie zuständig ist, erzeugt. In diesem Objekt können alle so genannten Rendering States (Zustände des Objekts während des Renderings)
manipuliert werden. So werden zunächst Attribute für die Darstellung eines
Polygons mittels der Klasse javax.media.j3d.PolygonAttributes gesetzt.
Dem Renderer wird etwa mitgeteilt, dass auch vom Betrachter abweisenden
Polygone gerendert (dargestellt) werden sollen (Aufruf der Methode setCullFace(PolygonAttributes.CULL_NONE)), dass jedes erzeugte Polygon
auch vollständig gefüllt werden soll (mittels Aufruf der Methode setPolygonMode(PolygonAttributes.POLYGON_FILL)) und dass die Normalen
der vom Betrachter abweisenden Flächen gedreht werden sollen (per setBackFaceNormalFlip(true)). Danach wird mit Hilfe des Textur-Indicis die
korrekte Textur geladen und ebenfalls dem Erscheinungsbild hinzugefügt.
Durch die Erzeugung und Anwendung eines Objekts der Java 3D-Klasse
javax.media.j3d.TextureAttributes können Attribute für die soeben erzeugte Textur, z. B. die Art wie die Farbe der Textur mit einstrahlendem
Licht (bzw. dessen Farbe) reagiert, gesetzt werden. Sind Texturen deaktiviert, wird ein Material der Klasse javax.media.j3d.Material erzeugt,
welches in weiterer Folge die Vertex-Farben aufnehmen wird.
Im Anschluss an die Erstellung des Erscheinungsbildes folgt die Erzeugung der Level-Geometrie. Dabei ist zunächst das Face Attribut type,
welches bereits in Abschnitt 4.3 erwähnt wurde, von großer Bedeutung.
Das Quake III -Level-Format unterstützt vier verschiedene Typen von Faces:
Meshes, Polygone, Bézier-Patches und Billboards. Meshes und Polygone sind
jeweils Ansammlungen von Dreiecken, wobei Meshes auch nicht zusammenhängende Flächen enthalten können, während Polygone immer eine große,
verbundene Fläche bilden. Der Rendervorgang ist jedoch für beide Typen
identisch. Darum kann in der render() Methode, bei der Abfrage des FaceTyp Attributs auch der selbe Code für beide Varianten verwendet werden.
Abbildung 4.9 stellt diesen Teil der Methode dar. Zunächst wird ein Array
aus Dreiecken der Klasse javax.media.j3d.TriangleArray angelegt. Diese
Datenstruktur nimmt eine Liste von Vertices auf, die intern zu Dreiecken zusammen gefasst werden. So wird das erste Dreieck durch die Vertices an den
Stellen 0, 1 und 2 gebildet, das Folgende mit den Vertices an den Positionen
3, 4 und 5 usw. (Abbildung 4.8 (a)). Da somit Vertices auch bei angrenzenden
Dreiecken nicht mehrfach verwendet werden, ist diese Datenstruktur in der
KAPITEL 4. JSCENEVIEWER3D
49
public void render(BSPVertexLump vertices, BSPMeshvertLump meshverts,
BSPTextureLump textures, BranchGroup branchGroup) {
Appearance appearance = new Appearance();
PolygonAttributes attributes = new PolygonAttributes();
attributes.setCullFace(PolygonAttributes.CULL_NONE);
attributes.setPolygonMode(PolygonAttributes.POLYGON_FILL);
attributes.setBackFaceNormalFlip(true);
appearance.setPolygonAttributes(attributes);
int vertexFormat;
if (RenderingOptions.isTextured()) {
Texture tex = textures.getTexture(texture);
appearance.setTexture(tex);
TextureAttributes texAttributes = new TextureAttributes();
texAttributes.setTextureMode(TextureAttributes.MODULATE);
appearance.setTextureAttributes(texAttributes);
vertexFormat = GeometryArray.COORDINATES | GeometryArray.NORMALS |
GeometryArray.TEXTURE_COORDINATE_2;
}
else {
Material material = new Material();
material.setAmbientColor(new Color3f(0.0f, 0.0f, 0.0f));
material.setSpecularColor(new Color3f(0.1f, 0.1f, 0.1f));
appearance.setMaterial(material);
vertexFormat = GeometryArray.COORDINATES | GeometryArray.NORMALS |
GeometryArray.COLOR_4;
}
[...]
}
Abbildung 4.7: Der Beginn der Methode render() für ein Face. Hier wird
die so genannte Appearance, also das Aussehen des Faces, festgelegt. Eine
Rolle spielen dabei vor allem das Material und die Textur.
Regel nicht so effizient wie etwa ein Triangle-Strip Array (Abbildung 4.8 (b))
oder ein Triangle-Fan Array (Abbildung 4.8 (c)), die durch das Teilen von
Vertices Rechenzeit sparen können.
Mit der fertigen Datenstruktur und dem zuvor angelegten Erscheinungsbild wird dann ein neues Mesh oder Polygon erzeugt. Java 3D verwendet
dafür den allgemeinen Begriff 3D-Form“. Repräsentiert wird sie durch die
”
Klasse javax.media.j3d.Shape3D. Diese ist gleichzeitig ein Leaf-Knoten
und kann somit in die BranchGroup des Szenengraphen gehängt werden.
Um gebogene oder kurvige Flächen darzustellen, bedient sich das Quake
III -Format der so genannten Bézier-Patches. Sie sind die dreidimensionale
KAPITEL 4. JSCENEVIEWER3D
(a)
50
(b)
(c)
Abbildung 4.8: Verschiedene Arten, Dreiecke zu speichern. Einzelne Dreiecke als Triangle-Array (a), zusammenhängende Dreiecke als Triangle-Strip
(b), zusammenhängende Dreiecke als Triangle-Fan (c).
Variante von Bézier-Kurven und eine effektive Methode, gekrümmte Flächen
zu speichern. Es werden nämlich nicht alle Vertices, die eine Kurve darstellen (für eine perfekte Kurve also theoretisch unendlich viele) in der LevelBeschreibung gespeichert, sondern lediglich so genannte Kontroll-Vertices,
die eine ungefähre Form der gewünschten Kurve vorgeben. Anhand dieser
Vertices kann dann mittels so genannter Vertex-Tessellierung die gekrümmte
Fläche berechnet werden. Dabei werden neue Vertices erzeugt, die dann für
die Darstellung des Patches verwendet werden. Je höher der Tessellierungsgrad ist, desto mehr Vertices werden errechnet und desto runder wirkt die
Kurve. Es bleibt also der jeweiligen Anwendung überlassen, wie genau sie
die Berechnungen der gekrümmten Flächen durchführen möchte. Somit kann
zwischen einem rechenintensiveren Modell und einer weniger exakten Darstellung abgewogen werden. Abbildung 4.10 zeigt einen Bézier-Patch mit
dazugehörigen Kontrollpunkten. In Abbildung 4.11 werden die Auswirkungen verschiedener Tessellierungsgrade dargestellt. Eine Übersicht über unterschiedliche Algorithmen und Methoden bei der Berechnung von BézierPatches gibt [1].
Die Tessellierung geschieht innerhalb der render() Methode durch den
Aufruf von tessellate(int level, BSPVertexLump vertices). Jene Methode erhält als Parameter den Tessellierungsgrad und den Vertex-Lump.
Letzterer wird benötigt, um auf die Kontroll-Vertices eines jeden Patches
zugreifen zu können. Der verwendete Algorithmus ist eine Modifikation des
KAPITEL 4. JSCENEVIEWER3D
51
public void render(BSPVertexLump vertices, BSPMeshvertLump meshverts,
BSPTextureLump textures, BranchGroup branchGroup) {
[...]
switch (type) {
case Face.POLYGON:
case Face.MESH:
TriangleArray array = new TriangleArray(nMeshverts, vertexFormat);
for (int i = 0; i < nMeshverts; i++) {
int meshvertOffset = meshverts.getOffset(meshvert + i);
int index = vertex + meshvertOffset;
array.setCoordinate(i, vertices.getVertex(index));
array.setNormal(i, vertices.getNormal(index));
if (RenderingOptions.isTextured()) {
array.setTextureCoordinate(0, i, vertices.getTexCoord(index));
}
else {
array.setColor(i, vertices.getColor(index));
}
}
Shape3D shape = new Shape3D(array, appearance);
branchGroup.addChild(shape);
break;
[...]
}
}
Abbildung 4.9: Das Rendering von Polygonen und Meshes. Zum Einsatz
kommt ein Array aus Dreiecken.
in [16] vorgestellten Ansatzes. Die Modifikationen betreffen vor allem die Anpassung der Datenstrukturen an Java bzw. Java 3D. Der Rückgabewert der
Tessellierungs-Methode ist ein Array aus neu generierten Vertices des Typs
BSPVertex. Da der Algorithmus die Vertices für eine Verwendung in einem
Triangle-Strip Array generiert, ist es nötig, Indices für einen korrekten Zugriff zu generieren. Dies geschieht in der Methode calculateIndices(int
level). Anschließend wird das Triangle-Strip Array (in Form der Klasse javax.media.j3d.TriangleStripArray) angelegt. Zusammen mit dem zuvor
erstellten Erscheinungsbild wird für jeden Bézier-Patch ein 3D-Form-Objekt
kreiert und dem Szenengraphen hinzugefügt.
Nachdem die Geometrie erzeugt wurde, werden nun noch die bereits
ausgelesenen Entitäten verarbeitet. Um diese Aufgabe zu erledigen, wird
KAPITEL 4. JSCENEVIEWER3D
52
Abbildung 4.10: Die schematische Darstellung eines Bézier-Patches. Die
neun Kontroll-Vertices sind rot dargestellt.
die Methode process(BranchGroup branchGroup) der Klasse BSPEntityLump aufgerufen. Diese enthält bereits alle Entitäten in einer Datenstruktur gespeichert. Jedes einzelne Element wird nun abgearbeitet und entsprechend seines Typs verarbeitet. Handelt es sich um eine Entität des Datentyps WorldspawnEntity (von der in der Regel nur eine einzige existiert),
wird ein Objekt des Typs javax.media.j3d.AmbientLight angelegt und
mit den Werten der Entität initialisiert. Jenes Objekt wird anschließend zu
der als Parameter übergebenen BranchGroup hinzugefügt. Somit wird die
Szene mit ambientem Licht ausgeleuchtet. Bei Entitäten, die eine Lichtquelle
spezifizieren (LightEntity), wird zunächst überprüft, ob es sich um eine
Punktlichtquelle oder um ein Spot-Licht handelt. Dementsprechend wird
entweder ein Objekt der Klasse javax.media.j3d.PointLight oder javax.media.j3d.SpotLight angelegt, mit den aus der Entität ausgelesenen
Werten initialisiert und in den Szenengraphen integriert. Bei Entitäten des
Typs InfoPlayerDeathmatchEntity kann die Position der Kamera an die
darin spezifizierten Koordinaten gesetzt werden, um den Spieler an der richtigen Position starten zu lassen. Dies wurde in der momentan vorliegenden
Version noch nicht berücksichtigt.
Mit dem Verarbeiten der Entitäten endet die bis zu diesem Zeitpunkt aktive Methode processData(byte[] data, PK3File pakFile) und somit
auch die Konstruktion des Content-Branch-Graph des Szenengraphen.
4.4.2
Darstellung der Szene
Die Anzeige der Szene beginnt damit, dass die Basis-Applikation ein neues
Fenster vom Typ J3DRenderWindow öffnet. Diese Klasse ist eine Erweiterung
des Typs javax.swing.JFrame, die eine Rendering-Umgebung für Java 3D
KAPITEL 4. JSCENEVIEWER3D
53
(a)
(b)
(c)
(d)
Abbildung 4.11: Verwendung von Bézier-Patches mit unterschiedlichen
Tessellierungsstufen, um eine Krümmung darzustellen. Ein Durchlauf (a),
drei Durchläufe (b), sieben Durchläufe (c), zehn Durchläufe (d).
beinhaltet. Die Umgebung ist ein Objekt der Klasse J3DCanvas, welche in
letzter Instanz für die Darstellung verantwortlich ist. Die folgenden Schritte
entsprechen den in Absatz 3.1.1 beschriebenen. Zunächst wird ein virtuelles
Universum erzeugt, das die ganze Szene beinhalten wird. Danach wird die
Position des Betrachters gesetzt. In weiterer Folge werden der Hintergrund
auf eine definierte Farbe gesetzt und Behavior Klassen für die Navigation
mit Tastatur und Maus erstellt und initialisiert. Weiters wird der Zähler für
die Framerate samt Textausgabe für diese aktiviert. Schließlich wird noch
der Content-Branch-Graph aus dem vom Loader generierten Scene Objekt
entpackt und in das virtuelle Universum eingefügt.
JSceneViewer3D kann in einem finalen Schritt noch die Kompilierung des
KAPITEL 4. JSCENEVIEWER3D
public void render(BSPVertexLump vertices, BSPMeshvertLump meshverts,
BSPTextureLump textures, BranchGroup branchGroup) {
[...]
switch (type) {
[...]
case Face.PATCH:
BSPVertex[] tessellatedVertices = tessellate(TESSELLATION_LEVEL,
vertices);
int[] indices = calculateIndices(TESSELLATION_LEVEL);
int format = TriangleStripArray.COORDINATES |
TriangleStripArray.NORMALS |
TriangleStripArray.TEXTURE_COORDINATE_2;
int[] trianglesPerRow = new int[TESSELLATION_LEVEL];
for (int i = 0; i < trianglesPerRow.length; i++) {
trianglesPerRow[i] = 2 * (TESSELLATION_LEVEL + 1);
}
TriangleStripArray geometry = new TriangleStripArray(indices.length,
format, trianglesPerRow);
for (int i = 0; i < indices.length; i++) {
geometry.setCoordinate(i,
tessellatedVertices[indices[i]].getPosition());
geometry.setNormal(i,
tessellatedVertices[indices[i]].getNormal());
if (RenderingOptions.isTextured()) {
geometry.setTextureCoordinate(0, i,
tessellatedVertices[indices[i]].getTexCoord());
}
else {
geometry.setColor(i,
tessellatedVertices[indices[i]].getColor());
}
}
Shape3D patch = new Shape3D(geometry, appearance);
branchGroup.addChild(patch);
break;
[...]
}
}
Abbildung 4.12: Das Rendering von Bézier-Patches. Zuerst werden neue
Vertices und Indices generiert, anschließend wird der Patch mit einem
Triangle-Strip-Array gerendert.
54
KAPITEL 4. JSCENEVIEWER3D
55
Graphen veranlassen, um Optimierungen vorzunehmen. Diese Kompilierung
ist standardmäßig aktiviert und wird für gute Ergebnisse auch empfohlen.
Somit ist der Szenengraph komplett und wird angezeigt. Das Rendering
wird beendet, in dem das entsprechende Fenster geschlossen wird.
4.5
Logger
Die Aufzeichnung relevanter Daten ist ein wichtiger Bestandteil von JSceneViewer3D. Die Applikation soll nach der Darstellung der Szene wichtige
Daten speichern und dem Benutzer zur Ansicht oder Auswertung zur Verfügung stellen. Dabei hat dieser zwei Möglichkeiten, die Daten einzusehen.
Nachdem das Fenster mit der Darstellung geschlossen wurde, werden die
Daten einerseits im Logger Tab angezeigt und können andererseits dort per
Knopfdruck in eine Log-Datei gespeichert werden.
Um die zweite Möglichkeit zu bieten, verwendet die für das Logging
verantwortliche Klasse JSV3DLogger Elemente des bewährten Frameworks
log4j 4 .
4.5.1
log4j
Bei log4j handelt es sich um ein so genanntes Logging-Framework, also eine
Software-Komponente, die es erlaubt, während der Ausführung eines Programms Meldungen über den Status, mögliche Probleme oder andere relevante Informationen auszugeben. Dies kann notwendig werden, falls es nicht
möglich ist, eine Anwendung zu debuggen (Fehler zu suchen und zu beseitigen), etwa weil eine Ausführung mit Debugger zu langsam ist oder weil viele
parallele Threads das Debugging erschweren. Logging-Einträge müssen nur
einmal an den relevanten Stellen im Quellcode platziert werden und geben
zum Zeitpunkt des Aufrufs die entsprechenden Informationen ohne späteres
Zutun eines Entwicklers aus. Natürlich ist auch Logging nicht ohne Nachteile: Werden zu viele Einträge gesetzt, so kann die Applikation verlangsamt
werden. Zu viele generierte Log-Einträge schaden unter Umständen auch der
Übersicht.
Die Handhabung von log4j ist bewusst einfach gestaltet. Das Framework besteht aus drei Hauptteilen, die hierarchisch miteinander verbunden
sind. An der Spitze steht die Klasse org.apache.log4j.Logger. Fast alle
Zugriffe auf log4j Funktionen erfolgen über sie. Jeder Logger besitzt einen
Level (d. h. eine Stufe). Dieser bestimmt, ab welchem Wichtigkeitsgrad Meldungen ausgegeben werden. Die verfügbaren Levels (in aufsteigender Reihenfolge der Wichtigkeit geordnet) sind: DEBUG - INFO - WARN - ERROR
- FATAL. Wird also ein Logger mit dem Level INFO angelegt, gibt er nur
Meldungen mit mindestens dieser Priorität aus (d. h. alle, außer als DEBUG
4
http://logging.apache.org/log4j/
KAPITEL 4. JSCENEVIEWER3D
56
klassifizierte Meldungen werden in diesem Fall ausgegeben). Um nun LogMeldungen abzusetzen (und gleichzeitig zu klassifizieren), stellt die Klasse
die folgenden fünf wichtigen Methoden zur Verfügung: debug(Object message), info(Object message) und warn(Object message) für leichtere
Fehler, sowie error(Object message) und fatal(Object message) für
schwerere Fehler. Als Parameter kann jedes beliebige Objekt mitgegeben
werden.
org.apache.log4j.Logger beinhaltet zunächst jedoch noch keinerlei Information, wie die Ausgabe der Log-Meldungen erfolgen soll. log4j stellt
dafür die Schnittstelle org.apache.log4j.Appender zur Verfügung. Klassen, die diese Schnittstelle implementieren, bieten die Möglichkeit, die vom
Logger gesammelten Daten auf ein bestimmtes Medium auszugeben. Solche
Medien können die Konsole einer Applikation, eine Datei auf der Festplatte
oder auch eine entfernte Netzwerk-Ressource sein. Jeder Logger kann dabei
eine beliebige Anzahl an Appendern verwalten. Sobald eine Log-Meldung
erzeugt wird, wird sie an alle beim Logger registrierten Appender-Klassen
weitergeleitet und von diesen entsprechend ausgegeben.
Das Aussehen der Ausgabe bestimmt der dritte Teil der log4j Hierarchie – die Klasse org.apache.log4j.Layout. Mit ihrer Hilfe können LogMeldungen individuell formatiert und an die Bedürfnisse der jeweiligen Applikation angepasst werden. log4j beinhaltet bereits einige Layouts, um Daten in HTML, XML oder als einfachen Text auszugeben.
Details zur Architektur von log4j sowie eine Vielzahl an Anwendungsbeispielen gibt [7].
4.5.2
Implementierung des Loggers
Die zuvor bereits erwähnte Klasse JSV3DLogger enthält einerseits eine Instanz eines log4j Logger-Objekts zur Ausgabe der Daten in eine Datei und
andererseits Datenkomponenten zur internen Speicherung der gemessenen
Werte. Gespeichert werden Startzeit, Endzeit, Gesamtdauer, durchschnittliche Frames pro Sekunde sowie die Anzahl der Vertices und Polygone in
einem Level. Angelegt wird die Instanz des JSceneViewer3D Loggers mit
einem Dateinamen als Parameter. Jener bestimmt, wie die zu schreibende
Log-Datei benannt werden soll. Alternativ kann für den Parameter auch
null als Wert übergeben werden. In diesem Fall wird keine Datei angelegt,
sondern es werden alle Meldungen auf der Konsole ausgegeben.
Der Logger wird mit der Stufe INFO angelegt, in der momentanen Implementierung kommen keine anderen Stufen zum Einsatz. Als Appender
wird die selbst entwickelte Klasse BufferedAppender verwendet. Die Notwendigkeit für eine eigene Implementierung entstand durch den Umstand,
dass es dem Benutzer obliegen soll, ob eine Log-Datei geschrieben wird oder
nicht. Alle mit log4j mitgelieferten Appender verfolgen jedoch die Strategie, erzeugte Meldungen sofort auszugeben. Daher wurde die Klasse Buf-
KAPITEL 4. JSCENEVIEWER3D
57
feredAppender entwickelt. Ihre Aufgabe ist es, alle vom Logger abgegebenen Meldungen aufzunehmen und in einem Puffer zu speichern. An die
Klasse können weitere Appender angehängt werden, die dann die eigentliche Ausgabe auf ein Medium übernehmen. Diese wird durch die Methode
writeLogs() angeregt. Alle im Puffer gespeicherten Meldungen werden somit an die angehängten Appender weitergeleitet und der Zwischenspeicher
wird anschließend geleert. Auf diese Art und Weise kann die Erzeugung der
Log-Datei genau kontrolliert werden.
4.5.3
Aufrufe des Loggers
Mit dem Öffnen einer Level-Datei wird der Logger angelegt. Sobald das
Rendering gestartet wird, erfolgt die Setzung der Startzeit, und das LoggerObjekt wird an den Level-Loader übergeben. Nachdem jener die Geometrie
geladen hat, wird die Anzahl der Polygone und Vertices ausgelesen und
im Logger gespeichert. Mit der Anzeige des Darstellungsfensters wird die
Instanz des Loggers an dieses weiter gegeben. Sobald das Fenster wieder
geschlossen wird, d. h. wenn die Darstellung beendet wurde, liest der Logger
die errechnete durchschnittliche Framerate aus und speichert sie zusammen
mit der Endzeit und der Gesamtzeit.
Im Anschluss werden die soeben gesammelten Informationen im LoggerTab eingefügt. Nun kann der Benutzer noch mit einem Klick auf Save Logs“
”
eine Log-Datei auf die Festplatte schreiben. Dies schließt einen kompletten
Rendering- und Auswertungs-Vorgang ab.
Kapitel 5
Auswertung der
Performance-Daten
Nach Abschluss der Implementierung von JSceneViewer3D wurden Tests zur
Messung der Performance (Performanz, Leistungsmerkmale) durchgeführt.
Diese sollen darstellen, wie sich die Applikation mit verschiedenen Levels
und unterschiedlichen Parametern verhält. Nicht zuletzt sollen diese Daten
auch darüber Auskunft geben, ob die Umsetzung mit Java geeignet erscheint
oder nicht.
5.1
Test-Voraussetzungen und Ablauf
Alle Tests wurden auf dem selben Testsystem durchgeführt. Die verwendete
Hardware bestand aus einem AMD Athlon 64 3200+ (2.00 GHz) Prozessor und einer MSI NX6800 (GeForce 6800 Chipsatz, PCIe, 256 MB DDR
RAM) Grafikkarte mit der Treiberversion ForceWare 84.21. Das verwendete
Betriebssystem war Microsoft Windows XP mit Service Pack 2. Weiters wurden der Java Development Kit der Java 2 Platform Standard Edition (J2SE)
5.0 Update 7 sowie Java 3D 1.4.0 Update 1 verwendet. Zum Zeitpunkt der
Tests wurden alle anderen Anwendungen im System deaktiviert.
Für die Tests mit JSceneViewer3D wurden fünf verschiedene Levels mit
verschieden hoher Polygonanzahl getestet. Pro Level wurde der Test jeweils
mit der niedrigsten und der höchsten verfügbaren Auflösung (640 × 480 bzw.
1280 × 1024 Pixel) durchgeführt. Weiters wurde jeweils sowohl mit als auch
ohne kompiliertem Szenengraphen getestet.
Bei den einzelnen Durchläufen wurde durch jeden Level eine Minute lang
hindurch navigiert. Dabei wurde darauf geachtet, möglichst alle Teile der
Welt zu besuchen und trotz fehlender Kollisionserkennung weitestgehend auf
den begehbaren Wegen und Pfaden zu verweilen. Da aus Zeitgründen kein
automatisiertes Durchlaufen des Levels implementiert werden konnte, musste die Navigation per Hand“ erfolgen. Dadurch ergaben sich zwangsläufig
”
58
KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN
59
Ungenauigkeiten, da es so nicht möglich war, immer exakt die gleichen Wege
zu gehen. Es wurde daher versucht, diesen Umstand zu kompensieren, indem
in jedem Testmodus fünf Durchläufe hintereinander durchgeführt wurden.
Für jeden Testlauf wurde die Applikation neu gestartet, um keine Verzerrungen durch überhöhten Speicherverbrauch zuzulassen.
Unter diesen Voraussetzungen ergaben sich somit insgesamt 100 Testläufe mit JSceneViewer3D (Fünf Levels, zwei verschiedene Auflösungen,
Kompilierung des Szenengraphen an bzw. aus – ergibt zwanzig Modi mit
je fünf Durchläufen). Pro Modus wurde ein Testlauf mit einer Webcam, die
an einem eigenständigen Rechner betrieben wurde, gefilmt. Die entstandenen zwanzig Videos liegen dieser Arbeit bei. Zusätzlich wurde nach jedem
einzelnen Durchlauf die generierte Log-Datei gespeichert. Die so entstandenen 100 Dateien liegen ebenfalls bei (siehe Anhang B).
Um die erhaltenen Werte von JSceneViewer3D vergleichen zu können,
wurden die selben Messungen ebenfalls mit dem Originalspiel Quake III
Arena durchgeführt. Verwendet wurde Quake III Arena 1.32 mit der Challenge Pro Mode Arena (CPMA) Modifikation. Wiederum wurden die selben
fünf Levels mit den selben Auflösungen jeweils fünf Mal getestet. Da die
Kompilierung des Szenengraphen auf Quake III selbstverständlich nicht anwendbar ist, halbierten sich die Testläufe auf 50. Um die Daten zu messen
und auch in Log-Dateien speichern zu können, wurde die Software FRAPS 1
verwendet. Diese erlaubt es, ähnlich wie JSceneViewer3D, die durchschnittlichen Frames pro Sekunde sowie die Dauer des Messvorgangs zu berechnen
und zu protokollieren. Lediglich die Polygon- und Vertexanzahl wird nicht
gemessen. Da es sich jedoch um die selben Welten handelt, konnten die
bereits von JSceneViewer3D ermittelten Daten verwendet werden. Die von
FRAPS generierten Log-Dateien sowie die ebenfalls mit der externen Webcam aufgezeichneten Videos liegen der Arbeit bei.
Um den Vergleich der Levels zwischen JSceneViewer3D und Quake III zu
ermöglichen, musste letzteres im Vorfeld noch angepasst werden. Folgende
Änderungen wurden durchgeführt, um den Funktionsumfang der Darstellung von Quake III dem von JSceneViewer3D anzugleichen. Die Werte in
den eckigen Klammern bezeichnen die Konsolebefehle, die in Quake III eingegeben werden müssen, um das jeweilige Verhalten zu erzielen.
Neue Karte laden und Cheatmodus aktivieren (nötig, um die Kollisionserkennung zu deaktivieren) [\devmap kartenname]
Heads Up Display (Anzeige der Gesundheit, der verfügbaren Munition
usw.) entfernen [\cg_draw2d 0]
Modell der Waffe ausblenden [\cg_drawGun 0]
Fadenkreuz ausblenden [\cg_drawCrosshair 0]
1
http://www.fraps.com/
KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN
60
Schatten deaktivieren [\cg_drawShadows 0]
Von Vollbild- auf Fenstermodus wechseln [\r_fullscreen 0]
Alle Entitäten in der Szene (Bonusgegenstände, Munition usw.) ausblenden [\r_drawEntities 0]
Maximale Anzahl an Frames pro Sekunde von den voreingestellten 90
auf 999 setzen [\com_maxfps 999]
Kollisionserkennung deaktivieren und den ”freien Schwebemodus“ aktivieren [\noclip 1]
5.1.1
Die getesteten Levels
Die fünf verwendeten Welten stammen alle aus der Challenge Pro Mode
Arena Modifikation. Sie haben den Vorteil, dass pro PK3-Datei genau ein
Level enthalten ist, und sie sich daher leicht verarbeiten lassen. Folgende
Levels kamen bei den Tests zum Einsatz:
cpm24 – phrantic: 1031 Polygone, 5528 Vertices, dargestellt in Abbildung 5.1.
cpm25 – Ptolemy’s Wrath: 1767 Polygone, 11662 Vertices, dargestellt in Abbildung 5.2.
cpm4a – Realm of Steel Rats: 2835 Polygone, 16981 Vertices,
dargestellt in Abbildung 5.3.
cpm19 – Q3JDM9: 4634 Polygone, 25025 Vertices, dargestellt in
Abbildung 5.4.
cpm13 – Abusive Intentions: 8815 Polygone, 40203 Vertices, dargestellt in Abbildung 5.5.
5.2
Tests mit JSceneViewer3D
Im Folgenden werden die gemessenen Ergebnisse der Performance Tests mit
JSceneViewer3D tabellarisch dargestellt. Die in den Tabellen-Kopfzeilen verwendeten Abkürzungen stehen dabei für den jeweils verwendeten Modus.
640 C entspricht einer Auflösung von 640 × 480 Pixel mit kompiliertem
Szenengraphen (compiled). NC steht für nicht kompiliert (not compiled).
Tabelle 5.1 veranschaulicht die Ergebnisse der Tests mit der kleinsten
Karte cpm24. Auffällig hierbei ist vor allem, dass die Tests mit nicht kompiliertem Szenengraphen bessere Ergebnisse erzielen. Bei 640 × 480 Pixel
liegt der Geschwindigkeitsgewinn bei etwa 3 %, bei 1280 × 1024 Pixel sind
es bereits knapp 7 %.
KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN
Abbildung 5.1: Ein Screenshot des Levels cpm24.
Abbildung 5.2: Ein Screenshot des Levels cpm25.
61
KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN
Abbildung 5.3: Ein Screenshot des Levels cpm4a.
Abbildung 5.4: Ein Screenshot des Levels cpm19.
62
KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN
Abbildung 5.5: Ein Screenshot des Levels cpm13.
Tabelle 5.1: Die Ergebnisse der Messungen des Levels cpm24 in Frames pro
Sekunde (JSceneViewer3D).
640 C
640 NC
1280 C
1280 NC
Test 1
402,54
398,57
161,88
150,33
Test 2
397,45
411,46
151,56
150,96
Test 3
480,28
476,13
146,96
183,69
Test 4
397,30
435,34
160,00
176,29
Test 5
387,52
405,16
143,45
155,66
Schnitt
413,02
425,33
152,77
163,39
63
KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN
64
Tabelle 5.2: Die Ergebnisse der Messungen des Levels cpm25 in Frames pro
Sekunde (JSceneViewer3D).
640 C
640 NC
1280 C
1280 NC
Test 1
399,93
341,74
146,54
149,18
Test 2
419,16
386,54
162,46
157,68
Test 3
315,31
405,81
155,97
157,44
Test 4
389,18
403,90
200,94
135,26
Test 5
387,31
414,55
142,98
181,04
Schnitt
382,18
390,51
161,78
156,12
Tabelle 5.3: Die Ergebnisse der Messungen des Levels cpm4a in Frames pro
Sekunde (JSceneViewer3D).
640 C
640 NC
1280 C
1280 NC
Test 1
289,54
337,43
124,13
138,95
Test 2
316,01
323,56
134,82
139,28
Test 3
300,57
354,62
127,88
128,60
Test 4
344,13
265,25
142,71
139,64
Test 5
325,52
315,54
126,30
132,25
Schnitt
315,15
319,28
131,17
135,74
Tabelle 5.2 beleuchtet die Ergebnisse der Karte cpm25. Verglichen mit
cpm24 kommen wesentlich mehr Vertices auf ein Polygon. Bei der niedrigeren Auflösung ist wiederum die nicht kompilierte Version des Szenengraphen
schneller, bei der größeren Auflösung ist das Ergebnis umgekehrt. Die Unterschiede bewegen sich jedoch im niedrigen Prozentbereich und sind weitgehend vernachlässigbar.
Auch bei der Auswertung des Levels cpm4a zeigt sich, dass die Testergebnisse der nicht kompilierten Version um wenige Frames pro Sekunde
besser als die der kompilierten sind. Verglichen zur polygonärmsten Karte
nimmt die durchschnittliche Framerate bei 640 × 480 Pixel bereits um ca.
100 Frames pro Sekunde ab, bei 1280 × 1024 beträgt die Abnahme lediglich
etwa 20 Frames pro Sekunde.
Die in Tabelle 5.4 dargestellten Daten der Karte cpm19 zeigen erneut ein
differenziertes Bild. In der niedrigen Auflösung ist wiederum der unveränderte, nicht kompilierte Szenengraph performanter, in der hohen Auflösung
erreicht der kompilierte Graph etwa 3 Frames pro Sekunde mehr.
KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN
65
Tabelle 5.4: Die Ergebnisse der Messungen des Levels cpm19 in Frames pro
Sekunde (JSceneViewer3D).
640 C
640 NC
1280 C
1280 NC
Test 1
295,36
315,11
128,91
142,82
Test 2
313,68
311,89
155,27
137,45
Test 3
322,40
350,54
142,63
141,48
Test 4
301,12
299,51
144,97
146,35
Test 5
304,86
315,23
148,84
136,96
Schnitt
307,48
318,46
144,12
141,01
Tabelle 5.5: Die Ergebnisse der Messungen des Levels cpm13 in Frames pro
Sekunde (JSceneViewer3D).
640 C
640 NC
1280 C
1280 NC
Test 1
249,09
181,70
89,97
118,17
Test 2
213,25
249,39
119,72
89,70
Test 3
241,75
212,26
111,70
120,68
Test 4
221,14
237,02
122,07
97,92
Test 5
207,16
203,19
106,43
120,68
Schnitt
226,48
216,76
109,98
109,43
Bei cpm13, der größten aller Karten, tritt zum ersten Mal der Fall ein,
dass die kompilierte Version des Szenengraphen in beiden Auflösungen bessere Ergebnisse liefert. Sind es bei 640 × 480 Pixel noch etwa zehn Frames
pro Sekunde, so beträgt der Unterschied bei 1280 × 1024 Pixel gerade noch
einen halben Frame pro Sekunde. Tabelle 5.5 veranschaulicht dies.
5.3
Tests mit Quake III Arena
Im Folgenden werden die Ergebnisse des Performance Tests mit Quake III
dargestellt. Die Werte in den Kopfzeilen der Tabellen repräsentieren die
jeweils verwendete Auflösung. 640 entspricht 640 × 480 Pixel, 1280 steht für
1280 × 1024 Pixel.
Die in Tabelle 5.6 abgebildeten Ergebnisse der Karte cpm24 zeigen eine
konstante Framerate über alle fünf Tests hinweg. Bei der niedrigen Auflösung
ist die Framerate mehr als dreimal so hoch als bei der hohen Auflösung.
Tabelle 5.7 stellt die Ergebnisse der Karte cpm25 dar. Obwohl diese
KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN
66
Tabelle 5.6: Die Ergebnisse der Messungen des Levels cpm24 in Frames pro
Sekunde (Quake III).
640
1280
Test 1
785,95
222,55
Test 2
776,44
228,20
Test 3
774,46
228,46
Test 4
784,61
230,53
Test 5
748,40
226,45
Schnitt
773,97
227,23
Tabelle 5.7: Die Ergebnisse der Messungen des Levels cpm25 in Frames pro
Sekunde (Quake III).
640
1280
Test 1
849,74
241,93
Test 2
846,58
239,59
Test 3
850,65
240,50
Test 4
854,72
242,09
Test 5
845,09
242,39
Schnitt
849,36
241,30
mehr Polygone und Vertices als cpm24 enthält, wird sowohl in der niedrigen
als auch in der hohen Auflösung eine deutlich höhere Framerate erreicht.
Verantwortlich dafür ist die Architektur des Levels – cpm25 enthält mehr
enge Gänge, während cpm24 aus einem großen Raum mit einigen Abteilern besteht. Je weniger Geometrie sichtbar ist, desto weniger Sichtbarkeitsberechnungen sind im BSP-Baum nötig. Entsprechend höher ist auch die
Framerate.
Die Karte cpm4a zeigt ein ähnliches Bild: Eine sehr hohe Framerate in
der niedrigen Auflösung, in der hohen wird noch etwa ein Drittel dieses
Werts erreicht. Tabelle 5.8 enthält die genauen Werte.
In Tabelle 5.9 sind die Ergebnisse des zweitgrößten Levels dargestellt.
Auffallend ist der Einbruch der Framerate bei 640 × 480 Pixel. Diese liegt
hier knapp 200 Frames pro Sekunde unter denen der restlichen Levels und
ist somit nur noch doppelt so hoch wie bei 1280 × 1024 Pixel.
Die Werte der komplexesten Karte cpm13 unterscheiden sich nur geringfügig von denen anderer Karten. Trotz der vielen Polygone und Vertices ist
KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN
Tabelle 5.8: Die Ergebnisse der Messungen des Levels cpm4a in Frames pro
Sekunde (Quake III).
640
1280
Test 1
760,49
232,30
Test 2
759,51
231,98
Test 3
776,47
229,18
Test 4
741,73
232,11
Test 5
750,90
226,84
Schnitt
757,82
230,48
Tabelle 5.9: Die Ergebnisse der Messungen des Levels cpm19 in Frames pro
Sekunde (Quake III).
640
1280
Test 1
493,16
220,01
Test 2
536,78
227,55
Test 3
551,49
224,09
Test 4
495,26
224,98
Test 5
548,09
227,32
Schnitt
524,96
224,79
Tabelle 5.10: Die Ergebnisse der Messungen des Levels cpm13 in Frames
pro Sekunde (Quake III).
640
1280
Test 1
673,43
233,36
Test 2
787,54
227,66
Test 3
712,64
228,68
Test 4
679,90
225,61
Test 5
737,81
229,90
Schnitt
718,26
229,04
67
KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN
68
kein Leistungseinbruch zu erkennen. Verantwortlich ist wiederum das Design der Karte. Viele einzelne durch Gänge verbundene Räume begrenzen
die Anzahl der sichtbaren Polygone. Tabelle 5.10 liefert die gemessenen Zahlen.
5.4
Vergleich und Fazit
Vergleicht man zunächst nur die von JSceneViewer3D generierten Daten,
so fällt auf, dass durch die Kompilierung des Szenengraphen keinerlei Geschwindigkeitsvorteile entstehen. Es tritt sogar eher das Gegenteil ein: In
sechs von zehn Fällen erweist sich die nicht kompilierte Version als performanter. Dies lässt mehrere Interpretationsmöglichkeiten zu: Eine Erklärung
dafür ist, dass die Geometriedaten des Quake III Levels bereits sehr optimiert vorliegen. Es werden nur die Vertices gespeichert, die auch tatsächlich
für die Bildung von Polygonen benötigt werden. Auch bei der Tessellierung
zur Generierung von gekrümmten Flächen werden nur die nötigsten Vertices
erstellt. Möglicherweise ist jedoch auch die Geometrie bereits zu komplex,
um weitreichende Optimierungen vornehmen zu können.
Beim Vergleich der Daten von JSceneViewer3D und Quake III (Abbildung 5.6 und Abbildung 5.7) fällt sofort auf, dass letzteres mit wesentlich
besseren Werten aufwarten kann. In der Regel ist die erzielte Framerate etwa
doppelt so hoch wie die von JSceneViewer3D. Dies gilt sowohl für die niedrige Auflösung mit 640 × 480 Pixel als auch für die hohe mit 1280 × 1024
Pixel. Die Zahlen sprechen somit eindeutig für die in C realisierte QuakeEngine, Java 3D hat in allen Fällen das Nachsehen. Allerdings sollte nicht
nur anhand der vorliegenden Zahlen geurteilt werden. Frameraten jenseits
der 700 Frames pro Sekunde sind zwar beeindruckend, jedoch keinesfalls
notwendig. Im Normalfall begrenzt Quake III die Framerate sogar auf 90
Frames pro Sekunde. Dies lässt eine flüssige Darstellung zu und vermeidet
unnötige Render-Vorgänge. Eine höhere Framerate als die Bildwiederholfrequenz des Monitors ist ebenfalls nur bedingt sinnvoll. Erfolgt die Darstellung
wie in Tabelle 5.7 angeführt etwa mit über 800 Frames pro Sekunde, so liefert die Grafikkarte in der Sekunde 800 Bilder an den Monitor. Wird dieser
mit 60 Hertz betrieben (so wie es beim vorliegenden Testsystem der Fall
war), kann er jedoch nur 60 Bilder in der Sekunde darstellen. Das bedeutet, dass während des Bildaufbaus des Monitors der Bild- oder Pufferinhalt
gewechselt wird. Der Monitor stellt also ab einer gewissen Zeile bereits das
nächste Bild dar und der Benutzer sieht ein Ergebnis, das aus mehreren
verschiedenen Einzelbildern besteht. Für das Auge unangenehme Schlieren
(so genannte Glitches) sind die Folge, da die einzelnen Abschnitte nicht genau zusammenpassen. Abhilfe dagegen schafft die Aktivierung der vertikalen
Synchronisation (des so genannten VSyncs). Dabei sendet die Grafikkarte
nur so viele Bilder, wie der Monitor verarbeiten kann. Dies würde in die-
KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN
69
Abbildung 5.6: Der Vergleich der fünf getesten Levels in der Auflösung
640 × 480 Pixel zwischen Quake III (Q3), JSceneViewer3D mit nicht kompiliertem Szenengraphen (NC) und JSceneViewer3D mit kompiliertem Szenengraphen (C). Die Werte sind in Frames pro Sekunde angegeben.
Abbildung 5.7: Der Vergleich der fünf getesten Level in der Auflösung
1280 × 1024 Pixel zwischen Quake III (Q3), JSceneViewer3D mit nicht
kompiliertem Szenengraphen (NC) und JSceneViewer3D mit kompiliertem
Szenengraphen (C). Die Werte sind in Frames pro Sekunde angegeben.
sem Fall etwa eine Begrenzung der Framerate auf 60 Frames pro Sekunde
bedeuten.
Führt man sich diese Thematik also vor Augen, so sind die bei Quake
III gemessenen Frameraten nur bis zu einem gewissen Punkt sinnvoll. Da
JSceneViewer3D selbst bei der komplexesten Karte in der höchsten Auflösung noch eine Framerate von ca. 110 Frames pro Sekunde erreicht (Tabelle
5.5), kann dies durchaus als ausreichend angesehen werden. Bezieht man weiters den Umstand mit ein, dass JSceneViewer3D aufgrund des – verglichen
zu Quake III – äußerst kurzen Entwicklungszeitraums sicherlich noch nicht
KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN
70
perfekt für die Darstellung von Quake III Welten optimiert ist, so könnten
durchaus noch bessere Ergebnisse erzielt werden.
Aufgrund der in diesem Kapitel ausgewerteten Performance-Daten lässt
sich zusammenfassend sagen, dass die derzeit vorliegende Version von JSceneViewer3D durchaus Leistungsdaten aufweist, die als geeignet für die Darstellung dieser komplexen 3D-Welten angesehen werden können. Der Abstand zu den mit Quake III erzielten Daten ist zwar groß, liegt jedoch in
einem Bereich, in dem die fehlenden Frames pro Sekunde noch keine Auswirkungen auf eine flüssige Darstellung haben. Die Kompilierung des Szenengraphens in Java 3D (Compiled Retained Mode) konnte keine spürbaren
Verbesserungen gegenüber der nicht kompilierten Version (Retained Mode)
erzielen. Eine Optimierung des Programmcodes kann jedoch mit Sicherheit
die Leistung weiter verbessern.
Kapitel 6
Evaluierung und
Schlussbemerkungen
Nach Abschluss der Implementierung der Applikation und der Auswertung
der damit erfolgten Testläufe soll nun versucht werden, eine Antwort auf
die eingangs gestellte Frage: Eignet sich Java zur Spiele-Entwicklung?“ zu
”
geben. Vorweg muss dabei erwähnt werden, dass diese Fragestellung keineswegs mit einem einfachen Ja“ oder Nein“ zu beantworten ist. Zu vielfältig
”
”
sind die unterschiedlichen Faktoren, die auf das Ergebnis einwirken.
6.1
Evaluierung
Die Evaluierung von Java für den Einsatz in der Spiele-Entwicklung soll zunächst aus Gründen der Übersicht in einige wenige große Bereiche eingeteilt
werden: Verfügbarkeit von Software, Produktivität und Performance.
6.1.1
Verfügbarkeit von Software
Ausschlaggebend für die Entscheidung, ob ein Software Projekt (d. h. in
diesem Fall ein Spiel) auf einer bestimmten Plattform (hier Java) umgesetzt wird bzw. werden kann, ist die Verfügbarkeit entsprechender Software
und Softewarekomponenten. In der Spiele-Entwicklung sind dabei die klassischen fünf Hauptkomponenten Grafik, Sound, Netzwerk, Physik und künstliche Intelligenz maßgebend. Miteinbezogen werden müssen jedoch auch die
Verfügbarkeit von Laufzeit-Umgebungen und Entwicklungswerkzeugen.
Die Verfügbarkeit von Grafik-Bibliotheken und so genannter Middleware
(Engines) für Java wurde in Kapitel 3 ausführlich behandelt. Die sieben beschriebenen Produkte zeigen, dass eine rege Aktivität auf diesem Sektor
herrscht und dass dem Entwickler gleichzeitig eine gute Auswahlmöglichkeit geboten wird, wenn es um die Selektion der zu verwendenden GrafikBibliotheken geht.
71
KAPITEL 6. EVALUIERUNG UND SCHLUSSBEMERKUNGEN
72
Bei der Implementierung von Sound (Musik und Geräusche) steht dem
Entwickler seit Java Version 1.3 die Java Sound API zur Verfügung. Mit Hilfe
der Klassen der Pakete javax.sound.midi und javax.sound.sampled kann
eine Vielzahl an Formaten wiedergeben werden. Um komplexere Audiobearbeitungen durchzuführen, steht weiters noch das Java Media Framework1
zur Verfügung. Viele von dieser Komponente gebotenen Funktionalitäten
werden jedoch in einem Spiel wohl nur selten benötigt. Schließlich gibt es
noch Java-Anbindungen für bekannte Sound-Bibliotheken wie etwa FMOD
oder OpenAL. Solche sind zum Beispiel in LWJGL (siehe Abschnitt 3.1.2)
vorhanden.
Im Bereich der Netzwerk-Implementierung für Spiele stehen in Java von
Beginn an zahlreiche Klassen in der Standardbibliothek zur Verfügung. Gosling nennt in [8] die Einfachheit im Umgang mit Netzwerk- Zugriffen einen
großen Vorteil von Java gegenüber C oder C++.
Auch für Physik-Simulationen gibt es mit Odejava2 eine Möglichkeit,
diese in Java zu realisieren. Darüber hinaus stehen jedoch kaum Bibliotheken
zur Verfügung.
Für die Realisierung künstlicher Intelligenz gibt es generell kaum fertige
Lösungen, weshalb eine Implementierung in der Regel selbst vorgenommen
werden muss. Java eignet sich hierbei aufgrund seiner guten Effizienz (siehe
auch Abschnitt 6.1.2) besonders für diese Aufgabe. Einige in Abschnitt 3.2
aufgelisteten Spiele machen von diesem Modell Gebrauch.
Die Verfügbarkeit von Laufzeitumgebungen ist in Java sehr gut. Das Java
Runtime Environment (JRE) ist frei verfügbar und wird regelmäßig aktualisiert. In [2] wird erwähnt, dass die Firma Sun Microsystems davon ausgeht,
dass etwa 50 Prozent aller neu ausgelieferten PCs bereits ein JRE installiert
haben. Eine Übersicht über weitere verfügbare Java Laufzeitumgebungen
bietet [11].
Betrachtet man schließlich noch die verfügbaren Entwicklungsumgebungen für Java, so gibt es auch hier eine Reihe von verfügbarer Software, die bei
der Spiele-Entwicklung eingesetzt werden kann. eclipse3 stellt die zur Zeit
wohl bekannteste und meist verwendete Umgebung dar. Das Programm ist
zudem frei verfügbar. Weitere (kommerzielle) Produkte sind IntelliJIDEA4
und Borland JBuilder5 .
Die Verfügbarkeit von Softwarekomponenten in Java ist also durchaus
gut, wenngleich natürlich Entwicklern in C oder C++ noch wesentlich mehr
Produkte zur Verfügung stehen. Auch die Tatsache, dass viele Java-Implementierungen lediglich Anbindungen an C oder C++ Bibliotheken sind, darf
nicht außer Acht gelassen werden. Anbindungen werden in der Regel erst
1
http://java.sun.com/products/java-media/jmf/
http://odejava.org/
3
http://www.eclipse.org/
4
http://www.jetbrains.com/idea/
5
http://www.borland.com/de/products/jbuilder/
2
KAPITEL 6. EVALUIERUNG UND SCHLUSSBEMERKUNGEN
73
mit einer gewissen Verzögerung auf den technischen Stand ihres Vorbilds“
”
gebracht.
6.1.2
Produktivität
Produktivität ist ein entscheidender Faktor bei der Realisierung von Software-Projekten. Je effizienter Entwickler arbeiten können, desto mehr Zeit
kann in die Umsetzung des Projekts investiert werden, da weniger Zeit für
andere Dinge, wie die Lösung technischer oder sprachspezifischer Probleme,
aufgewandt werden muss. Zahlreiche Artikel wie etwa [12], [22] oder [24] erwähnen die hohe Produktivität, die Entwickler durch die Verwendung von
Java erreichen können. Als Hauptgründe werden vor allem die Plattformunabhängigkeit, einfache Speicherverwaltung, die große, mitgelieferte Standardbibliothek und das einfache, objektorientierte Konzept genannt.
Auch Stabilität ist ein wichtiger Faktor zur Produktionssteigerung. Die
Java-Laufzeitumgebungen enthalten sehr wenige Fehler, weshalb es in der
Regel kaum zu nicht nachvollziehbaren Abstürzen kommt. Fehler, die durch
den Entwickler verursacht wurden, werden durch das Konzept der JavaExceptions (Ausnahmen) gut sichtbar gemacht und sind dadurch leichter
nachzuvollziehen. Kryptische Fehlermeldungen, wie etwa ein Speicherzugriffsfehler an einer bestimmten Adresse, treten in Java nicht auf.
Auch das Vorhandensein einer guten Dokumentation bewirkt eine Steigerung der Effizienz. Die Java-Standardbibliothek ist durchgehend und sehr
ausführlich dokumentiert, die Dokumentation des API ist im Internet abrufbar. Auch für selbst erstellte Applikationen kann auf einfache Art und Weise
aus dem dokumentierten Quellcode eine Dokumentation generiert werden.
Bei der Implementierung von JSceneViewer3D wurden alle soeben genannten Faktoren zur Produktivitätssteigerung beobachtet. Neben diversen
Funktionalitäten der Standardbibliothek wurden mit Java 3D, JIMI und
log4j lediglich drei externe Bibliotheken benötigt. Deren Einbindung in das
Projekt gestaltete sich aufgrund der guten Dokumentation äußerst einfach.
Für die Erstellung der grafischen Benutzeroberfläche etwa war keine externe Bibliothek notwendig. Durch die Java-Speicherverwaltung konnte von
Beginn an eine Applikation ohne grobe Speicherlecks (Memory Leaks) erzeugt werden. Auch Programmabstürze waren zumeist schnell behebbar. Im
Falle der Interaktion des Benutzers mit dem GUI konnten aufgrund des
Ausnahmen-Systems auf einfache Art und Weise Rückmeldungen generiert
werden, die dem Benutzer abseits technischer Details erklärten, welcher Fehler aufgetreten war.
Die Verwendung von Java 3D im (Compiled) Retained Mode erwies sich
als sehr effektiv. Es war lediglich notwendig, den Szenengraphen mit Hilfe des
selbst erstellten Level-Loaders zu erzeugen. Der eigentliche Rendervorgang
musste nicht implementiert werden. Auch Techniken zur Optimierung der
Leistung, wie etwa View-Frustum-Culling oder Sichtbarkeitsberechnungen
KAPITEL 6. EVALUIERUNG UND SCHLUSSBEMERKUNGEN
74
(siehe Abschnitt 2.3.2), brauchten nicht selbst realisiert zu werden, da Java
3D diese Algorithmen bereits bei der Darstellung anwendet.
6.1.3
Performance
Performance von Java-Applikationen ist seit Erfindung dieser Programmiersprache ein heikles und viel diskutiertes Thema. Der generelle Konsens lautet, dass Java-Programme in Punkto Performance C oder C++ Programmen
unterlegen sind. Jedoch gibt es auch Artikel, die durch Tests belegen, dass es
kaum einen Unterschied gibt bzw. Java sogar schneller sein kann. Beispiele
hierfür sind [13] oder [14].
Die in Kapitel 5 gelisteten Ergebnisse zeigen klar und deutlich, dass die
vorliegende Implementierung, an den Leistungsdaten gemessen, nicht mit
der Original-Applikation mithalten kann. Dies ist zum Teil auf Java 3D
zurückzuführen, welches im Compiled Retained Mode zwar selbst Optimierungen vornimmt, den Benutzer aber mit Ausnahme der konfigurierbaren
Parameter keinerlei Veränderungen oder Verbesserungen an der Darstellung
vornehmen lässt. Nicht unwesentlich ist jedoch auch die Tatsache, dass der
vorliegende Code nicht explizit auf Performance ausgelegt (d. h. getweaked)
wurde. So sind sicherlich an einigen Stellen noch Verbesserungen möglich,
die aus zeitlichen Gründen aber ausbleiben mussten. Dennoch bewegen sich
die erreichten Frameraten in einem Bereich (über 100 Frames pro Sekunde),
der als durchaus gut angesehen werden kann.
6.2
Fazit
Basierend auf den soeben vorgestellten drei Faktoren spricht vor allem die
hohe Produktivität für eine Entwicklung eines Spiels in Java. Die Verfügbarkeit von Softwarekomponenten und die erreichbare Performance sind differenziert zu sehen. Vor allem für kleinere Applikationen, wie etwa die vorliegende Version von JSceneViewer3D, reichen die vorgestellten und verwendeten Bibliotheken aus. Es lässt sich damit auch eine akzeptable Performance
bei der Umsetzung erreichen, ohne viel Code Tweaking durchführen zu müssen. Für umfangreichere Applikationen (wie etwa ein voll implementiertes
Spiel) kann es unter Umständen zu Performance-Engpässen kommen, vor
allem wenn keine Code-Optimierungen durchgeführt wurden. In diesem Fall
wird auch die Einbindung von nativem C/C++ Code nicht immer vermieden werden können. Dies bedeutet ein Arbeiten auf niedrigerem Level und
verringert wiederum die Produktivität, da die Grundkonzepte von Java hier
nicht mehr zur Anwendung kommen.
Zusammenfassend lässt sich sagen, dass die Entwicklung eines Spiels in
Java mit den in dieser Arbeit vorgestellten Mitteln durchaus möglich und
auch sinnvoll ist. Eine genaue Spezifikation der Anforderungen an die Per-
KAPITEL 6. EVALUIERUNG UND SCHLUSSBEMERKUNGEN
75
formance sollte im Vorfeld jedoch nicht fehlen, da jene ab einer gewissen
Komplexität des Spieles sicherlich noch ihre Grenzen hat.
6.3
Ausblick
Die vorliegende Version von JSceneViewer3D bietet ein Gründgerüst, um
komplexe 3D-Szenen unter Verwendung von Java und Java 3D darzustellen.
Es ist möglich, Quake III -Levels zu laden und anzuzeigen. Dabei wird die
Geometrie vollständig geladen, Texturen werden angezeigt und Entitäten
zum Teil verarbeitet. Um alle Details der Welt darstellen zu können, müssen in weiterer Folge die verbleibenden, bis jetzt nicht ausgelesenen Lumps
ausgewertet werden. Dies betrifft vor allem Planes, Models, Brushes, Effects
und Lightmaps. Jene Lumps, welche die BSP-Baum Struktur repräsentieren
(Nodes, Leafs, Leaffaces, Leafbrushes) müssen nicht beachtet werden.
Um die Ergebnisse der bestehenden Applikation weiter zu verbessern,
kann versucht werden, wichtige Codeteile zu optimieren. So werden etwa zur
Zeit Polygone und Meshes noch mit Triangle-Arrays gerendert und BackfaceCulling wurde deaktiviert, um das Level von beiden Seiten sichtbar zu machen.
Eine sinnvolle Erweiterungsmöglichkeit besteht darin, neben Java 3D
noch JSR 231/JOGL zur Darstellung einzusetzen. In diesem Fall müssen der
Level-Loader angepasst und ein eigener Renderer entworfen werden. Letzterer kann dann gegebenfalls auch den BSP-Baum auswerten und ihn zur
Sichtbarkeitsbestimmung verwenden. Auch andere Sichtbarkeitsalgorithmen
(Culling) müssen ebenfalls implementiert werden. Mit einem zweiten, direkt
auf OpenGL basierenden Renderer können in Folge akkuratere Vergleichstest durchgeführt werden.
Weiters bietet sich die Implementierung von Level-Loadern für andere
Formate an. Mögliche Spiele, deren Levels geeignet sein könnten, wären Unreal Tournament oder Half-Life.
Folgeprojekte dieser Arbeit können auf den hier gewonnenen Erkenntnissen aufbauen, um etwa die Leistungsfähigkeit zukünftiger Java Versionen,
wie etwa der Java 2 Standard Edition Version 6.0 (Codename Mustang“)
”
zu überprüfen. Auch die weiteren Entwicklungen von Java 3D können dabei
miteinbezogen werden. Version 1.5 ist zur Zeit als Beta-Version verfügbar
und wird in absehbarer Zeit zuverlässig einsetzbar sein.
Die Verwendung von Java in der Spiele-Entwicklung steht zum gegebenen
Zeitpunkt noch am Anfang. Zukünftige Entwicklungen werden zeigen, ob
diese Konstellation Erfolg haben kann, oder nicht.
Anhang A
Quake III Level Format
Alle in der Folge dargestellten Informationen über das Quake III -LevelFormat sind aus [17] entnommen. Sie wurden für die Auflistung in dieser
Arbeit ins Deutsche übersetzt und gegebenfalls durch eigene Ausführungen
ergänzt. Die im Quake III -Format verwendeten Bezeichnungen werden im
englischen Original verwendet und falls möglich, übersetzt.
A.1
Dateistruktur
Die Dateistruktur der Quake III BSP-Datei ist ähnlich anderer in der Regel
ältererer BSP-Formate (etwa von Quake oder Quake II ). Am Beginn steht
ein Header. Dieser enthält Informationen über die verwendete BSP-Version
und ein Verzeichnis aller 17 so genannten Lumps. Jeder einzelne Lump enthält einen bestimmten Teil der Level-Informationen. Abbildung A.1 stellt
diese Struktur schematisch dar.
Abbildung A.1: Der Aufbau einer BSP-Datei. Auf den Header folgen die
17 Lumps in vorgegebener Reihenfolge.
76
ANHANG A. QUAKE III LEVEL FORMAT
A.2
77
Datentypen
In einer Quake III BSP-Datei kommen vier verschiedene Datentypen zum
Einsatz:
ubyte: Vorzeichenloses Byte (unsigned byte). Zumeist für die Speicherung von Farbwerten verwendet.
int: 4 Byte große Ganzzahl (integer), little-Endian.
float: 4 Byte große Fließkommazahl (float), little-Endian.
string[n]: Zeichenkette (string) bestehend aus n ASCII Bytes, nicht
zwingend null-terminiert.
Alle Daten in einer BSP-Datei sind in Blöcken zusammengefasst, die
immer aus diesen vier Datentypen bestehen. Blöcke sind in weiterer Folge
fett dargestellt, Datentypen werden dicktengleich gekennzeichnet und die
Namen von Datentypen oder Blöcken sind kursiv abgebildet. Die Inhalte der
Blöcke werden wiederum in eigenen Unterabschnitten erläutert. Die Anzahl
der Blöcke in jedem Lump kann durch die Division der Größe eines Blocks
in Byte mit der Gesamtlänge des Lumps in Bytes errechnet werden.
A.3
Header und Verzeichniseinträge
Der Header einer Quake III BSP-Datei steht immer am Anfang und hat
immer die gleiche Länge. Er besteht aus folgenden Teilen:
header
string[4] magic: Die so genannte magische Nummer (magic number).
Enthält immer den Wert IBSP“.
”
int version: Die Versionsnummer
der BSP-Datei. Im Falle von Quake
III ist der Wert 0x2e“ (46).
”
direntry[17] direntries:
Das Verzeichnis mit Lump-Einträgen. Es enthält 17 Elemente.
direntry
Anhand des Verzeichniseintrages kann die Position und Länge eines jeden
Lumps in der BSP-Datei genau ermittelt werden.
int offset: Der Offset dieses Lumps in Bytes zum Anfang der BSPDatei.
int length: Die Länge des Lumps in Bytes. Sie beträgt immer ein
Vielfaches von vier.
ANHANG A. QUAKE III LEVEL FORMAT
A.4
78
Lumps
Jede Quake III BSP-Datei besteht aus genau 17 so genannten Lumps. Sie
sind im Lump-Verzeichnis des Headers in der durch die folgenden Abschnitte
dargestellten Reihenfolge referenziert.
A.4.1
Entities (Entitäten)
Der Entitäten Lump beinhaltet Informationen über das Level wie den Namen oder auch die zu verwendende ambiente Beleuchtung. Darüber hinaus
spezifiziert der Lump Objekte, die in der Karte platziert werden. Solche
Objekte sind etwa Waffen, Rüstungen oder Health Packs (Gegenstände zur
Verbesserung der Gesundheit). Genauso jedoch werden die Startpunkte des
Spielers angegeben, Lichter platziert und zu ladende Modelle beschrieben.
Der Lump enthält genau einen Block, der die Entitäten textuell beschreibt.
entities
string[l] entities: Die Beschreibung aller Entitäten als Zeichenkette
der Länge l.
l ist durch die Länge des Lumps festgelegt, die im Lump-Verzeichnis
angeführt ist. Details zu den einzelnen Entitäten und deren Parametern
können [10] entnommen werden.
A.4.2
Textures (Texturen)
Dieser Lump enthält die Namen der Texturen, die auf Oberflächen angebracht werden. Die Texturen werden über eindeutige Indices bei Faces, Brushes und Brushsides referenziert.
texture
string[64] name: Der Name der Textur (ohne Dateierweiterung).
int flags: Parameter (flags) für die verwendete Oberfläche.
int contents: Parameter (flags) des Inhalts der Textur.
A.4.3
Planes (Ebenen)
Dieser Lump enthält eine bestimmte Anzahl von Planes, die von Nodes
und Brushsides referenziert werden. Um beidseitig sichtbar zu sein, werden
immer zwei Ebenen mit jeweils invertiertem Normalvektor benötigt. Dies ist
der Fall bei den Planes mit den Ebenen i und i ∧ 1 (XOR-Verbindung mit
1).
ANHANG A. QUAKE III LEVEL FORMAT
79
plane
float[3] normal: Der Normalvektor der Ebene.
float dist: Die Distanz der Ebene zum Ursprung, gemessen entlang
des Normalvektors.
A.4.4
Nodes (Knoten)
Der Knoten-Lump speichert alle Knoten des verwendeten BSP-Baums. Jener wird verwendet, um das Level räumlich in konvexe Regionen aufzuteilen.
Diese werden als Leafs bezeichnet. Der erste Knoten im Lump ist der Wurzelknoten (Root-Node).
node
int plane: Der Index der zugewiesenen Ebene.
int[2] children: Die Indices der Kinder dieses Knotens. Ist der Wert
negativ, handelt es sich um den Index eines Leafs: -(leaf + 1).
int[3] mins: Die minimalen Koordinaten der Bounding-Box.
int[3] maxs: Die maximalen Koordinaten der Bounding-Box.
A.4.5
Leafs (Blätter)
Dieser Lump enthält alle Blätter des BSP-Baums. Diese sind die Endstücke,
die sich nicht mehr weiter verzweigen und repräsentieren eine konvexe Region des Raums. Jedes Leaf enthält einen so genannten Cluster-Index, der
die Sichtbarkeit anderer Leafs von diesem aus gesehen bestimmt. Weiters
eine Liste von darzustellenden Faces und eine Liste von Brushes, die für
die Kollisionserkennung herangezogen werden. Ist der Cluster-Index negativ, befindet sich das Leaf außerhalb des Levels oder ist ungültig.
leaf
int cluster : Der Cluster Index der Sichtbarkeitsdaten.
int area: Der Index eines Portals, welches für die Sichtbarkeitsberechnung bei Türen benötigt wird.
int[3] mins: Die minimalen Koordinaten der Bounding-Box.
int[3] maxs: Die maximalen Koordinaten der Bounding-Box.
int leafface: Das erste Leafface dieses Leafs.
int n leaffaces: Die Anzahl der Leaffaces dieses Leafs.
int leafbrush: Der Erste Leafbrush dieses Leafs.
int n leafbrushes: Die Anzahl der Leafbrushes dieses Leafs.
ANHANG A. QUAKE III LEVEL FORMAT
A.4.6
80
Leaffaces
Dieser Lump speichert eine liste von Face-Indices, wobei für jedes Leaf eine
eigene Liste (repräsentiert durch einen leafface-Block) vorhanden ist.
leafface
int face: Der Index des zugehörigen Faces.
A.4.7
Leafbrushes
Dieser Lump speichert eine Liste von Brush-Indices, wobei für jeden Brush
eine eigene Liste (repräsentiert durch einen leafbrush-Block) vorhanden ist.
leafbrush
int brush: Der Index des zugehörigen Brushs.
A.4.8
Models (Modelle)
Der Models-Lump beschreibt rigide Teile der Level-Geometrie. Das erste Modell repräsentiert dabei den Unterbau des Levels, die restlichen beschreiben
bewegliche Teile wie Türen, Plattformen und Knöpfe. Jedes Modell besteht
aus einer Liste von Faces und Brushes, welche vor allem für die beweglichen Teile von Bedeutung sind, die ja – anders als der Unterbau – keinen
BSP-Baum besitzen.
model
float[3] mins: Die minimalen Koordinaten der Bounding-Box.
float[3] maxs: Die maximalen Koordinaten der Bounding-Box.
int face: Das erste Face dieses Modells.
int n faces: Die Anzahl der Faces dieses Modells.
int brush: Der erste Brush dieses Modells.
int n brushes: Die Anzahl der Brushes dieses Modells.
A.4.9
Brushes
Im Brush-Lump wird eine Liste von Brushes gespeichert, welche für die
Kollisionserkennung benötigt werden. Als Brush wird ein konvexer Polyeder
bezeichnet.
ANHANG A. QUAKE III LEVEL FORMAT
81
brush
int brushside: Die erste Brushside dieses Brushs.
int n brushsides: Die Anzahl an Brushsides in diesem Brush.
int texture: Der Index der verwendeten Textur.
A.4.10
Brushsides
Als Brushside wird eine Seitenfläche der Bounding-Box bezeichnet, die einen
Brush umgibt. Der Brushside-Lump enthält die Beschreibungen dieser Flächen.
brushside
int plane: Der Index der verwendeten Fläche.
int texture: Der Index der verwendeten Textur.
A.4.11
Vertexes (Vertices)
Der Vertex-Lump enthält Vertexlisten, die zur Beschreibung von Faces verwendet werden.
vertex
float[3] position: Die Koordinaten (Position) des Verticis.
float[2][2] texcoord: Die Texturkoordinaten des Verticis. Die Koor-
dinaten an Stelle 0 des Arrays sind die Oberflächen-Texturkoordinaten,
an Stelle 1 befinden sich die Lightmap-Daten.
float[3] normal: Der Normalvektor des Verticis.
ubyte[4] color : Die Farbe des Verticis, gespeichert im Format RGBA
(rot, grün, blau, alpha).
A.4.12
Meshverts
Der Meshvert-Lump enthält eine Liste von Offsets, die benötigt werden, um
Dreiecke zu beschreiben. Eine andere Bezeichnung lautet Vertex-Indices.
meshvert
int offset: Der Vertex-Index Offset, relativ zum ersten Vertex im dazugehörigen Face.
ANHANG A. QUAKE III LEVEL FORMAT
A.4.13
82
Effects (Effekte)
Der Effekt-Lump enthält Referenzen zu volumetrischen Shadern wie etwa
Nebel. Effekte können immer auf eine Gruppe von Faces angewandt werden.
effect
string[64] name: Der Name des verwendeten Shaders.
int brush: Der Index des Brushs, von dem dieser Effekt generiert
wurde.
int unknown: Zusätzlicher Parameter, der bis auf wenige Ausnahmen
immer den Wert 5 enthält.
A.4.14
Faces
Der Face-Lump enthält alle Informationen, um die Geometrie des Levels
darzustellen.
In Quake III kommen vier verschiedene Typen von Faces zum Einsatz:
Polygone, Bézier Patches, Meshes und Billboards.
Handelt es sich bei dem Face um ein Polygon, ist durch vertex und
n vertexes die Liste an Vertices festgelegt, die dieses Polygon bilden. Diese
Liste ist immer vollständig und enthält in bestimmten Fällen sogar noch
einen zusätzlichen Vertex im Zentrum des Polygons. Die Werte meshvert und
n meshverts beinhalten die Indizes, die benötigt werden, um das Dreieck in
der richtigen Reihenfolge zu erstellen.
Ist das aktuelle Face ein Patch, so beinhalten vertex und n vertexes eine
Liste von so genannten Kontroll-Vertices. Die Anzahl und Ausdehnung wird
durch size vorgegeben. Informationen über die Verarbeitung und Darstellung
dieser Patches sind in [1] und [16] vorhanden.
Meshes können äquivalent zu Polygonen repräsentiert werden.
Für Billboards beschreibt vertex die Position des Billboards. Verwendung
finden diese vor allem bei Lichtreflex-Effekten (Flare).
face
int texture: Der Index der Textur, die für dieses Face verwendet werden soll.
int effects: Ein Index auf den Effekt-Lump, falls für dieses Face ein
Effekt zum Einsatz kommt. Andernfalls ist der Index -1.
int type: Spezifiziert, ob es sich bei dem Face um ein Mesh, ein Polygon, einen Bézier-Patch oder ein Billboard handelt.
int vertex : Der Index des ersten Verticis im Face.
int n vertexes: Die Anzahl der Vertices im Face.
ANHANG A. QUAKE III LEVEL FORMAT
83
int meshvert: Der Index des ersten Mesh-Verticis im Face.
int n mehsverts: Die Anzahl der Mesh-Vertices (Indizes) im Face.
int lm index : Der Index der Lightmap.
int[2] lm start: Spezifiziert die linke obere Ecke der Lightmap für
dieses Face in der gesamten Lightmap-Textur.
int[2] lm size: Beziffert die Ausdehnung der Lightmap-Textur für
dieses Face innerhalb der gesamten Lightmap.
float[3] lm origin: Die Weltkoordinaten der Lightmap.
float[2][3] lm vecs: Die Textur-Vektoren für diese Lightmap.
float[3] normal: Die Oberflächen-Normale für dieses Face.
int[2] size: Die Größe eines Bézier-Patches (falls es sich bei dem Face
um einen solchen handelt).
Die mit lm beginnenden Werte enthalten Informationen zur Darstellung
von Lightmaps. Besitzt ein Face eine Lightmap, ist lm index positiv. Dieser
Index verweist auf den Lightmap-Lump, der eine große Lightmap enthält.
Über lm start und lm size kann das benötigte Sub-Bild eingegrenzt werden.
Bei Polygonen können lm origin und lm vecs verwendet werden, um die
Position des Polygons im Weltkoordinatensystem zu berechnen. Diese Position kann in weiterer Folge verwendet werden, um dynamische Beleuchtung
für dieses Face zu berechnen.
Es ist zu beachten, dass keine der mit lm beginnenden Werte verwendet
werden, um Textur-Koordinaten der Lightmaps zu berechnen. Diese sind
vielmehr bereits mit den Vertices mitgespeichert und ersparen somit Berechnungszeiten.
A.4.15
Lightmaps
Dieser Lump speichert die Lightmap-Texturen, die angewandt werden können, um Oberflächen realistischer zu beleuchten. Jede Lightmap ist 128 ×
128 Pixel groß und besteht aus RGB Farbwerten.
lightmap
ubyte[128][128][3] map: Die RGB Farbwerte der Lightmap.
A.4.16
Lightvols (Beleuchtungsdaten)
Der Lightvols-Lump enthält ein einheitliches Gitternetz mit Beleuchtungsinformationen um Objekte zu beleuchten, die nicht in der Karte enthalten
sind.
ANHANG A. QUAKE III LEVEL FORMAT
84
lightvol
ubyte[3] ambient: Der ambiente Anteil der Beleuchtung.
ubyte[3] directional: Der gerichtete Anteil der Beleuchtung.
ubyte[2] dir : Die Richtung des Lichts, gegeben in Kugelkoordinaten.
Der Wert an der Stelle 0 ist der Polarwinkel φ, der Wert an Stelle 1
der Azimutwinkel θ.
A.4.17
Visdata (Sichtbarkeitsinformationen)
Im Visdata-Lump sind Vektoren enthalten, die eine Anzahl so genannter
Cluster-zu-Cluster Sichtbarkeitsinformationen beinhalten. Jede BSP-Datei
enthält genau einen visdata-Block.
visdata
int n vecs: Die Anzahl der verfügbaren Vektoren.
int sz vecs: Die Größe jedes Vektors in Bytes.
ubyte[n_vecs ∗ sz_vecs] vecs: Die Sichtbarkeitsdaten. Dabei wird
ein Bit pro Cluster pro Vektor zugeordnet.
Anhang B
Inhalt der DVD
File System: ISO9660 + Joliet
Mode: Single-Session DVD
B.1
Diplomarbeit
Pfad:
/
da dm04007.dvi . . . .
da dm04007.pdf . . . .
da dm04007.ps . . . . .
B.2
Literatur
Pfad:
/literatur/
essential facts.pdf . . .
java myths.pdf . . . . .
java productivity.pdf . .
java technology.pdf . .
java vms.pdf . . . . . .
java vs c++.pdf . . . .
performance tests.pdf .
q3radiant manual.pdf .
q3 specs.pdf . . . . . .
rendering patches.pdf .
rendering q3 maps.pdf
the java language.pdf .
Diplomarbeit (DVI-Datei)
Diplomarbeit (PDF-Datei)
Diplomarbeit (PostScript-Datei)
Dokument
Dokument
Dokument
Dokument
Dokument
Dokument
Dokument
Dokument
Dokument
Dokument
Dokument
Dokument
85
zu
zu
zu
zu
zu
zu
zu
zu
zu
zu
zu
zu
[3]
[21]
[24]
[12]
[11]
[13]
[14]
[10]
[17]
[1]
[16]
[8]
ANHANG B. INHALT DER DVD
86
B.3
Performance-Daten
Pfad:
/daten/
logs/ . . . . . . . . . . .
videos/ . . . . . . . . .
B.3.1
Pfad:
Log-Dateien der Testläufe
Vidoes der Testläufe
Log-Dateien
/daten/logs/
cpm4a 640 cp/ . .
cpm4a 640 ncp/ .
cpm4a 1280 cp/ .
cpm4a 1280 ncp/
cpm13 640 cp/ . .
cpm13 640 ncp/ .
cpm13 1280 cp/ .
cpm13 1280 ncp/
cpm19 640 cp/ . .
cpm19 640 ncp/ .
cpm19 1280 cp/ .
cpm19 1280 ncp/
cpm24 640 cp/ . .
cpm24 640 ncp/ .
cpm24 1280 cp/ .
cpm24 1280 ncp/
cpm25 640 cp/ . .
cpm25 640 ncp/ .
cpm25 1280 cp/ .
cpm25 1280 ncp/
q3 cpm4a 640/ . .
q3 cpm4a 1280/ .
q3 cpm13 640/ . .
q3 cpm13 1280/ .
q3 cpm19 640/ . .
q3 cpm19 1280/ .
q3 cpm24 640/ . .
q3 cpm24 1280/ .
q3 cpm25 640/ . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
Karte
cpm4a,
cpm4a,
cpm4a,
cpm4a,
cpm13,
cpm13,
cpm13,
cpm13,
cpm19,
cpm19,
cpm19,
cpm19,
cpm24,
cpm24,
cpm24,
cpm24,
cpm25,
cpm25,
cpm25,
cpm25,
cpm4a,
cpm4a,
cpm13,
cpm13,
cpm19,
cpm19,
cpm24,
cpm24,
cpm25,
640 × 480, kompiliert
640 × 480, nicht kompiliert
1280 × 1024, kompiliert
1280 × 1024, nicht kompiliert
640 × 480, kompiliert
640 × 480, nicht kompiliert
1280 × 1024, kompiliert
1280 × 1024, nicht kompiliert
640 × 480, kompiliert
640 × 480, nicht kompiliert
1280 × 1024, kompiliert
1280 × 1024, nicht kompiliert
640 × 480, kompiliert
640 × 480, nicht kompiliert
1280 × 1024, kompiliert
1280 × 1024, nicht kompiliert
640 × 480, kompiliert
640 × 480, nicht kompiliert
1280 × 1024, kompiliert
1280 × 1024, nicht kompiliert
640 × 480, Quake III
1280 × 1024, Quake III
640 × 480, Quake III
1280 × 1024, Quake III
640 × 480, Quake III
1280 × 1024, Quake III
640 × 480, Quake III
1280 × 1024, Quake III
640 × 480, Quake III
ANHANG B. INHALT DER DVD
q3 cpm25 1280/ . . . .
B.3.2
Pfad:
Karte cpm25, 1280 × 1024, Quake III
Videos
/daten/videos/
jsv3d/ . . . . . . . . . .
quake3/ . . . . . . . . .
B.4
Quellcode
Pfad:
/quellcode/
bin/ . . . . . . . . .
dep/ . . . . . . . . .
doc/ . . . . . . . . .
lib/ . . . . . . . . .
log/ . . . . . . . . .
src/ . . . . . . . . .
.classpath . . . . . .
.project . . . . . . .
JSceneViewer3D.bat
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Videos der JSceneViewer3D Testläufe
Videos der Quake III Testläufe
Klassen-Dateien
Benötigte Komponenten zur Ausführung
JavaDoc Dokumentation
Externe Bibliotheken
Order für Log-Dateien
Quellcode-Dateien
eclipse Klassenpfad-Datei
eclipse Projekt-Datei
Batch-Datei zur Ausführung
87
Literaturverzeichnis
[1] Bentley, C.: Rendering Cubic Bezier Patches. URL, http://web.cs.
wpi.edu/˜matt/courses/cs563/talks/surface/bez surf.html, 1995. Kopie
auf CD-ROM.
[2] Davison, A.: Killer Game Programming in Java. O’Reilly Media,
Sebastopol, Erste Aufl., 2005.
[3] Entertainment Software Association: Essential Facts about the
Computer and Video Game Industry. URL, http://theesa.com/files/
2005EssentialFacts.pdf, 2005. Kopie auf CD-ROM.
[4] Fuchs, H., Z. M. Kedem und B. Naylor: Predetermining visibility priority in 3-D scenes (Preliminary Report). In: SIGGRAPH ’79:
Proceedings of the 6th annual conference on Computer graphics and
interactive techniques, S. 175–181, New York, NY, USA, 1979. ACM
Press.
[5] Fuchs, H., Z. M. Kedem und B. F. Naylor: On visible surface generation by a priori tree structures. In: SIGGRAPH ’80: Proceedings of
the 7th annual conference on Computer graphics and interactive techniques, S. 124–133, New York, NY, USA, 1980. ACM Press.
[6] Gamma, E., R. Helm, R. Johnson und J. Vlissides: Entwurfsmuster . Addison Wesley Verlag, München, Erste Aufl., 2004.
[7] Gülcü, C.: The Complete log4j Manual. COS.ch, 2003.
[8] Gosling, J.: The Java Language: An Overview . URL, http://java.sun.
com/docs/overviews/java/java-overview-1.html, 1996. Kopie auf CDROM.
[9] Huizinga, J.: Homo Ludens. Vom Ursprung der Kultur im Spiel. Rowohlt, 19. Aufl., Jänner 1994.
[10] Jaquays, P.: Q3Radiant Editor Manual. URL, http://www.qeradiant.
com/manual/Q3Rad Manual/index.htm, 2000. Kopie auf CD-ROM.
88
LITERATURVERZEICHNIS
89
[11] java-virtual-machine.net: Java virtual machines, Java development
kits and Java runtime environments. URL, http://java-virtual-machine.
net/other.html, 2006. Kopie auf CD-ROM.
[12] Levin, R.: Java Technology Comes of Age. URL, http://java.sun.com/
features/1999/05/birthday.html, Mai 1999. Kopie auf CD-ROM.
[13] Lewis, J. P. und U. Neumann: Performance of Java versus
C++. URL, http://www.idiom.com/˜zilla/Computer/javaCbenchmark.
html, Januar 2003. Kopie auf CD-ROM.
[14] Mangione, C.: Performance tests show Java as fast as C++. URL,
http://www.javaworld.com/jw-02-1998/jw-02-jperf.html, 1998. Kopie
auf CD-ROM.
[15] Marner, J.: Evaluating Java for Game Development. Techn. Ber.
01-11-11, Department of Computer Science, University of Copenhagen,
Denmark, Kopenhagen, Dänemark, März 2002.
[16] McGuire, M.: Rendering Quake 3 Maps. URL, http://graphics.cs.
brown.edu/games/quake/quake3.html, Juli 2003. Kopie auf CD-ROM.
[17] Proudfood, K.: Unofficial Quake 3 Map Specs. URL, http://graphics.
stanford.edu/˜kekoa/q3/, Januar 2000. Kopie auf CD-ROM.
[18] Rollings, A. und E. Adams: Andrew Rollings and Ernest Adams on
Game Design. New Riders, Erste Aufl., 2003.
[19] Rollings, A. und D. Morris: Game Architecture and Design. The
Coriolis Group, Scottsdale, Erste Aufl., 2000.
[20] Scherfgen, D.: 3D-Spieleprogrammierung - Modernes Game Design
mit DirectX 9 und C++. Carl Hanser Verlag, Wien, Erste Aufl., 2003.
[21] Twilleager, D.: Java Game Development Myths. URL, http://
weblogs.java.net/blog/yensid/archive/2005/02/java game devel.html, Februar 2005. Kopie auf CD-ROM.
[22] Tyma, P.: Why are we using Java again? . Commun. ACM, 41(6):38–
42, 1998.
[23] Ullenboom, C.: Java ist auch eine Insel. Galileo Press GmbH, Bonn,
Vierte Aufl., 2005.
[24] Wells, R.: Java offers Increased Productivity. URL, http://www.
wellscs.com/robert/java/productivity.htm, Mai 1999. Kopie auf CDROM.
[25] Zerbst, S., O. Düvel und E. Anderson: 3D-Spieleprogrammierung.
Markt+Technik Verlag, München, Erste Aufl., 2004.
Messbox zur Druckkontrolle
— Druckgröße kontrollieren! —
Breite = 100 mm
Höhe = 50 mm
— Diese Seite nach dem Druck entfernen! —
90