Плитка Java мерцает

Всякий раз, когда я перемещаю камеру в 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
    }

}

Любая помощь будет принята с благодарностью.

Я бы предпочел, чтобы вы могли создать и опубликовать действительный минимальный воспроизводимый пример с вашим вопросом, но пункт об одной строке кода, которую мы видим: g2.dispose(); //clear the system recources used to paint objects Вы никогда не должны удалять объект Graphics, который дает вам система, только тот, который вы сами создаете. В противном случае вы рискуете разорвать цепочку рисования, которая может использовать этот объект.

Hovercraft Full Of Eels 14.04.2023 21:53

@Hovercraft Full Of Eels Извините за неудобства; Я отредактировал свой вопрос, включив в него минимальный воспроизводимый пример.

Oliver Langford 14.04.2023 22:56

Спасибо, но также, что произошло, когда вы прокомментировали //g2.dispose();?

Hovercraft Full Of Eels 14.04.2023 23:02

Когда это закомментировано, ошибка сохраняется.

Oliver Langford 14.04.2023 23:09

1. Компоненты Swing по умолчанию имеют двойную буферизацию; 2. Swing не является потокобезопасным, вы никогда не должны обновлять пользовательский интерфейс или указывать, что пользовательский интерфейс зависит от вне контекста потока диспетчеризации событий.

MadProgrammer 15.04.2023 01:00

В качестве общей рекомендации лучше использовать Swing Timer, а не поток, в противном случае вам придется изучить вопрос с помощью BufferStrategy.

MadProgrammer 15.04.2023 01:01

Итак, я провел быстрый тест с вашим кодом, и, похоже, он работает нормально для меня — может быть, ваш delta слишком хорош — в моем тестировании вы в основном рисуете каждую миллисекунду, а из общего опыта и отзывов сообщества ~ 5 миллисекунд. это самая низкая возможная скорость перерисовки, учитывая все другие задействованные «накладные расходы», это «может» объяснить мерцание, ваша система может просто не справиться. Если вы нацелены на 60 кадров в секунду, то Thread.sleep(16) должен почти достичь этого.

MadProgrammer 15.04.2023 01:24

@MadProgrammer Может быть, это мой редактор кода? Я использую Visual Studio Code, однако я обнаружил, что большинство разработчиков Java используют Eclipse, поэтому использование VSCode может быть причиной низкой производительности.

Oliver Langford 15.04.2023 01:35

Я сомневаюсь в этом, скорее всего, это сочетание проблем с вашим оборудованием, драйверами и нарушениями потокобезопасности Swing API. Например, я почти уверен, что вы, вероятно, все равно увидите проблему, если экспортируете свое приложение в Jar и запускаете его напрямую. Дело в том, что вы делаете что-то не так (например, обновляете пользовательский интерфейс из-за пределов EDT) и, возможно, слишком часто перерисовываете. Я бы начал с теста Swing Timer (используя задержку примерно 16 миллисекунд) и посмотрел, куда это вас приведет. Если вы действительно хотите быть лицом к лицу с металлом, вам нужно искать BufferedStrategy

MadProgrammer 15.04.2023 01:41

@MadProgrammer Я пробовал кое-что из того, что вы рекомендовали, но все равно не повезло. Сокращение кода для использования thread.sleep, чтобы убедиться, что я не перерисовывал часто, не помогло. Я также пытался обновить драйверы и экспортировать в виде jar на всякий случай, но это тоже ничего не дало. Я сомневаюсь, что это моя система, так как она может без проблем запускать другие игры. На данный момент я действительно не уверен, что еще я должен сделать, чтобы заставить это работать.

Oliver Langford 15.04.2023 03:07

"Другие игры" - сделаны в Swing?

MadProgrammer 15.04.2023 03:59

@MadProgrammer Сделано на Java. Я не уверен, используют ли они свинг или нет. Я не уверен, должен ли я отойти от свинга и как это сделать.

Oliver Langford 15.04.2023 04:01
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
12
64
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Этот...

У меня работает, но 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

Второй у меня работал. Большое спасибо за помощь!

Oliver Langford 15.04.2023 04:33

Другие вопросы по теме