Мой игровой движок рисует черепичную карту на холсте внутри JFrame. Я использую Bufferstrategy, чтобы получить более плавный рендеринг и попытался повысить производительность в нескольких точках внутри программы и в итоге с ок. 120 кадров в секунду, чтобы заполнить окно 1920 * 1080 черным цветом и нарисовать некоторые изометрические 512 * 256 плиток сверху. При перемещении карты вокруг между плитами есть маленькие черные линии. Я предполагаю, что они происходят из-за того, что некоторые плитки уже перемещены в новую позицию, но изображение еще не сделано, когда накладывается на экран. Но у меня действительно нет такого решения, и я тоже не уверен, прав ли я.
Я должен, вероятно, отметить, что я использую два потока в своей программе, один поток вызывает методы обновления и рендеринга, а другой в настоящее время просто вводит пользовательский ввод для перемещения положения камеры. Также в некоторых лачугах все работает плавно, а в других он мерцает очень плохо.
Вот код создания и рендеринга окна:
private final void createWindow(){
frame = new JFrame(title);
frame.setMinimumSize(size);
frame.setMaximumSize(size);
frame.setPreferredSize(size);
display = new Canvas();
display.setMinimumSize(size);
display.setMaximumSize(size);
display.setPreferredSize(size);
image = ImageLoader.boostPerformance(new BufferedImage((int)size.getWidth(), (int)size.getHeight(), BufferedImage.TYPE_INT_RGB));
graphics = (Graphics2D) image.getGraphics();
frame.add(display);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.requestFocusInWindow();
}
public final void renderToScreen(){
BufferStrategy bs = display.getBufferStrategy();
if(bs==null){
display.createBufferStrategy(bufferCount);
return;
}
graphicsToScreen = (Graphics2D) bs.getDrawGraphics();
graphicsToScreen.drawImage(image, 0, 0, display.getWidth(), display.getHeight(), null);
graphicsToScreen.dispose();
bs.show();
}
Я использую графический объект Graphics2D в других классах для рисования фрагментов.
public void render(Graphics2D g) {
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
tilemap.render(g);
g.setColor(Color.red);
g.drawString(String.valueOf(Main.actualFps), 30, 30);
}
Выше представлен метод рендеринга в другом классе с использованием графического объекта, о котором я упоминал. И здесь вы можете увидеть способ отображения карты плитки:
private final void renderIsometric(Graphics2D g){
for(int x = 0; x < 4; x++){
for(int y = 3; y >= 0; y--){
if(x > tilesX - 1|| y > tilesY - 1)break;
if(x < 0 || y < 0)continue;
if(map[x][y] == null)continue;
g.drawImage(map[x][y].getImage(), (int)orthoToIso(x, y).x - cameraPosX, (int)orthoToIso(x, y).y - cameraPosY, null);
}
}
}
Наконец, вот основной цикл, чтобы убедиться, что счетчик fps работает, и значение 120 кадров в секунду правильное:
while(running){
long now = System.nanoTime();
double ns = 1000000000.0 / preferredUps;
delta += (now - lastTime) / ns;
lastTime = now;
while(delta >= 1){
//call the update method
update();
updates++;
delta--;
}
//call the render method
render();
frames++;
//refresh the actualUps and actualFps once every second
if(System.currentTimeMillis() - timer > 1000){
timer+=1000;
actualUps = updates;
actualFps = frames;
updates = 0;
frames = 0;
}
}
Как я могу избавиться от этих крошечных задержек и проблесков мерцания? И, кроме того, мне было интересно, будет ли лучше использовать OpenGL через LWJGL вместо Java2D?
Спасибо за вашу помощь!
То, что вы видите там, скорее всего, не мерцает. Его разрывающие и ткацкие артефакты. Это естественное следствие обновления экрана без синхронизации с вертикальной синхронизацией обновления экрана.
Оконные системы, как правило, не соблюдают v-sync (они предпочитают производительность, поскольку эти эффекты не очень ощутимы - или важны - в обычном пользовательском интерфейсе). Насколько я знаю, нет возможности синхронизировать с (J) фреймом (вы, вероятно, могли бы что-то взломать с помощью JNI, попросив ОС рассказать вам, когда растровый луч находится в верхней части кадра, но это не так),
Кроме того, рендеринг со скоростью 120 кадров в секунду может ухудшить проблему, если дисплейное устройство не обновится на 120 Гц - когда вы показываете больше кадров, чем на самом деле отображает устройство, вы вынуждаете устройство отображать графику из более чем одного кадра (например, кадр 1 в верхней половине экрана, кадр 2 в нижней половине). Это всегда приведет к перцептивным артефактам с движущимися объектами.
В чистом java есть один путь, и это полноэкранный режим (который будет ждать v-sync, в результате ваша скорость рендеринга ограничена и контролируется частотой обновления дисплея).
Вы можете попробовать openGL, но я понятия не имею, будет ли он синхронизироваться при рендеринге в оконном режиме.
Первым шагом для улучшения ситуации было бы не отображать быстрее, чем отображаемые устройства отображения (например, не более 60 кадров в секунду для экрана 60 Гц).
Если ваше приложение не критично, вы можете разрешить мерцание, используя стратегию двойной буферизации.
По существу, изображение, которое вы компонуете, копируется во вторичный буфер за пределами экрана, который не отображается в этом кадре, а в следующем кадре. Есть несколько трюков, которые вы можете использовать для автоматического двойного буфера для Swing, см. Также:
https://gamedev.stackexchange.com/questions/30921/thread-safe-double-buffering
Пример двойного буферизованного изображения в Jpanel
Недостатком двойной буферизации является то, что вы вводите один кадр задержки, что означает, что ваш ввод приведет только к видимым изменениям на один кадр позже, чем без двойной буферизации. На 120hz это, вероятно, не будет большой проблемой, а просто ради полноты.