Всякий раз, когда я перемещаю камеру в java-игре, над которой я работаю, края плиток начинают мерцать, а между швами появляются промежутки, как показано на представленном рисунке. изображение мерцает
Я следил за серией руководств, созданных RyiSnow на YouTube, и эта ошибка возникла, когда я добрался до 5-го руководства в этой серии.
Я подозреваю, что это может быть проблема с производительностью, однако я не уверен, в чем проблема. Странно то, что при перемещении камеры вверх и влево тайлы мерцают, но при перемещении камеры вниз и влево плитки не мерцают. Эта ошибка еще более заметна в полноэкранном режиме, даже если все плитки уже были отрисованы в оконном режиме.
Минимальный воспроизводимый пример:
Основная.java:
package src.main;
import javax.swing.JFrame;
public class Main //the main class
{
public static void main(String args[])
{
GamePanel gamePanel = new GamePanel();
JFrame window = new JFrame(); //create a new JFrame
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //task will terminate when window is closed
window.add(gamePanel); //adds the game panel to the window frame
window.pack(); //sets size to preferred size
window.setVisible(true);
window.setLocationRelativeTo(null); //center window
}
}
GamePanel.java:
package src.main;
//import necessary libraries
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class GamePanel extends JPanel implements Runnable //class for the game screen
{
Thread gameThread; //the thread in which the game operates
BufferedImage sprite;
public int cameraY = -1400;
public int cameraX = -1400;
public GamePanel()
{
this.setPreferredSize(new Dimension(480, 320)); //set preferred size to the variables previously defined
this.setDoubleBuffered(true); //all drawing done in offscreen buffer; improves game performance
this.setFocusable(true);
//load the tile image
try
{
sprite = ImageIO.read(getClass().getResourceAsStream("/res/Grass.png"));
}
catch(IOException e)
{
e.printStackTrace();
}
gameThread = new Thread(this); //instantiate a new thread
gameThread.start(); //start the thread
}
@Override
public void run() //runs when thread is started
{
double drawInterval = 1000000000/60; //set the interval to 1000000000 divide by the fps (60)
double delta = 0;
long lastTime = System.nanoTime();
long currentTime;
while(gameThread != null) //repeat while gamethread is active
{
currentTime = System.nanoTime();
delta +=(currentTime - lastTime) / drawInterval;
lastTime = currentTime;
if (delta >= 1)
{
cameraY++;
cameraX++;
repaint();
delta--;
}
}
}
public void paintComponent(Graphics g) //In this case, this method is used to draw the tiles on a grid
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g; //create a new variable equal to the variable g converted to Graphics2D
for(int x = 0; x<100; x++)
{
for(int y = 0; y<100; y++)
{
g2.drawImage(sprite, x*16+cameraX, y*16+cameraY, 16, 16, null);
}
}
g2.dispose(); //clear the system recources used to paint objects
}
}
Любая помощь будет принята с благодарностью.
@Hovercraft Full Of Eels Извините за неудобства; Я отредактировал свой вопрос, включив в него минимальный воспроизводимый пример.
Спасибо, но также, что произошло, когда вы прокомментировали //g2.dispose();?
Когда это закомментировано, ошибка сохраняется.
1. Компоненты Swing по умолчанию имеют двойную буферизацию; 2. Swing не является потокобезопасным, вы никогда не должны обновлять пользовательский интерфейс или указывать, что пользовательский интерфейс зависит от вне контекста потока диспетчеризации событий.
В качестве общей рекомендации лучше использовать Swing Timer, а не поток, в противном случае вам придется изучить вопрос с помощью BufferStrategy.
Итак, я провел быстрый тест с вашим кодом, и, похоже, он работает нормально для меня — может быть, ваш delta слишком хорош — в моем тестировании вы в основном рисуете каждую миллисекунду, а из общего опыта и отзывов сообщества ~ 5 миллисекунд. это самая низкая возможная скорость перерисовки, учитывая все другие задействованные «накладные расходы», это «может» объяснить мерцание, ваша система может просто не справиться. Если вы нацелены на 60 кадров в секунду, то Thread.sleep(16) должен почти достичь этого.
@MadProgrammer Может быть, это мой редактор кода? Я использую Visual Studio Code, однако я обнаружил, что большинство разработчиков Java используют Eclipse, поэтому использование VSCode может быть причиной низкой производительности.
Я сомневаюсь в этом, скорее всего, это сочетание проблем с вашим оборудованием, драйверами и нарушениями потокобезопасности Swing API. Например, я почти уверен, что вы, вероятно, все равно увидите проблему, если экспортируете свое приложение в Jar и запускаете его напрямую. Дело в том, что вы делаете что-то не так (например, обновляете пользовательский интерфейс из-за пределов EDT) и, возможно, слишком часто перерисовываете. Я бы начал с теста Swing Timer (используя задержку примерно 16 миллисекунд) и посмотрел, куда это вас приведет. Если вы действительно хотите быть лицом к лицу с металлом, вам нужно искать BufferedStrategy
@MadProgrammer Я пробовал кое-что из того, что вы рекомендовали, но все равно не повезло. Сокращение кода для использования thread.sleep, чтобы убедиться, что я не перерисовывал часто, не помогло. Я также пытался обновить драйверы и экспортировать в виде jar на всякий случай, но это тоже ничего не дало. Я сомневаюсь, что это моя система, так как она может без проблем запускать другие игры. На данный момент я действительно не уверен, что еще я должен сделать, чтобы заставить это работать.
"Другие игры" - сделаны в Swing?
@MadProgrammer Сделано на Java. Я не уверен, используют ли они свинг или нет. Я не уверен, должен ли я отойти от свинга и как это сделать.




У меня работает, но Swing не является потокобезопасным. Это означает, что вам следует избегать обновления пользовательского интерфейса или любого состояния, на которое опирается пользовательский интерфейс, вне контекста потока диспетчеризации событий.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new GamePanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class GamePanel extends JPanel implements Runnable //class for the game screen
{
Thread gameThread; //the thread in which the game operates
BufferedImage sprite;
public int cameraY = -1400;
public int cameraX = -1400;
public GamePanel() throws IOException {
sprite = ImageIO.read(getClass().getResourceAsStream("/resources/Grass.png"));
gameThread = new Thread(this); //instantiate a new thread
gameThread.start(); //start the thread
}
@Override
public void addNotify() {
super.addNotify();
if (gameThread != null) {
return;
}
gameThread = new Thread(this);
gameThread.start();
}
@Override
public void removeNotify() {
super.removeNotify();
Thread reference = gameThread;
gameThread = null;
if (reference != null) {
try {
reference.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(480, 320);
}
@Override
public void run() //runs when thread is started
{
double drawInterval = 1000000000 / 60; //set the interval to 1000000000 divide by the fps (60)
double delta = 0;
long lastTime = System.nanoTime();
long currentTime;
while (gameThread != null) //repeat while gamethread is active
{
currentTime = System.nanoTime();
delta += (currentTime - lastTime) / drawInterval;
lastTime = currentTime;
if (delta >= 1) {
cameraY++;
cameraX++;
repaint();
delta--;
}
}
}
public void paintComponent(Graphics g) //In this case, this method is used to draw the tiles on a grid
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g; //create a new variable equal to the variable g converted to Graphics2D
for (int x = 0; x < 100; x++) {
for (int y = 0; y < 100; y++) {
g2.drawImage(sprite, x * 16 + cameraX, y * 16 + cameraY, 16, 16, this);
}
}
}
}
}
Также работает для меня, но вместо этого использует Swing Timer, что, как правило, было бы лучшим решением...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new GamePanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class GamePanel extends JPanel //class for the game screen
{
BufferedImage sprite;
public int cameraY = -1400;
public int cameraX = -1400;
private Timer timer;
public GamePanel() throws IOException {
sprite = ImageIO.read(getClass().getResourceAsStream("/resources/Grass.png"));
timer = new Timer(16, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
cameraY++;
cameraX++;
repaint();
}
});
}
@Override
public void addNotify() {
super.addNotify();
timer.start();
}
@Override
public void removeNotify() {
super.removeNotify();
timer.stop();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(480, 320);
}
public void paintComponent(Graphics g) //In this case, this method is used to draw the tiles on a grid
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g; //create a new variable equal to the variable g converted to Graphics2D
for (int x = 0; x < 100; x++) {
for (int y = 0; y < 100; y++) {
g2.drawImage(sprite, x * 16 + cameraX, y * 16 + cameraY, 16, 16, this);
}
}
}
}
}
Я сомневаюсь, что это моя система, так как она может без проблем запускать другие игры.
Ваш базовый код работает для меня, хотя я бы не рекомендовал вам создавать систему таким образом, он указывает пальцем на вашу систему. Это может быть ОС, у меня были проблемы с различными комбинациями оборудования/драйверов, которые делали странные вещи в некоторых системах, так что это нелегко сбрасывать со счетов.
Сделано на Яве
Swing — не единственная доступная графическая библиотека. Swing также использует пассивный движок перерисовки, и его процесс рисования, как правило, находится вне вашего контроля, поэтому он не совсем подходит для более сложных или интенсивных игр/анимации.
Вы также можете использовать BufferStrategy (и JavaDocs, у которого есть достойный пример), который предоставит вам самый низкий уровень доступа к оборудованию, которое Java предоставляет из коробки.
Вы также можете взглянуть на JavaFx или даже libGDX или LWJGL
Второй у меня работал. Большое спасибо за помощь!
Я бы предпочел, чтобы вы могли создать и опубликовать действительный минимальный воспроизводимый пример с вашим вопросом, но пункт об одной строке кода, которую мы видим:
g2.dispose(); //clear the system recources used to paint objectsВы никогда не должны удалять объект Graphics, который дает вам система, только тот, который вы сами создаете. В противном случае вы рискуете разорвать цепочку рисования, которая может использовать этот объект.