1

To be honest, I'm not to sure how to describe my problem, you can see it here. In that video of my code running, it is most prominent in the first few seconds.

Occasionally and randomly I am getting a weird flickery/stuttery/laggy glitch in my moving pipe across the screen, I've been trying to sort it out for 3 days now and I truly have no idea why it is occurring.. I have tested my code on other computers and the same happens! As this has been happening I have remained at 60fps.

Does anyone know why this would be occurring...?

My code: (I know this code is bad, its a rushed example) It will probably take a few tries to experience the same bug that I am facing......

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;

public class Test extends Canvas implements Runnable {
    private static final long serialVersionUID = 1L;

    private static int width = 300 - 80;
    private static int height = 168;
    private static int scale = 3;
    public static String title = "Test";

    private Thread thread;
    private JFrame frame;
    private boolean running = false;

    private BufferedImage image;

    public Test() {
        setPreferredSize(new Dimension(width * scale, height * scale));
        frame = new JFrame();
        image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    }

    public static int getWindowWidth() {
        return width * scale;
    }

    public static int getWindowHeight() {
        return height * scale;
    }

    public synchronized void start() {
        running = true;
        thread = new Thread(this);
        thread.start();
    }

    public synchronized void stop() {
        running = false;
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void run() {
        long lastTime = System.nanoTime();
        long timer = System.currentTimeMillis();
        final double ns = 1000000000.0 / 60.0;
        double delta = 0;
        int frames = 0;
        int updates = 0;
        requestFocus();
        while (running) {
            long now = System.nanoTime();
            delta += (now - lastTime) / ns;
            lastTime = now;
            while (delta >= 1) {
                update();
                updates++;
                delta--;
                render();
                frames++;
            }

            if (System.currentTimeMillis() - timer > 1000) {
                timer += 1000;
                System.out.println(updates + " tps, " + frames + " fps");
                updates = 0;
                frames = 0;
            }
        }
        stop();
    }
    
    int x = width;
    
    static BufferedImage img;
    
    static {
        try {
            img = ImageIO.read(Test.class.getResource("/pipe.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void update() {
        if (x + 30 <= -1) x = width;
        else x-=2;
    }
    
    public void draw(Graphics2D g2)
    {
        g2.setColor(Color.red);
        g2.drawImage(img, x, 10, 30, 90, null);
    }

    public void render() {
        BufferStrategy bs = getBufferStrategy();
        if (bs == null) {
            createBufferStrategy(3);
            return;
        }
        
        drawToImage();
        
        Graphics g = bs.getDrawGraphics();
        g.setColor(new Color(0xff00ff));
        g.fillRect(0,  0, getWidth(), getHeight());
        g.drawImage(image, 0, 0, width * scale, height * scale, null);

        g.dispose();
        bs.show();
    }
    
    public void drawToImage()
    {
        Graphics2D g2 = (Graphics2D) image.getGraphics();
        g2.clearRect(0, 0, width, height);
        draw(g2);
        g2.dispose();
    }
    
    public static void main(String[] args) {
        Test game = new Test();
        game.frame.setResizable(false);
        game.frame.setTitle(Test.title);
        game.frame.add(game);
        game.frame.pack();
        game.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        game.frame.setLocationRelativeTo(null);
        game.frame.setVisible(true);

        game.start();
    }

}

EDIT: Paint component code that produces same result:

import javax.swing.*;
import java.awt.*;

public class GamePanel extends JPanel implements Runnable
{
    private int WIDTH = 200 * 4;
    private int HEIGHT = 150 * 4;

    int playerX = 30;
    int playerY = 30;

    Thread gameThread;

    public GamePanel()
    {
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setBackground(Color.BLACK);
        setDoubleBuffered(true);
    }

    public void startGameThread()
    {
        this.gameThread = new Thread(this);
        this.gameThread.start();
    }

    public void update()
    {
        this.playerX+= 5;
    }

    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D) g;

        g2.setColor(Color.WHITE);
        g2.fillRect(playerX, playerY, 32, 32);

        g2.dispose();
    }

    @Override
    public void run()
    {
        double drawInterval = 1000000000/60;
        double delta = 0;
        long lastTime = System.nanoTime();
        long currentTime;
        long timer = 0;
        int drawCount = 0;

        while (gameThread != null)
        {
            currentTime = System.nanoTime();

            delta += (currentTime - lastTime) / drawInterval;
            timer += (currentTime - lastTime);
            lastTime = currentTime;

            if (delta >= 1)
            {
                update();
                repaint();
                drawCount++;
                delta--;
            }

            if (timer >= 1000000000)
            {
                System.out.println("FPS: " + drawCount);
                drawCount = 0;
                timer = 0;
            }
        }
    }

    public static void main(String[] args)
    {
        System.setProperty("sun.java2d.opengl", "true");

        JFrame window = new JFrame();
        window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        window.setResizable(false);
        window.setTitle("Test");

        GamePanel gamePanel = new GamePanel();
        window.add(gamePanel);

        window.pack();
        window.setVisible(true);

        gamePanel.startGameThread();
    }
}
toadless
  • 147
  • 10
  • Oracle has a helpful tutorial, [Creating a GUI With Swing](https://docs.oracle.com/javase/tutorial/uiswing/index.html). Skip the Learning Swing with the NetBeans IDE section. Pay particular attention to the [Performing Custom Painting](https://docs.oracle.com/javase/tutorial/uiswing/painting/index.html) section. – Gilbert Le Blanc Aug 21 '22 at 11:14
  • 1
    That doesn't help me, I have already tried this with paint component and it still gives me the same result, also there is nothing in the troubleshooting area that helps me in that article. – toadless Aug 21 '22 at 11:34
  • 1
    Sorry, but I don't see a drawing `JPanel` or a Swing `Timer` in your code. Put something together using correct principles and you might get more help. – Gilbert Le Blanc Aug 21 '22 at 12:15
  • this looks like you are updating a component off the EDT - do you? That would be a _no-no-never_ which might lead to all types of unexpected/undeterministic behaviors – kleopatra Aug 21 '22 at 12:20
  • 1
    I have just tried to use `SwingUtils#envokeLater` so that I can run my rendering code on the EDT, and it made no difference... – toadless Aug 21 '22 at 16:30
  • 1
    @GilbertLeBlanc I have posted some refactored code that used "drawing JPanel" and still produces the same problem... – toadless Aug 21 '22 at 16:31

1 Answers1

0

Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section.

I rearranged your code. The motion seems pretty smooth to me when I run the modified code.

When I create a Swing GUI, I use the model-view-controller pattern. This pattern allows me to separate my concerns and focus on one part of the application at a time.

I created an application model using a plain Java getter/setter class to hold the player position and the size of the playing field.

All Swing applications must start with a call to the SwingUtilities invokeLater method. This method ensures that all Swing components are created and executed on the Event Dispatch Thread.

Your biggest mistake was disposing of the Graphics instance in your paintComponent method. Never dispose of a Graphics instance unless you create it.

I used a Swing Timer rather than your complicated timing code. Pick a frame rate and stick with it. Most monitors can't refresh any faster than 60 frames per second.

I modified your update code so the player rectangle moves back and forth.

Here's the complete runnable code. I made all the additional classes inner classes so I could post the code as one block.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class ExampleGame implements Runnable {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new ExampleGame());
    }

    private final GameModel model;

    private final GamePanel gamePanel;

    public ExampleGame() {
        this.model = new GameModel();
        this.gamePanel = new GamePanel(model);
    }

    @Override
    public void run() {
        System.setProperty("sun.java2d.opengl", "true");

        JFrame window = new JFrame();
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//        window.setResizable(false);
        window.setTitle("Test");

        window.add(gamePanel, BorderLayout.CENTER);

        window.pack();
        window.setLocationByPlatform(true);
        window.setVisible(true);

        Timer timer = new Timer(20, new PlayerListener(this, model));
        timer.start();
    }

    public void repaint() {
        gamePanel.repaint();
    }

    public class GamePanel extends JPanel {

        private static final long serialVersionUID = 1L;

        private final GameModel model;

        public GamePanel(GameModel model) {
            this.model = model;
            setPreferredSize(model.getPlayingField());
            setBackground(Color.BLACK);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2 = (Graphics2D) g;
            g2.setColor(Color.WHITE);
            Point point = model.getPlayerePosition();
            g2.fillRect(point.x, point.y, 32, 32);
        }

    }

    public class PlayerListener implements ActionListener {

        private int increment;

        private final ExampleGame view;

        private final GameModel model;

        public PlayerListener(ExampleGame view, GameModel model) {
            this.view = view;
            this.model = model;
            this.increment = 5;
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            update();
            view.repaint();
        }

        public void update() {
            Dimension d = model.getPlayingField();
            Point point = model.getPlayerePosition();
            point.x += increment;
            if (point.x > (d.width - 64)) {
                increment = -5;
                point.x += increment;
            }
            if (point.x < 32) {
                increment = 5;
                point.x += increment;
            }

            model.setPlayerePosition(point);
        }

    }

    public class GameModel {

        private final Dimension playingField;

        private Point playerePosition;

        public GameModel() {
            this.playingField = new Dimension(800, 600);
            this.playerePosition = new Point(32, 32);
        }

        public Point getPlayerePosition() {
            return playerePosition;
        }

        public void setPlayerePosition(Point playerePosition) {
            this.playerePosition = playerePosition;
        }

        public Dimension getPlayingField() {
            return playingField;
        }

    }

}
Gilbert Le Blanc
  • 50,182
  • 6
  • 67
  • 111