2009-12-26 2 views
15

В следующем семестре у нас есть модуль для создания приложений Java в команде. Требование модуля - сделать игру. За рождественские каникулы я занимался небольшой практикой, но я не могу найти лучший способ рисовать графику.Java 2D игровая графика

Я использую объект Java Graphics2D для рисования фигур на экране и вызова repaint() 30 раз в секунду, но это ужасно мерцает. Есть ли лучший способ рисовать высокопроизводительные 2D-графики на Java?

ответ

16

Что вы хотите сделать, это создать компонент холста с помощью BufferStrategy и визуализировать его, приведенный ниже код должен показать вам, как это работает, я извлек код из моего собственного письменного Engine за here.

Производительность зависит только от материала, который вы хотите нарисовать, в моих играх в основном используются изображения. Около 1500 из них я все еще выше 200 FPS на 480x480. И всего за 100 изображений я нажимаю 6k FPS при отключении рамки.

Небольшая игра (это один имеет около 120 изображений одновременно на экране), который я создал, можно найти here (да подход ниже, также прекрасно работает как апплет.)

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

Спасибо !!! Это очень интересно. Ограничение FPS также. Игра, которую вы сделали, ОЧЕНЬ СЛАВНА! –

+0

Интересно, есть стратегия.show() безопасно звонить из-за пределов EDT? – Pool

+0

Короткий тест со второй нитью говорит «да», это безопасно. Для try/catch это только потому, что Toolkit.getDefaultToolkit(). Sync() МОЖЕТ выбросить исключение в редких случаях. –

3

Java OpenGL (JOGL) - один способ.

+0

JOGL хорош, но я сомневаюсь, что смогу убедить других членов команды использовать его. Команды посеяны на всех уровнях мастерства, и, хотя я являюсь человеком, который делает игры в свободное время и записывает параллельный код для развлечения, другие люди в группе захотят, чтобы все было как можно проще (к сожалению) – Martin

2

Я думаю, вы сделали переопределение от paint(Graphics g)? Это не очень хорошо. Используйте тот же код, но в paintComponent(Graphics g) вместо paint(Graphics g).

Наклейка, которую вы можете найти, - doublebuffer. Это будет сделано автоматически путем переопределения paintComponent.

+0

, поэтому я могу буквально просто скопировать код из краски в компонент рисования, и все будет работать одинаково, за исключением того, что он будет дважды буферизирован? – Martin

+0

да, это то, что я имею в виду. Ответ Пира описывает, что происходит.Но у Java уже есть встроенное решение. «The The Feast» просто избегает использования 'paintComponent' и делает свое собственное решение. –

8

Мерцание связано с тем, что вы пишете прямо на экране. Используйте буфер для рисования, а затем напишите весь экран за 1 раз. Это Double Buffering, о котором вы, возможно, слышали раньше. Here - самая простая форма.

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 

См использование экрана от графики offG. Очень дорого создать изображение вне экрана, поэтому я предлагаю создать его только при первом вызове.

Есть другие области, которые вы можете улучшить, например, creating a compatible image, используя clipping и т. Д. Для более точной настройки анимации вы должны посмотреть active rendering.

Есть приличная страница, которую я заложил, обсуждая учебные уроки игры here.

Удачи вам!

0

Eсть простой способ оптимизации вашей программы. Избавьтесь от любого сложного кода и просто используйте JComponent вместо Canvas и нарисуйте на нем свои объекты. Это все. Наслаждайтесь этим ...

Смежные вопросы