Entwicklung eines 2D Computerspiels in Java Motivation • Arbeiten mit grösseren Programmen! • Lesen und manipulieren von Programmen Anderer. Alle Beispielprogramme sind aus dem Buch: "Developing Games in Java." von David Brackeen http://www.brackeen.com/javagamebook Das Buch wird für die Vorlesung und die Übungen NICHT benötigt, der Programmcode für Kapitel 2 und 3 sollte übers Web geladen werden. 1 Themenübersicht • • 2D Grafik und Animation – Darstellungsmodi – Vollbildschirmdarstellung – Bilder – Animation – Sprites Interaktivität – • Eingabe per Tastatur und Maus Ein 2D-Spiel – Spielfelddesign und Scrolling – Spieler, Gegner und andere Objekte – Kollisionsdetektion 2D Grafik und Animation 2 Raster Displays Die Bildausgabe besitzt meist einen eigenen Speicher. Da Veränderungen im Speicher sofort beim nächsten Bildaufbau sichtbar werden, kommt es bei Animationen zu störenden Artefakten. True-Color Frame Buffer Für jeden Bildpunkt benötigt mindestens 3 Byte. 2^24 Farben Reihenfolge der Bytes ist zu beachten ( „Endians“) . 3 Double Buffering Ein zweiter Bildspeicher verbessert Animationen. Bildaufbau wird unsichtbar – zwischen den Speichern wird nur zu Beginn einer Seite und wenn der Bildaufbau abgeschlossen ist umgeschalten. Grafik Hardware Computergraphik ist einer der wenigen Bereiche bei dem immer mehr spezial Hardware zum Einsatz kommt. z.B. 1 M pixel * 60 Bilder/sec * 1 ops/ pix = 60M ops/sec Schon einfaches verschieben einer Bildschirmseite ist eine sehr aufwändige Operation!! Sind es nicht gerade die Bilder, warum wir einen Computer nutzen!! 4 Darstellungsmodi • Der Bildschirm kann in verschiedene Darstellungsmodi geschaltet werden. • Die verfügbaren Modi hängen von der Grafikkarte und dem Bildschirm ab. • Ein Darstellungsmodus (display mode) besteht aus: – Bildschirmauflösung in Pixeln (z.B.: 1024 x 768, 1280 x 1024, ...) – Farbtiefe pro Pixel (z.B.: 8 Bit = 256 Farben, 24 Bit = 16 Mio Farben) – Bildwiederholfrequenz (z.B. 75Hz, 85Hz, ...) Warum so eine Vielfalt? Sehr viele unterschiedliche Technologien und Hersteller. Vollbildschirmdarstellung • Vollbildschirmdarstellung (full screen graphics) ist für Spiele am geeignetsten. • Es werden 3 Objekte zur Umschaltung auf Vollbildschirmdarstellung benötigt. • Window-Objekt • – Abstraktion, dessen was auf dem Bildschirm dargestellt wird (Leinwand). – Im Beispiel: JFrame DisplayMode-Objekt – • Spezifiziert Auflösung, Farbtiefe und Bildwiederholfrequenz. GraphicsDevice-Objekt – Erlaubt den Darstellungsmodus zu ändern. – „Interface zur Grafikkarte” – Beschaffung über GraphicsEnvironment-Objekt. 5 Vollbildschirmdarstellung JFrame window = new JFrame(); DisplayMode displayMode = new DisplayMode(800, 600, 16, 75) // get the GraphicsDevice GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice device = environment.getDefaultScreenDevice(); // use the Jframe as the full screen window device.setFullScreenWindow(window); // change the display mode device.setDisplayMode(displayMode); SimpleScreenManager.java • Die SimpleScreenManager Klasse ist nur dazu da den Bildschirm in die Vollbildschirmdarstellung zu schalten! – Es wird der Rand des Fensters entfernt ! – Die Fenstergrösse soll nicht mehr manuell verändert werden können. 6 SimpleScreenManager.java import java.awt.*; import javax.swing.JFrame; /** The SimpleScreenManager class manages initializing and displaying full screen graphics modes. */ public class SimpleScreenManager { private GraphicsDevice device; public SimpleScreenManager() {…….} /** Enters full screen mode and changes the display mode. */ public void setFullScreen(DisplayMode displayMode, JFrame window) /** Returns the window currently used in full screen mode. */ public Window getFullScreenWindow() {………….. } /** Restores the screen's display mode. */ public void restoreScreen() {…..} } // END SimpleScreenManager SimpleScreenManager. setFullScreen public class SimpleScreenManager { …….. /** Enters full screen mode and changes the display mode. */ public void setFullScreen(DisplayMode displayMode, JFrame window) { window.setUndecorated(true); window.setResizable(false); device.setFullScreenWindow(window); } if (displayMode != null && device.isDisplayChangeSupported() { try { device.setDisplayMode(displayMode); } catch (IllegalArgumentException ex) { // ignore - illegal mode for this device } } ) 7 SimpleScreenManager. ….. public class SimpleScreenManager { ………………… /** Returns the window currently used in full screen mode. */ public Window getFullScreenWindow() { return device.getFullScreenWindow(); } /** Restores the screen's display mode. */ } public void restoreScreen() { Window window = device.getFullScreenWindow(); if (window != null) { window.dispose(); } device.setFullScreenWindow(null); } Vollbildschirmdarstellung • Die Funktionalität zum Umschalten auf die Vollbildschirmdarstellung wird in die Klasse S i m p l e S c r e e n M a n a g e r gepackt. • Listing: SimpleScreenManager.java • F u l l S c r e e n T e s t ist ein Testprogramm zur Vollbildschirmdarstellung • – Kommandozeilenparameter: Auflösung und Farbtiefe – Aufruf z.B.: java FullScreenTest 1024 768 32 – Demo ... Listing: FullScreenTest.java 8 Bilder • Transparenz • Dateiformate • Bilder einlesen • Hardwarebeschleunigte Bilder • Benchmarks Repräsentation eines Bildes durch ‚Pixel‘ • Die Bildpunkte (Pixel) eines Bildes werden als ganze Zahlen (integer) gespeichert. Rot Grün Blau Alpha Die 4 Byte eines 32-bit Integer Bildpunktes. Für Alpha = 0 ist der Bildpunkt transparent, RGBA wird bei OpenGL verwendet. für Alpha = 255 ist er undurchsichtig. ARGB bei Java 3D ..... Beim Speichern und Lesen ist die Reihenfolge der Bytes der verwendeten Hardware zu beachten (big- / little endian)!! • Ein Bild wird in einem 1-dim Array von ints gespeichert. pixel [127] z.B.: Höhe = 4 Breite = 32 => 128 pixel pixel [0] pixel[0]: pixel [31] ist bei OpenGL links unten, bei Java 3D links oben! 9 Bilder: Transparenz • Es gibt verschiedene Möglichkeiten Bilder darzustellen • Opaque – alle Pixel sind sichtbar – auch die Hintergrundpixel! • Transparent – Pixel sind 100% sichtbar – oder 100% unsichtbar – z.B. unsichtbarer Hintergrund • Translucent – Pixel teilweise sichtbar – durchscheinen Bilder: Dateiformate • Bilder können in verschiedenen Dateiformaten abgespeichert werden. • GIF – Verlustfreie Kompression – geeignet für Graphiken • PNG – Verlustfreie Kompression – mehr Funktionalität als GIF • JPEG – Verlustbehaftete Kompression (lossy compression) – geeignet für Photos 10 Bilder einlesen 1. Möglichkeit: ( java.awt ) Toolkit Image toolkit = Toolkit.getDefaultToolkit(); image = toolkit.getImage(filename); Da das Laden des Bildes in einem eigenen Thread geschieht, kann es vorkommen, dass man das Bild anzeigt obwohl es noch nicht vollständig geladen ist. 2. Möglichkeit: ( javax.swing ) ImageIcon icon = new ImageIcon(filename); Image image = icon.getImage(); Hier wird das Laden überwacht und das Bild erst dargestellt, wenn es auch vorhanden ist. ImageTest.java import java.awt.*; import javax.swing.ImageIcon; import javax.swing.JFrame; public static void main(String[] args) { DisplayMode displayMode; public class ImageTest extends JFrame { if (args.length == 3) { displayMode = new DisplayMode( Integer.parseInt(args[0]), Integer.parseInt(args[1]), Integer.parseInt(args[2]), DisplayMode.REFRESH_RATE_UNKNOWN); } else { displayMode = new DisplayMode(800, 600, 16, DisplayMode.REFRESH_RATE_UNKNOWN); } public static void main(String[] args) {…….} private static final int FONT_SIZE = 24; private static final long DEMO_TIME = 10000; private private private private private private private SimpleScreenManager screen; Image bgImage; Image opaqueImage; Image transparentImage; Image translucentImage; Image antiAliasedImage; boolean imagesLoaded; public void run(DisplayMode displayMode) {…….} ImageTest test = new ImageTest(); test.run(displayMode); } public void loadImages() {…….} public void paint(Graphics g) {…….} } public void drawImage(Graphics g, Image image, int x, int y, String caption) {…….} 11 ImageTest.java import java.awt.*; import javax.swing.ImageIcon; import javax.swing.JFrame; public static void main(String[] args) { public void run(DisplayMode displayMode) { setBackground(Color.blue); DisplayMode displayMode; setForeground(Color.white); public class ImageTest extends JFrame { setFont(new Font("Dialog", Font.PLAIN, FONT_SIZE)); if (args.length == 3) { imagesLoaded = false; displayMode = new DisplayMode( public static void main(String[] args) {…….} Integer.parseInt(args[0]), screen = new SimpleScreenManager(); Integer.parseInt(args[1]), private static final int FONT_SIZE = 24; try { Integer.parseInt(args[2]), private static final long DEMO_TIME = 10000; screen.setFullScreen(displayMode, this); DisplayMode.REFRESH_RATE_UNKNOWN); loadImages(); } private SimpleScreenManager screen; try { else { private Image bgImage; Thread.sleep(DEMO_TIME); displayMode = new DisplayMode(800, 600, 16, private Image opaqueImage; } DisplayMode.REFRESH_RATE_UNKNOWN); private Image transparentImage; catch (InterruptedException ex) { } } private Image translucentImage; } private Image antiAliasedImage; finally { ImageTest test = new ImageTest(); private boolean imagesLoaded; screen.restoreScreen(); test.run(displayMode); } } public void run(DisplayMode displayMode) {…….} } public void loadImages() {…….} public void paint(Graphics g) {…….} public void drawImage(Graphics g, Image image, int x, int y, String caption) {…….} } ImageTest.java (2) public static void main(String[] args) { DisplayMode displayMode; if (args.length == 3) { displayMode = new DisplayMode( Integer.parseInt(args[0]), Integer.parseInt(args[1]), Integer.parseInt(args[2]), DisplayMode.REFRESH_RATE_UNKNOWN); } else { displayMode = new DisplayMode(800, 600, 16, DisplayMode.REFRESH_RATE_UNKNOWN); } public void run(DisplayMode displayMode) { setBackground(Color.blue); setForeground(Color.white); setFont(new Font("Dialog", Font.PLAIN, FONT_SIZE)); imagesLoaded = false; screen = new SimpleScreenManager(); try { screen.setFullScreen(displayMode, this); loadImages(); try { Thread.sleep(DEMO_TIME); } catch (InterruptedException ex) { } } finally { screen.restoreScreen(); } ImageTest test = new ImageTest(); test.run(displayMode); } } 12 ImageTest.java (3) public void run(DisplayMode displayMode) { setBackground(Color.blue); setForeground(Color.white); setFont(new Font("Dialog", Font.PLAIN, FONT_SIZE)); imagesLoaded = false; public void loadImages() { bgImage = loadImage("images/background.jpg"); screen = new SimpleScreenManager(); opaqueImage = loadImage("images/opaque.png"); try { transparentImage = loadImage("images/transparent.png"); screen.setFullScreen(displayMode, this); translucentImage = loadImage("images/translucent.png"); loadImages(); try { antiAliasedImage = loadImage("images/antialiased.png"); Thread.sleep(DEMO_TIME); imagesLoaded = true; } // signal to AWT to repaint this window catch (InterruptedException ex) { } repaint(); } } finally { screen.restoreScreen(); } private Image loadImage(String fileName) { } return new ImageIcon(fileName).getImage(); } public void drawImage(Graphics g, Image image, int x, int y, String caption) { g.drawImage(image, x, y, null); g.drawString(caption, x + 5, y + FONT_SIZE + image.getHeight(null) ); } ImageTest.java (3) public void paint(Graphics g) { // set text anti-aliasing if (g instanceof Graphics2D) { Graphics2D g2 = (Graphics2D)g; g2.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } // draw images if (imagesLoaded) { g.drawImage(bgImage, 0, 0, null); drawImage(g, opaqueImage, 0, 0, "Opaque"); drawImage(g, transparentImage, 320, 0, "Transparent"); drawImage(g, translucentImage, 0, 300, "Translucent"); drawImage(g, antiAliasedImage, 320, 300, "Translucent (Anti-Aliased)"); } else { g.drawString("Loading Images...", 5, FONT_SIZE); public void drawImage(Graphics g, Image image, int x, int y, } String caption) } { g.drawImage(image, x, y, null); g.drawString(caption, x + 5, y + FONT_SIZE + image.getHeight(null) ); } 13 Spielentwicklung: was haben wir bis jetzt! SimpleSreeenManager.java ( kein main ) Hier werden alle für den Bildschirm relevanten Einstellungen gesetzt. Vollbildschirm, Pixelauflösung, FullSreenTest.java ( mit main ) Testprogramm für SimpleSreenManager Klasse ImageTest.java ( mit main ) Bilder in gängigen Formaten ( .jpg, .png ...) können in die Klasse java.awt.Image eingelesen werden. Bilder im java.awt.Image Format können dargestellte werden. Bilder: Benchmarks • Benchmarks 14 ImageSpeedTest.java public void run(DisplayMode displayMode) { publicsetBackground(Color.blue); static void main(String[] args) { setForeground(Color.white); import java.awt.*; setFont(new Font("Dialog", DisplayMode displayMode; Font.PLAIN, FONT_SIZE)); import javax.swing.ImageIcon; imagesLoaded = false; import javax.swing.JFrame; if (args.length == 3) { screen = new SimpleScreenManager(); displayMode = new DisplayMode( public class ImageTest extends JFrame { try { Integer.parseInt(args[0]), this); Integer.parseInt(args[1]), public static void main(String[] args) { …as in ImageTest…. } screen.setFullScreen(displayMode, synchronized (this) { Integer.parseInt(args[2]), loadImages(); DisplayMode.REFRESH_RATE_UNKNOWN); private static final int FONT_SIZE = 24; // wait for test to complete } private static final long TIME_PER_IMAGE = 1500; // changed try { else { wait(); displayMode = new DisplayMode(800, 600, 16, private SimpleScreenManager screen; } DisplayMode.REFRESH_RATE_UNKNOWN); private Image bgImage; catch (InterruptedException ex) { } } private Image opaqueImage; } private Image transparentImage; finally { ImageSpeedTest test = new ImageSpeedTest(); private Image translucentImage; screen.restoreScreen(); test.run(displayMode); private Image antiAliasedImage; } } private boolean imagesLoaded; } public void run(DisplayMode displayMode) {… changed….} public void loadImages() {… as in ImageTest ….} public void paint(Graphics g) {… changed ….} } public void drawImage(Graphics g, Image image, int x, int y, String caption) {…changed….} ImageSpeedTest.java (2) public void paint(Graphics g) { if (g instanceof Graphics2D) { Graphics2D g2 = (Graphics2D)g; g2.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); public void drawImage(Graphics g, Image image, String name) { } int width = screen.getFullScreenWindow().getWidth() - image.getWidth(null); int height = screen.getFullScreenWindow().getHeight() - image.getHeight(null); int numImages = 0; // draw images } if (imagesLoaded) { g.drawImage(bgImage, 0, 0, null); drawImage(g, opaqueImage, "Opaque"); drawImage(g, transparentImage, "Transparent"); drawImage(g, translucentImage, "Translucent"); long startTime = System.currentTimeMillis(); drawImage(g, antiAliasedImage, "Translucent (Anti-Aliased)"); while (System.currentTimeMillis() - startTime < TIME_PER_IMAGE ) { // notify that the test is complete int x = Math.round( (float) Math.random() * width ); synchronized (this) { int y = Math.round( (float) Math.random() * height ); notify(); g.drawImage(image, x, y, null); } numImages++; } } else { long time = System.currentTimeMillis() - startTime; g.drawString("Loading Images...", 5, FONT_SIZE); } float speed = numImages * 1000f / time; System.out.println(name + ": " + speed + " images/sec"); } 15 Animation • Dynamisches verändern des Bildes! Am einfachsten ist eine Abfolge von fertigen Teilbildern. Bild A Zeit ms Bild B 0ms 200ms Bild C 275ms Bild B 300ms end Animation Abfolge von einzelnen Teilbildern, wobei jedes Bild für eine bestimmte Zeit präsentiert wird. Teilbilder : class AnimFrame { Image image; long endTime; public AnimFrame( Image image, long endTime ) { this.image = image; this.endTime = endTime; } } 16 Animation.java public class Animation { java.util.ArrayList; private private private private ArrayList frames; int currFrameIndex; long animTime; long totalDuration; /** Creates a new, empty Animation. public Animation() { ……… } /** */ Adds an image to the animation with the specified duration (time to display the image). */ public synchronized void addFrame(Image image, long duration) {……. } /** Starts this animation over from the beginning. */ /** Updates this animation's current image (frame), if neccesary. */ /** Gets this Animation's current image. Returns null if this animation has no images. */ public synchronized void start() { ………..} public synchronized void update(long elapsedTime) {…………. } public synchronized Image getImage() {…….. } private AnimFrame getFrame(int i) { } } private class AnimFrame { } Animation.java (2) public class Animation { private private private private ArrayList frames; int currFrameIndex; long animTime; long totalDuration; Innere Klasse public Animation() synchronized{void update(long elapsedTime) { public if (frames.size() > 1) { frames = new ArrayList(); animTime += totalDuration = 0;elapsedTime; start(); if (animTime >= totalDuration) { } animTime = animTime % totalDuration; currFrameIndex = 0; } public synchronized void addFrame(Image image, long duration) while (animTime > getFrame(currFrameIndex).endTime) { totalDuration += duration; /** Creates a new, empty Animation. */ currFrameIndex++; frames.add(new AnimFrame(image, totalDuration)); public Animation() { ……… } } } } /** Adds an image to the animation with the specified } duration (time to display the image). */ public synchronized void addFrame(Image image, longsynchronized duration) {……. } public void start() { { animTime = 0; currFrameIndex = 0; if (frames.size() == 0) { } return null; /** Updates this animation's current image (frame), if neccesary. */ } public synchronized void update(long elapsedTime) {…………. } else { return getFrame(currFrameIndex).image; /** Gets this Animation's current image. Returns null if this } animation has no images. */ public synchronized Image getImage() {…….. } } /** Starts this animation over from the beginning. public synchronized void start() { ………..} private AnimFrame getFrame(int i) { } } private class AnimFrame { } */ public synchronized Image getImage() { private AnimFrame getFrame(int i) { return (AnimFrame)frames.get(i); } 17 Animation.java (3) public Animation() { frames = new ArrayList(); totalDuration = 0; start(); } public synchronized void update(long elapsedTime) { if (frames.size() > 1) { animTime += elapsedTime; if (animTime >= totalDuration) { animTime = animTime % totalDuration; currFrameIndex = 0; } public synchronized void addFrame(Image image, long duration) { totalDuration += duration; frames.add(new AnimFrame(image, totalDuration)); } while (animTime > getFrame(currFrameIndex).endTime) { currFrameIndex++; } } } public synchronized void start() { animTime = 0; currFrameIndex = 0; } public class AnimFrame { Image image; long endTime; public AnimFrame( Image image, long endTime ) { this.image = image; this.endTime = endTime; } } public synchronized Image getImage() { if (frames.size() == 0) { return null; } else { return getFrame(currFrameIndex).image; } } private AnimFrame getFrame(int i) { return (AnimFrame) frames.get(i); } 'Active Rendering' Um eine Animation anzuzeigen muss der Bildschirm kontinuierlich erneuert werden. Bisher wurde dies von paint() übernommen, welches mit repaint() ausgelöst wurde. Dies kann jedoch zu kleinen Verzögerungen führen, wenn der "AWT-Thread" gerade noch anderweitig beschäftigt ist. AUSWEG: 'Active Rendering' , über getGraphics() von Component erhalten wir den Graphics Context des Bildschirms. Graphics g = screen.getFullScreenWindow().getGraphics(): draw(g); g.dispose(); Nach dem Zeichen sollte sofort g.dispose() aufgerufen werden um Systemressourcen frei zu geben. 18 AnimationTest1.java Ein Animationszyklus [ animationLoop() ] hat folgende Schritte: 1. 2. 3. 4. Alle Animationen auf die richtige Zeit setzen, update(). Alle entsprechenden Bilder anzeigen. Eventuell warten. Wieder zu Schritt eins. AnimationTest1.java (2) ein vereinfachter 'animationLoop()' while (true) { // update any animation updateAnimations(); // draw to screen Graphics g = screen.getFullScreenWindow().getGraphics(): draw(g); g.dispose(); } // take a nap try { Tread.sleep( 2000); } catch (InteruptException ex) {} 19 AnimationTest1.java (3) public void run(DisplayMode displayMode) { screen = new SimpleScreenManager(); try { screen.setFullScreen(displayMode, new JFrame()); loadImages(); animationLoop(); public class AnimationTest1 { } finally { public static void main(String args[]) { ..as usual.. } screen.restoreScreen(); } private static final long DEMO_TIME = 5000; } import java.awt.*; import javax.swing.ImageIcon; import javax.swing.JFrame; private SimpleScreenManager screen; private Image bgImage; private Animation anim; public void loadImages() { } private Image loadImage(String fileName) { } public void loadImages() { // load images bgImage = loadImage("images/background.jpg"); Image player1 = loadImage("images/player1.png"); Image player2 = loadImage("images/player2.png"); Image player3 = loadImage("images/player3.png"); // create animation anim = new Animation(); anim.addFrame(player1, 250); anim.addFrame(player2, 150); anim.addFrame(player1, 150); anim.addFrame(player2, 150); anim.addFrame(player3, 200); anim.addFrame(player2, 150); public void run(DisplayMode displayMode) { } public void animationLoop() { } public void draw(Graphics g) { } } } AnimationTest1.java (4) public void run(DisplayMode displayMode) { screen = new SimpleScreenManager(); try { screen.setFullScreen(displayMode, new JFrame()); loadImages(); animationLoop(); } finally { screen.restoreScreen(); } } public void animationLoop() { long startTime = System.currentTimeMillis(); long currTime = startTime; while (currTime - startTime < DEMO_TIME) { long elapsedTime = System.currentTimeMillis() - currTime; currTime += elapsedTime; // update animation anim.update(elapsedTime); // draw to screen Graphics g = screen.getFullScreenWindow().getGraphics(); draw(g); g.dispose(); public void draw(Graphics g) { // draw background g.drawImage(bgImage, 0, 0, null); // take a nap try { Thread.sleep(20); } catch (InterruptedException ex) { } // draw image g.drawImage(anim.getImage(), 0, 0, null); } } } 20 Problem mit AnimationTest1 Die Animation flimmert!!! Mögliche Gründe: 1. Während der Animation werden halbfertige Bilder angezeigt. 2. Das Bild wird angezeigt bevor alle Teilbilder der draw - Routine gemalt sind. 3. Das Anzeigen des neuen Bildes ist nicht mit der Bildfrequenz synchronisiert (~75 Hz). Problem mit AnimationTest1 Die Animation flimmert!!! mögliche Lösungen: 1. Bilder werden off-screen erstellt und dann als Ganzes in den Framebuffer kopiert. 2. Falls der Framebuffer gross genug ist werden zwei Bildbereiche angelegt. Der eine wird vom Bildschirm ausgelesen, und im anderen wird das Bild verändert. Danach wechseln die Rollen "page-flipping„. Es bleibt immer noch die mögliche Interferenz mit der Bildwiederholungsfrequenz. Die beste Strategie ist von der Hardware abhängig. Die Optimierung der Lösung kann der BufferStrategy Klasse überlassen werden! 21 BufferStrategy Klasse API ---> gibt guten Überblick zu den verschiedenen Möglichkeiten. Canvas, Window besitzen eine BufferStrategy: Für das Jframe möchten wir mindestens 2 Buffer: // Create a general double-buffering strategy frame.createBufferStrategy(2); BufferStrategy strategy = frame.getBufferStrategy(); // Render loop while (!done) { Graphics g = strategy.getDrawGraphics(); // Draw to graphics draw(g); g.dispose(); strategy.show(); // switch to next buffer } Verbesserter ScreenManager 'Double Buffering' + 'Active Rendering' sollen von ScreenManager übernommen werden. Weitere Verbesserungen: Screenmanager vergleicht automatisch mögliche Displaymodi mit einer vorgegebenen internen Liste. Da 'Active Rendering' verwendet wird, wird die automatische paint() Methode abgeschaltet. frame.setIgnoreRepaint(true) ScreenManager.java soll hier nicht im Detail besprochen werden, relativ viel Code zur Anpassung an die vielen verschieden Plattformen! 22 SimpleScreenManager public void setFullScreen(DisplayMode displayMode, Jframe window ) { window.setUndecorated(true); window.setResizable(false); public void setFullScreen(DisplayMode displayMode) { final JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setUndecorated(true); frame.setIgnoreRepaint(true); frame.setResizable(false); device.setFullScreenWindow(window); } if (displayMode != null && device.isDisplayChangeSupported() { try { device.setDisplayMode(displayMode); } catch (IllegalArgumentException ex) { // ignore - illegal mode for this device } } ScreenManager device.setFullScreenWindow(frame); ) } if (displayMode != null && device.isDisplayChangeSupported()) { try { device.setDisplayMode(displayMode); } catch ( IllegalArgumentException ex ) { } // fix for mac os x frame.setSize(displayMode.getWidth(), displayMode.getHeight()); } // avoid potential deadlock in 1.4.1_02 try { EventQueue.invokeAndWait(new Runnable() { public void run() { frame.createBufferStrategy(2); } }); } catch (InterruptedException ex) { // ignore } catch (InvocationTargetException ex) { // ignore } AnimationTest2.java public class AnimationTest1 { public static void main(String args[]) { ..changed.. } private static final DisplayMode POSSIBLE_MODES[] = { new DisplayMode(1024, 768, 32, 0), new DisplayMode( 800, 600, 32, 0), ………. }; private private private private static final long DEMO_TIME = 10000; SimpleScreenManager screen; Image bgImage; Animation anim; public void loadImages() { ..as before } private Image loadImage(String fileName) {..as before } public void run(DisplayMode displayMode) { ..changed.. } public void animationLoop() {..changed.. } } public void draw(Graphics g) {..as before } 23 AnimationTest1.java public void animationLoop() { long startTime = System.currentTimeMillis(); long currTime = startTime; AnimationTest2.java public void animationLoop() { long startTime = System.currentTimeMillis(); long currTime = startTime; while (currTime - startTime < DEMO_TIME) { long elapsedTime = System.currentTimeMillis() - currTime; currTime += elapsedTime; while (currTime - startTime < DEMO_TIME) { long elapsedTime = System.currentTimeMillis() - currTime; currTime += elapsedTime; // update animation anim.update(elapsedTime); // update animation anim.update(elapsedTime); // draw to screen Graphics g = screen.getFullScreenWindow().getGraphics(); draw(g); g.dispose(); // draw and update screen Graphics2D g = screen.getGraphics(); draw(g); g.dispose(); screen.update(); // take a nap try { Thread.sleep(20); } catch (InterruptedException ex) { } // take a nap try { Thread.sleep(20); } catch (InterruptedException ex) { } } } } } AnimationTest1.java public static void main(String args[]) { DisplayMode displayMode; if (args.length == 3) {displayMode = new DisplayMode( Integer.parseInt(args[0]), Integer.parseInt(args[1]), Integer.parseInt(args[2]), DisplayMode.REFRESH_RATE_UNKNOWN ); } else { displayMode = new DisplayMode(800, 600, 16, DisplayMode.REFRESH_RATE_UNKNOWN); } AnimationTest1 test = new AnimationTest1(); test.run(displayMode); } public void run(DisplayMode displayMode) { screen = new SimpleScreenManager(); try { screen.setFullScreen(displayMode, new JFrame()); loadImages(); animationLoop(); } finally { screen.restoreScreen(); } } AnimationTest2.java public static void main(String args[]) { AnimationTest2 test = new AnimationTest2(); test.run(); } public void run() { screen = new ScreenManager(); try { DisplayMode displayMode = screen.findFirstCompatibleMode(POSSIBLE_MODES); screen.setFullScreen(displayMode); loadImages(); animationLoop(); } finally { screen.restoreScreen(); } } 24 AnimationTest 2 .draw() public void draw(Graphics g) { // draw background g.drawImage(bgImage, 0, 0, null); // draw image g.drawImage(anim.getImage(), 0, 0, null); } 25