Installiere die Dokumente-Online App

<
>
Download
Dokumenttyp

Fachbereichsarbeit
Informatik

Universität, Schule

Köln, Realschule

Note, Lehrer, Jahr

3, Prof. lang, 2013

Autor / Copyright
Oliver G. ©
Metadaten
Preis 5.50
Format: pdf
Größe: 0.07 Mb
Ohne Kopierschutz
Bewertung
sternsternsternsternstern_0.75
ID# 45154







GESCHRIEBEN VON:

Spieleprogrammierung in java


Vorwort

“Warum Spieleprogrammierung?” Diese Frage ist einfach zu beantworten, wenn man selbst gerne

spielt und vielleicht auch schon ab und zu davon geträumt hat ein “eigenes” Spiel zu entwickeln: es

macht einfach großen Spaß! Aber wenn man sich etwas intensiver mit dem Thema auseinandersetzt,

dann wird man recht bald feststellen, daß sich zum Spaß auch schnell die Arbeit gesellt, denn Spiele

sind, auch wenn sie auf den ersten Blick oft einfach erscheinen, alles andere als einfach!

Genaugenommen sind Spiele kleine Simulationen einer realen Welt und verhalten sich als solche

nach einem klar definierten und abgegrenzten Logik- und Regelwerk.

Die Spieleprogrammierung ist daher fachlich betrachtet nicht uninteressant! Sie vereint in sich nicht

nur verschiedene Disziplinen wie Audio, Grafik, Animation und Logik sondern stellt den

Programmierer auch im Bereich Performance und Timing vor einige Herausforderungen. Soll ein

Spiel dann auch noch netzwerkfähig und über das Internet in Echtzeit spielbar sein, muß man sich

mit dem Thema Synchronisation außeinandersetzen und dabei natürlich auf die Unzuverlässigkeit

des Internets Rücksicht nehmen. Dieses ist nämlich, aufgrund ständiger unvorhersehbarer

Verzögerungen im Datentransfer, für eine flüssige Synchronisation nicht gerade optimal!

2. Grundlagen der Spieleprogrammierung in Java

“Kann man überhaupt Spiele in Java programmieren?” Das ist die wichtigste Frage, die man klären

muß, bevor man mit der Entwicklung eines Spiels in Java beginnt! Die Antwort dazu ist

“grundsätzlich ja, aber natürlich nicht alles”.

Java ist mittlerweile technisch weit fortgeschritten und obwohl es in manchen Bereichen noch etwas

hinter der Performance von C/C++ oder anderen Sprachen zurückliegt, wird es doch langsam mehr

und mehr eine ernste Alternative für die Spieleentwicklung. Durch zahlreiche interne und externe

Bibliotheken und APIs kann man auch unter Java mittlerweile auf Hardwarebeschleunigung durch

OpenGL oder DirectX zurückgreifen und so performance-lastige Funktionen auf die

darunterliegende Hardware auslagern. Es gibt außerdem Bibliotheken, die ein direktes Ansteuern

der Eingabegeräte über DirectInput unterstützen und so ein Event-System-unabhängiges Abfragen

von Eingabegeräten (wie z.B. Joysticks oder Gamepads) ermöglichen.

Es gibt zahlreiche Studien, die bewiesen haben, daß Java in der Ausführung von Programmcode im

Vergleich zu C/C++ langsamer ist. Bei manchen Funktion benötigt Java etwa 130-150% der Zeit

um die gleichen Rechenoperationen durchzuführen, während es bei anderen sogar schneller ist und

nur etwa 90% der Rechenzeit benötigt. Im Durchschnitt kann man dann mit etwa 20-30% mehr

Rechenzeit im Vergleich zu C/C++ rechnen. Diese Zahlen sind jedoch stark abhängig von der

verwendeten JVM Version und wurden mit jeder neuen Version von Java immer etwas besser.

Wenn man diese Entwicklung sieht und auch noch im Hinterkopf behält, daß bisher nur etwa 50%

der Optimierungsmöglichkeiten in den JVMs genutzt wurden, kann man davon ausgehen, daß Java

auch in Zukunft noch stark an Performance gewinnen wird.

Download Spie­le­pro­gram­mie­rung in Java - Grund­la­gen; Aktives Rendern - Kann man Spiele in Java Program­mie­ren?
• Download Link zum vollständigen und leserlichen Text
• Dies ist eine Tauschbörse für Dokumente
• Laden sie ein Dokument hinauf, und sie erhalten dieses kostenlos
• Alternativ können Sie das Dokument auch kаufen

Aber auch wenn Java um vielleicht 30% langsamer als C++ ist, reicht die Leistung immer noch aus,

um interessante Spiele zu erstellen, die auch aktuellen Erwartungen an die Qualität von Grafik und

Sound gerecht werden können. Natürlich können keine Highend-Spiele erstellt werden, die durch

hardwarenahe Programmierung und hardwarespezifische Optimierung jedes bischen Performance

nutzen. Aber dafür ist Java auch nicht gedacht! Der Vorteil bei der Verwendung von Java ist ein

hohes Abstraktionsniveau durch Objektorientierung und die Verwendung funktionsreicher und

“intelligenter” APIs, um so eine erhöhte Produktivität und Wiederverwendbarkeit zu erziehlen.2.1. Java 2D

Java bietet mit Java2D ein mächtiges und flexibles Framework für die Darstellung von geräte- und

auflösungsunabhängiger 2D Grafik. Es ist eine Erweiterung des Abstract Windowing Toolkits

(AWT) und stellt eine Sammlung von Klassen, für die Arbeit mit geometrischen Formen, Text und

Rasterbildern, zur Verfügung. Als Bestandteil der Java Foundation Classes (JFC) ist Java2D

automatisch in jeder aktuellen JVM vorhanden.

Zusammen mit Java Swing bietet Java2D alle Grundlagen für die Darstellung und Verarbeitung von

Grafik. Eine Einführung zu Java Swing und Java2D befindet sich im Anhang.

Als generelle, vielseitige und mächtige API zur Darstellung von Grafik und GUI sind Java Swing

und Java2D natürlich nicht auf Spiele optimiert und deshalb weniger performant als andere

spezialisierte Bibliotheken. Seit der Java Version 1.4 sind jedoch zahlreiche Grafikfunktionen von

Java Swing und Java2D hardwarebeschleunigt und somit gut für die Spieleprogrammierung

einsetzbar. Insbesondere das Zeichnen von Lienen und das Füllen von Rechtecken, sowie das

Zeichnen / Kopieren von Bildern sind optimiert. 1-Bit Transparenz von Bildern (also komplett

durchsichtige Bereiche) ist ebenfalls hardwarebeschleunigt, während teilweise Transparenz rein

softwarebasiert berechnet wird.

In Java Version 1.5 soll die Hardwarebeschleunigung noch weiter ausgebaut werden. Da es aber zu

Beginn unserer Arbeit noch keine (stabile) Version von Java 1.5 gab, können wir darüber auch noch

keine Angaben machen. Für unsere Arbeit haben wir Java 1.4.2_04 verwendet und alle Aussagen

sind daher auf dieser Version von Java basiert.

a) Aktives Rendern

Will man eine Anwendung schreiben, die den Bildschirminhalt häufig verändert und neuzeichnet,

dann wird man früher oder später zwangsläufig an die Grenzen des Event-Handling-Systems stoßen.

In AWT / Swing wird nämlich ein Neuzeichnen von Fenstern und Komponenten über die Methode

den Main-Thread des GUI-Toolkits weiter. Dieser ist jedoch zuständig für das Zeichnen (Rendern)

aller GUI Komponenten und somit ziemlich beschäftigt. Wird nun ein repaint() in häufigen

Intervallen (z.B. 25 mal pro Sekunde) durchgeführt, dann führt das zu einer erhöhten Belastung des

Event-Systems und des Main-Threads. Dies erzeugt nicht nur einen erhöhten Verwaltungsaufwand

für Events und so eine verzögerte Verarbeitung von Benutzereingaben wie z.B. Tastatur- und

Mouse-Events, sondern auch eine insgesamt langsamere Reaktion der gesamten GUI.

Um dies zu vermeiden, sollte das Neuzeichnen in einem eigenständigen Thread unter Umgehung

des Swing Repaint-Modells erfolgen. Hierfür wird die entsprechende Methode paintComponent

(Graphics g) überschrieben und eine zusätzliche Methode renderScreen() erstellt, die dann in

regelmäßigen Abständen vom Render-Thread aufgerufen wird. Diese Methode ist nun für das

Rendern der Komponente zuständig und verwendet hierfür das Graphics-Objekt der Komponente.Der entsprechende Quellcode dazu könnte in ungefähr so aussehen:

import java.awt.*;

import javax.swing.JComponent;

public class MyComponent extends JComponent

{

private Image myImage;

// more code to write here .

public void paintComponent(Graphics g)

{

// do nothing here .

}

public void renderScreen()

{

// get Graphics object from component

Graphics g = getGraphics();

// perform rendering

g.drawImage(myImage,0,0,this);

g.drawLine(0,0,10,20);

// .

}

}

public class Renderer extends Thread

{

private MyComponent myComponent = new MyComponent();

// more code to write here .

public void run()

{

while(true)

{

// render component

myComponent.renderScreen();


// rest a bit and give time to other Threads

try

{

Thread.sleep(20L);

}

catch (InterruptedException ex) {}

}

}

}

Double Buffering

Führt man diesen Code aus, dann wird man feststellen, daß das Rendern zwar funktioniert, aber

nicht ohne ein gelegentliches Flackern des gerenderten Bildes. Das Flackern tritt nämlich dann auf,

Bereich auf die Bildröhre zu projezieren. Diesen Effekt kann man jedoch umgehen, indem man das

zu zeichnende Bild zuerst in einem nicht-sichtbaren Speicherbereich erstellt und dann diesen

Bereich anschließend in den sichtbaren Bereich kopiert. Das Zeichnen selbst nimmt nämlich

deutlich mehr Zeit in Anspruch als das Kopieren das Speicherbereiches!Diese Methode des Renderns nennt man Double Buffering. In Java verwenden wir für das Double

Buffering ein eigens erstelltes BufferedImage, daß die Größe der zu rendernden Oberfläche hat und

im Folgenden als BackBuffer bezeichnet wird. Der BackBuffer wird über die Methode createImage

(int width, int height) der Komponente erstellt und verhält sich ansonsten wie ein normales Image-

Objekt. Das Kopieren des BackBuffers in einen sichtbaren Bildschirmbereich erfolgt daher über die

drawImage( .) Methode des zugehörigen Graphics-Objektes.

Rendert man nun die Komponente über Double Buffering, dann erfolgt das Rendern in 3 Schritten:

1. Erzeuge einen BackBuffer, falls dieser noch nicht vorhanden ist

2. Verwende das Graphics-Objekt des BackBuffers für alle Renderoperationen

3. Zeichne den BackBuffer auf den Bildschirm über das Graphics-Objekt der Komponente

Der entsprechende Quellcode dazu könnte in ungefähr so aussehen:

import java.awt.*;

import javax.swing.JComponent;

{

private Image backBuffer;

private Image myImage;

// more code to write here .

private void createBackBuffer()

{

backBuffer = createImage(getWidth(),getHeight());

}

public void paintComponent(Graphics g)

{

updateScreen();

}

public void renderScreen()

{

// if backBuffer doesn't exist, create one

if (backBuffer == null) createBackBuffer();

// get Graphics object from backBuffer

Graphics g = backBuffer.getGraphics();

// render screen on backBuffer

g.drawImage(myImage,0,0,this);

g.drawLine(0,0,10,20);

// .

}

public void updateScreen()

{

Graphics g = getGraphics();

if (g != null) // component already visible?

{

// is there a backBuffer to draw?

if (backBuffer != null) g.drawImage(backBuffer, 0, 0, null);

else

{

// if not, create one and render on it

createBackBuffer();

renderScreen();

}

}

}

}public class Renderer extends Thread

{

private MyComponent myComponent = new MyComponent();

// more code to write here .

public void run()

{

while(true)

{

myComponent.renderScreen(); // render component

myComponent.updateScreen(); // draw backBuffer to screen


try

{

Thread.sleep(20L);

}

catch (InterruptedException ex) {}

}

}

}

Führt man diesen Code jetzt aus, dann stellt man fest, daß das Flackern nun nicht mehr auftritt.

Interessant ist an dieser Stelle vielleicht noch, daß Java Swing Komponenten schon standardmäßig

einen internes Double Buffering verwenden, um so ein Flackern zu vermeiden. Dieses sollte man,

wenn man Komponenten nach dem oben beschriebenen Verfahren selbst rendert, über die Funktion

setDoubleBuffered(false) deaktiveren, um so einen Performanceverlust durch ein zusätzliches

(unnötiges) Puffern durch die Swing Komponente zu vermeiden!

b) Bilder und Sprites

Programmiert man in Java, dann macht man sich eigentlich zuerst einmal keine Gedanken darüber

wo und wie ein Objekt gespeichert wird, denn diese Arbeit nimmt uns ja glücklicherweise die JVM

ab! Und da Image-Objekte ebenfalls Objekte sind, trifft das auch für sie zu. Aber trotzdem ist

Image-Objekt nicht gleich Image-Objekt und Java hat auch nicht zufälligerweise verschiedene

Bildtypen.

BufferedImage

nämlich komplett von Java verwaltet und ist somit perfekt für den Einstieg in die Programmierung

mit Bildern geeignet.

Was macht aber Java im Hintergrund? Und wie sieht so eine “Verwaltung” eigentlich intern aus?

Nun genaugenommen werden Image-Objekte in der Regel zuerst einmal im Hauptspeicher angelegt

und dann dort durch verschiedene Grafik-Funktionen (wie z.B. Draw, Transform, Filter, etc.)

bearbeitet. Zum Anzeigen des Bildes wird anschließend der Speicherbereich, der die eigentliche

Bild-Information (also die einzelnen Bildpunkte / Pixel) enthält, aus dem Hauptspeicher in den

Videospeicherbereich (VRAM) kopiert und von dort aus dann über die Grafikkarte auf den

Bildschirm projeziert. Das Anlegen der Kopie im Hauptspeicher, sowie das Kopieren der Pixel

(Blitting) aus dem Hauptspeicher in das VRAM führt Java bei einem BufferedImage automatisch

durch. Um jedoch den Kopiervorgang zwischen Hauptspeicher und VRAM zu beschleunigen, legt

Java bei einem BufferedImage, eine zusätzliche Kopie des Bildes in einem hardwarebeschleunigtenBereich an und synchronisiert diese dann bei Bedarf automatisch mit der Kopie aus dem

BufferedImage um ein Bild mit relativ statischem Inhalt handelt.

(Quelle: VolatileImage.pdf)

Sprites

Sprites sind Bilder mit statischem Inhalt, die Charaktere und Objekte eines Spiels darstellen. Da ihr

Inhalt sich nach dem Laden nicht mehr verändert, sollte für sie ein BufferedImage verwendet

werden. Das BufferedImage erstellt nämlich beim ersten Rendern automatisch eine Kopie im

VRAM und verwendet diese anschließend für weitere Render-Durchläufe. Da der Inhalt der Bilder

statisch ist, ist eine permanente Synchronisation zwischen Hauptspeicher und VRAM nicht

notwendig, wodurch man hier eine performante und automatisch-verwaltete Version des Bildes

bekommt.

(Quelle: VolatileImage.pdf)VolatileImage

Wird der Inhalt eines Bildes jedoch häufig verändert, dann muß eine evtl. vorhandene Kopie im

hardwarebeschleunigten Speicherbereich ebenfalls häufig aktualisiert werden. Wie man sich sicher

vorstellen kann, geht in dieser Situation sehr viel Zeit für das Kopieren zwischen Hauptsspeicher

und hardwarebeschleunigtem Speicher verloren und Java kann hier intern auch nicht viel

Verwaltung und Synchronisation verzichtet und das Bild direkt im hardwarebeschleunigten Bereich,

also dem VRAM bei Windows Betriebssystemen, anlegt. Dadurch entfällt das Kopieren aus dem

Hauptspeicher und das Verändern der Pixel wird durch hardwarebeschleunigte Grafik-Funktionen

direkt im VRAM durchgeführt. So wird nicht nur die CPU entlastet, sondern Grafik-Operationen

können auch parallel, durch die Verwendung der Grafik-Hardware, durchgeführt werden. Dieser

neue, beschleunigte Bildtyp heißt VolatileImage.

(Quelle: VolatileImage.pdf)

Wie das Wort Volatile (auf Deutsch “flüchtig”) aber schon andeutet, ist das VRAM ein “flüchtiger”

Speicherbereich, in dem Daten jederzeit überschrieben werden können. Deshalb müssen bei der

Verwendung eines VolatileImages auch spezielle Maßnahmen getroffen werden, um den korrekten

Inhalt des Bildes sicherzustellen bzw. bei Bedarf zu erneuern. Diesen Zusatzaufwand muß man hier

leider betreiben, bekommt aber im Gegenzug einen extrem performanten Bild-Typ. Da ein

Überschreiben der Bilddaten im VRAM seltener auftritt, als man es zunächst annehmen würde und

als BackBuffer für das DoubleBuffering.

Folgende Situationen führen zu einem Überschreiben von Daten im VRAM:

• Ausführen einer anderen Anwendung im Fullscreen-Modus

• Starten eines Bildschirmschoners

• Unterbrechen eines Tasks über den Taskmanager (unter Windows)

• Verändern der Bildschirmauflösung

VolatileImage als BackBuffer

Wird ein VolatileImage als BackBuffer verwendet, dann muß man vor jedem Zugriff auf den

BackBuffer überprüfen, ob dieser noch gültig ist und bei Bedarf einen neuen BackBuffer erzeugen.

Außerdem muß man nach dem Rendern überprüfen, ob der Inhalt des VolatileImages in der

Zwischenzeit vielleicht überschrieben wurde und ggf. nochmal neu rendern. Ist dieser Fall jedoch

nicht eingetreten, dann kann man den BackBuffer jetzt auf dem Bildschirm anzeigen.Der veränderte Quellcode könnte in ungefähr so aussehen:

import java.awt.*;

import java.awt.image.*;

import javax.swing.JComponent;

public class MyComponent extends JComponent

{

private VolatileImage backBuffer;

private Image myImage;

// more code to write here .

{

// get the actual GraphicsConfiguration and create a compatible

// VolatileImage as BackBuffer

GraphicsConfiguration gc = getGraphicsConfiguration();

backBuffer = gc.createCompatibleVolatileImage(getWidth(),getHeight());

}

public void renderScreen()

{

// if backBuffer doesn't exist, create one

if (backBuffer == null) createBackBuffer();

do

{

// validate the backBuffer

int valCode = backBuffer.validate(getGraphicsConfiguration());

if (valCode == VolatileImage.IMAGE_RESTORED)

{

System.out.println("backBuffer - IMAGE_RESTORED");

// This case is just here for illustration

// purposes. Since we are

// recreating the contents of the back buffer

// every time through this loop, we actually

// do not need to do anything here to recreate

// the contents. If our VImage was an image that

// we were going to be copying _from_, then we

// would need to restore the contents at this point

}

else if (valCode == VolatileImage.IMAGE_INCOMPATIBLE)

{

// backBuffer Image is incompatible with actual screen

// settings, so we have to create a new compatible one

createBackBuffer();

}

// get Graphics object from backbuffer

Graphics g = backBuffer.getGraphics();

// render on backbuffer

g.drawImage(myImage,0,0,this);

g.drawLine(0,0,10,20);

// .

// rendering is done; now check if contents got lost

// and loop if necessary

} while (backBuffer.contentsLost());

}

// . more code to write here

}Einsatz und Grenzen der VolatileImage API

Die VolatileImage API ist momentan noch stark in Entwicklung und wird mit kommenden Java

Versionen noch weiter verbessert werden. Derzeit ist nur das Zeichnen von Lienen und das

Kopieren und Füllen von rechteckigen Bereichen hardwarebeschleunigt, sowie einige komplexere

Funktionen, die als Kombination dieser Basisfunktionen auftreten. Beim Rendern von Text können

die einzelnen Buchstaben nach dem Rendern im VRAM zwischengespeichert und von dort bei

weiteren Render-Durchläufen wieder kopiert werden, wodurch man sich ein aufwendiges Neu-

Rendern spart. Komplexere Funktionen jedoch wie z.B. diagonale Linien, Curved Surfaces, Anti-

Aliasing und Komposition sind nicht hardwarebeschleunigt und werden derzeit über reines


Swop your Documents