2009-12-26 15 views
15

Nel prossimo semestre abbiamo un modulo per creare applicazioni Java in un team. Il requisito del modulo è di fare un gioco. Durante le vacanze di Natale ho fatto un po 'di pratica, ma non riesco a capire il modo migliore per disegnare la grafica.Grafica di gioco 2D Java

Sto usando l'oggetto Graphics2D Java per dipingere forme sullo schermo e chiamare repaint() 30 volte al secondo, ma questo tremolio terribilmente. C'è un modo migliore per dipingere grafica 2D ad alte prestazioni in Java?

risposta

16

Quello che vuoi fare è creare un componente canvas con una BufferStrategy e renderlo, il codice qui sotto dovrebbe mostrarti come funziona, ho estratto il codice dal mio motore scritto su here.

Le prestazioni dipendono esclusivamente dalle cose che si desidera disegnare, i miei giochi utilizzano principalmente le immagini. Con circa 1500 di loro sono ancora sopra 200 FPS a 480x480. E con solo 100 immagini sto colpendo 6k FPS quando disabilita il frame limit.

Un piccolo gioco (questo ha circa 120 immagini contemporaneamente lo schermo) che ho creato può essere trovato here (sì l'approccio seguito anche funziona bene come applet.)

import java.awt.Canvas; 
import java.awt.Color; 
import java.awt.Graphics2D; 
import java.awt.GraphicsConfiguration; 
import java.awt.GraphicsEnvironment; 
import java.awt.Toolkit; 
import java.awt.Transparency; 
import java.awt.event.WindowAdapter; 
import java.awt.event.WindowEvent; 
import java.awt.image.BufferStrategy; 
import java.awt.image.BufferedImage; 

import javax.swing.JFrame; 
import javax.swing.WindowConstants; 

public class Test extends Thread { 
    private boolean isRunning = true; 
    private Canvas canvas; 
    private BufferStrategy strategy; 
    private BufferedImage background; 
    private Graphics2D backgroundGraphics; 
    private Graphics2D graphics; 
    private JFrame frame; 
    private int width = 320; 
    private int height = 240; 
    private int scale = 1; 
    private GraphicsConfiguration config = 
      GraphicsEnvironment.getLocalGraphicsEnvironment() 
       .getDefaultScreenDevice() 
       .getDefaultConfiguration(); 

    // create a hardware accelerated image 
    public final BufferedImage create(final int width, final int height, 
      final boolean alpha) { 
     return config.createCompatibleImage(width, height, alpha 
       ? Transparency.TRANSLUCENT : Transparency.OPAQUE); 
    } 

    // Setup 
    public Test() { 
     // JFrame 
     frame = new JFrame(); 
     frame.addWindowListener(new FrameClose()); 
     frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 
     frame.setSize(width * scale, height * scale); 
     frame.setVisible(true); 

     // Canvas 
     canvas = new Canvas(config); 
     canvas.setSize(width * scale, height * scale); 
     frame.add(canvas, 0); 

     // Background & Buffer 
     background = create(width, height, false); 
     canvas.createBufferStrategy(2); 
     do { 
      strategy = canvas.getBufferStrategy(); 
     } while (strategy == null); 
     start(); 
    } 

    private class FrameClose extends WindowAdapter { 
     @Override 
     public void windowClosing(final WindowEvent e) { 
      isRunning = false; 
     } 
    } 

    // Screen and buffer stuff 
    private Graphics2D getBuffer() { 
     if (graphics == null) { 
      try { 
       graphics = (Graphics2D) strategy.getDrawGraphics(); 
      } catch (IllegalStateException e) { 
       return null; 
      } 
     } 
     return graphics; 
    } 

    private boolean updateScreen() { 
     graphics.dispose(); 
     graphics = null; 
     try { 
      strategy.show(); 
      Toolkit.getDefaultToolkit().sync(); 
      return (!strategy.contentsLost()); 

     } catch (NullPointerException e) { 
      return true; 

     } catch (IllegalStateException e) { 
      return true; 
     } 
    } 

    public void run() { 
     backgroundGraphics = (Graphics2D) background.getGraphics(); 
     long fpsWait = (long) (1.0/30 * 1000); 
     main: while (isRunning) { 
      long renderStart = System.nanoTime(); 
      updateGame(); 

      // Update Graphics 
      do { 
       Graphics2D bg = getBuffer(); 
       if (!isRunning) { 
        break main; 
       } 
       renderGame(backgroundGraphics); // this calls your draw method 
       // thingy 
       if (scale != 1) { 
        bg.drawImage(background, 0, 0, width * scale, height 
          * scale, 0, 0, width, height, null); 
       } else { 
        bg.drawImage(background, 0, 0, null); 
       } 
       bg.dispose(); 
      } while (!updateScreen()); 

      // Better do some FPS limiting here 
      long renderTime = (System.nanoTime() - renderStart)/1000000; 
      try { 
       Thread.sleep(Math.max(0, fpsWait - renderTime)); 
      } catch (InterruptedException e) { 
       Thread.interrupted(); 
       break; 
      } 
      renderTime = (System.nanoTime() - renderStart)/1000000; 

     } 
     frame.dispose(); 
    } 

    public void updateGame() { 
     // update game logic here 
    } 

    public void renderGame(Graphics2D g) { 
     g.setColor(Color.BLACK); 
     g.fillRect(0, 0, width, height); 
    } 

    public static void main(final String args[]) { 
     new Test(); 
    } 
} 
+0

Grazie !!! Questo è molto interessante. Anche il FPS limita. Il gioco che hai fatto è MOLTO BELLO! –

+0

Interessante, è strategy.show() sicuro chiamare dall'esterno dell'EDT? – Pool

+0

Un breve test con una seconda discussione dice sì, è sicuro. Per il try/catch, è lì solo perché Toolkit.getDefaultToolkit(). Sync() può lanciare un'eccezione in rari casi. –

3

Java OpenGL (JOGL) è a senso unico.

+0

JOGL è bello, ma dubito che possa convincere altri membri del team a usarlo. Le squadre sono teste di serie in tutti i livelli di abilità, e mentre io sono il tipo di persona che fa giochi nel tempo libero e scrive codice concorrente per divertimento, le altre persone del gruppo vorranno mantenere le cose il più semplice possibile (sfortunatamente) – Martin

2

Penso che l'utente abbia eseguito l'override da paint(Graphics g)? Questo non è il modo giusto. Utilizzare lo stesso codice ma in paintComponent(Graphics g) anziché paint(Graphics g).

Un'etichetta che è possibile cercare è doublebuffer. Questo è ciò che verrà fatto automaticamente ignorando paintComponent.

+0

quindi posso letteralmente copiare il codice da paint a paint component e tutto funzionerà allo stesso modo, tranne che sarà double buffered? – Martin

+0

sì, questo è ciò che intendo. La risposta di Feast descrive cosa sta succedendo.Ma Java aveva già una soluzione integrata. Quello che fa The Feast è semplicemente evitare di usare 'paintComponent' e fare la sua soluzione. –

8

Lo sfarfallio è dovuto alla scrittura diretta sullo schermo. Usa un buffer per disegnare e poi scrivi l'intero schermo in 1 go. Questo è Double Buffering di cui potresti aver sentito parlare prima. Here è la forma più semplice possibile.

public void paint(Graphics g) 
{ 

    Image image = createImage(size + 1, size + 1); 
    Graphics offG = image.getGraphics(); 
    offG.setColor(Color.BLACK); 
    offG.fillRect(0, 0, getWidth(), getHeight()); 
    // etc 

vedere l'uso dello schermo largo grafico offG. È costoso creare l'immagine fuori campo, quindi suggerirei di crearlo solo alla prima chiamata.

Altre aree possono essere ulteriormente migliorate, ad es. creating a compatible image, utilizzando clipping ecc. Per un controllo più accurato dell'animazione, è necessario esaminare active rendering.

C'è una pagina decente che ho aggiunto ai segnalibri discutendo i tutorial di gioco here.

Buona fortuna!

0

C'è un modo semplice per ottimizzare il tuo programma. Sbarazzati di qualsiasi codice complesso e usa semplicemente JComponent invece Canvas e dipingi i tuoi oggetti su di esso. È tutto. Buon divertimento ...