В следующем семестре у нас есть модуль для создания Java-приложений в команде. Требование модуля - сделать игру. Во время рождественских каникул я немного потренировался, но не могу найти лучший способ рисовать графику.
Я использую объект Java Graphics2D для рисования фигур на экране и вызываю repaint ()
30 раз в секунду, но это ужасно мерцает. Есть ли лучший способ рисовать высокопроизводительную 2D-графику на Java?
Что вы хотите сделать, так это создать компонент холста с BufferStrategy и выполнить его рендеринг. Код ниже должен показать вам, как это работает. Я извлек код из своего собственного движка. выше здесь .
Производительность зависит исключительно от того, что вы хотите рисовать, в моих играх в основном используются изображения. Примерно с 1500 из них я все еще выше 200 кадров в секунду при 480x480. И всего со 100 изображениями я получаю 6k FPS при отключении ограничения кадров.
Я создал небольшую игру (в этой игре одновременно отображается около 120 изображений), и ее можно найти здесь (да, приведенный ниже подход также отлично работает как апплет.)
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();
}
}
Мерцание происходит из-за того, что вы пишете прямо на экран. Используйте буфер для рисования, а затем записывайте весь экран за один раз. Это Двойная буферизация
, о которой вы, возможно, слышали раньше. Здесь - самая простая возможная форма.
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
. Создание внеэкранного изображения дорого, поэтому я бы посоветовал создавать его только при первом вызове.
Есть и другие области, в которых вы можете улучшить это дальше, например, создание совместимого изображения , используя отсечение ] и т. д. Для более точной настройки управления анимацией вам следует заглянуть в активный рендеринг .
Там есть приличная страница, которую я добавил в закладки, обсуждая обучающие материалы по играм здесь .
Удачи!
Я думаю, вы сделали переопределение из paint (Graphics g)
? Это не лучший способ. Используйте тот же код, но в paintComponent (Graphics g)
вместо paint (Graphics g)
.
Метка, которую вы можете найти, - это doublebuffer
. Это то, что будет сделано автоматически при переопределении paintComponent
.