John M
John M

Reputation: 1496

jframe won't repaint at constant rate

I am trying to learn Java, coming from a C/assembly embedded systems background. After a few weeks of learning, I thought it would be fun to try and make a game, but I am having some problems with a JPanel being repainted at an inconsistent rate.

My "game" GUI consists of a single JFrame which contains a JPanel. As you can see, the main thread for the JFrame sleeps for 30 milliseconds and then updates "game" logic and redraws the JFrame and JPanel. Each time the JPanel is redrawn, I check that it took about 30 milliseconds. As you would expect, it never takes more than about 32 milliseconds between frame redraws.

In spite of the fact that the JPanel is definitely repainted every 30 milliseconds or so, The animation in it can be very jerky. On Ubuntu 14.10, it is extremely obvious, even though the time between calls to my JPanel's repaint() are still never more than 32ms apart.

The most vexing thing is that if I uncomment the lines

//this.resize(300 + a, 300);
//a = (a == 1)?(0):1;

in SimFrame.java, everything is perfectly smooth, which implies that my computer has the capability to update the JPanel at the rate I want.

Is there some way I can force the JFrame to update at the rate I want it to without doing the absurd .resize call?

Main.java

package jdemo;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class Main
{
    public static void main(String[] args)
    {
        SimFrame s = new SimFrame();
        Thread t = new Thread(s);
        t.start();
    }
}

Sim.java

package jdemo;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

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



public class Sim extends JPanel implements KeyListener
{
        /**
         * 
         */
        private static final long serialVersionUID = 1L;
        private int keys[] = new int[1024];

        double x = 100;
        double y = 100;
        double dx = 0;
        double dy = 0;
        double theta = 0.0;

        private int size = 0;
        private int dsize = 1;
        public Sim()
        {
                this.addKeyListener(this);
                this.setFocusable(true);
        }


        long prevmillis;
        public void paintComponent(Graphics g)
        {
                super.paintComponent(g);
                Graphics2D drawing = (Graphics2D)g;

                //clear the background.
                drawing.setColor(Color.WHITE);
                drawing.fillRect(0, 0, this.getWidth() - 1, this.getHeight() - 1);

                //System.out.printf("dt = %d\n", System.currentTimeMillis() - prevmillis);
                prevmillis = System.currentTimeMillis();

                drawing.setColor(Color.BLACK);
                drawing.drawRect(0, 0, size, size);

                drawing.setColor(Color.BLACK);
                int[] xpoints = {(int)(x + 10 * Math.cos(Math.toRadians(theta))),
                                                 (int)(x + 5 * Math.cos(Math.toRadians(theta - 150))),
                                                 (int)(x + 5 * Math.cos(Math.toRadians(theta + 150)))};
                int[] ypoints = {(int)(y + 10 * Math.sin(Math.toRadians(theta))),
                                                 (int)(y + 5 * Math.sin(Math.toRadians(theta - 150))),
                                                 (int)(y + 5 * Math.sin(Math.toRadians(theta + 150)))};
                drawing.drawPolygon(xpoints, ypoints, 3);
        }

        public void updateLogic()
        {
                if(keys[KeyEvent.VK_UP] == 1)
                {
                        size++;
                }
                else if(keys[KeyEvent.VK_DOWN] == 1)
                {
                        size--;
                }

                //update theta.
                if(keys[KeyEvent.VK_LEFT] == 1)
                {
                        theta += 5;
                }
                if(keys[KeyEvent.VK_RIGHT] == 1)
                {
                        theta -= 5;
                }
                if(theta > 360.1)
                {
                        theta -= 360;
                }
                if(theta < -0.1)
                {
                        theta += 360;
                }


                //update acceleration
                if(keys[KeyEvent.VK_SPACE] == 1)
                {
                        dx += 0.08* Math.cos(Math.toRadians(theta));
                        dy += 0.08 * Math.sin(Math.toRadians(theta));
                }

                dx *= 0.99;
                dy *= 0.99;
                //update position
                x = x + dx;
                y = y + dy;
                System.out.printf("%f, %f\n", dx, dy);


                //update size
                if(size > 150)
                {
                    dsize = -1;
                }
                if(size < 10)
                {
                    dsize = 1;
                }
                size += dsize;
        }

        @Override
        public void keyPressed(KeyEvent arg0)
        {
                // TODO Auto-generated method stub
                keys[arg0.getKeyCode()] = 1;
                System.out.printf("%d\n", arg0.getKeyCode());
        }

        @Override
        public void keyReleased(KeyEvent arg0)
        {
                // TODO Auto-generated method stub
                keys[arg0.getKeyCode()] = 0;
        }

        @Override
        public void keyTyped(KeyEvent arg0)
        {
                // TODO Auto-generated method stub

        }
}

SimFrame.java

package jdemo;


import jdemo.Sim;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class SimFrame extends JFrame implements Runnable
{
        private Sim s;
        public SimFrame()
        {
                this.s = new Sim();
                this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                this.setContentPane(this.s);
                this.pack();
                this.setLocationRelativeTo(null);
                this.setSize(200, 200);
        }

        @Override
        public void run()
        {
                int a = 0;
                this.setVisible(true);
                while(true)
                {
                        //repaint
                        s.updateLogic();
                        this.getContentPane().revalidate();
                        this.repaint();
                        //this.resize(300 + a, 300);
                        //a = (a == 1)?(0):1;

                        try
                        {
                                Thread.sleep(30);
                        }
                        catch(InterruptedException e)
                        {
                                System.out.printf("failed");
                                break;
                        }
                }
        }
}

Thank you very much.

Upvotes: 4

Views: 466

Answers (1)

user1803551
user1803551

Reputation: 13407

Try this and if it works I'll transform it into a true answer (I just can't know if it will work on your system better than your current code):

public class SimFrame extends JFrame {

    public SimFrame() {

        setContentPane(new Sim());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pack();
        setLocationRelativeTo(null);
        setVisible(true);
    }

    private class Sim extends JPanel {

        double x = 100;
        double y = 100;
        double dx = 0;
        double dy = 0;
        double theta = 0.0;
        private int size = 0;
        private int dsize = 1;

        public Sim() {

            addKeyListener(new Controller());
            setFocusable(true);
            setBackground(Color.WHITE);

            new Timer(30, new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {

                    dx *= 0.99;
                    dy *= 0.99;

                    // update position
                    x = x + dx;
                    y = y + dy;

                    // update size
                    if (size > 150)
                        dsize = -1;
                    if (size < 10)
                        dsize = 1;
                    size += dsize;

                    // update theta.
                    if (theta > 360.1) {
                        theta -= 360;
                    }
                    if (theta < -0.1) {
                        theta += 360;
                    }

                    repaint();
                }
            }).start();
        }

        @Override
        public Dimension getPreferredSize() {

            return new Dimension(200, 200);
        };

        @Override
        public void paintComponent(Graphics g) {

            super.paintComponent(g);
            Graphics2D drawing = (Graphics2D) g;

            drawing.setColor(Color.BLACK);
            drawing.drawRect(0, 0, size, size);

            drawing.setColor(Color.BLACK);
            int[] xpoints = {(int) (x + 10 * Math.cos(Math.toRadians(theta))), (int) (x + 5 * Math.cos(Math.toRadians(theta - 150))),
                                (int) (x + 5 * Math.cos(Math.toRadians(theta + 150)))};
            int[] ypoints = {(int) (y + 10 * Math.sin(Math.toRadians(theta))), (int) (y + 5 * Math.sin(Math.toRadians(theta - 150))),
                                (int) (y + 5 * Math.sin(Math.toRadians(theta + 150)))};
            drawing.drawPolygon(xpoints, ypoints, 3);
        }

        private class Controller extends KeyAdapter {

            @Override
            public void keyPressed(KeyEvent evt) {

                switch (evt.getKeyCode()) {
                    case KeyEvent.VK_UP:
                        size++;
                        break;
                    case KeyEvent.VK_DOWN:
                        size--;
                        break;
                    case KeyEvent.VK_LEFT:
                        theta += 5;
                        break;
                    case KeyEvent.VK_RIGHT:
                        theta -= 5;
                        break;
                    case KeyEvent.VK_SPACE:
                        dx += 0.08 * Math.cos(Math.toRadians(theta));
                        dy += 0.08 * Math.sin(Math.toRadians(theta));
                        break;
                }
            }
        }
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {

                new SimFrame();
            }
        });
    }
}

And yes, I know there's the OS's delay on holding a key, we'll get to it if it works.


Edit:

Simpler animation:

public class CopyOfSimFrame extends JFrame {

    public CopyOfSimFrame() {

        setContentPane(new Sim());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pack();
        setLocationRelativeTo(null);
        setVisible(true);
    }

    private class Sim extends JPanel {

        private int size = 0;
        private int dsize = 1;

        public Sim() {

            setBackground(Color.WHITE);

            new Timer(30, new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {

                    // update size
                    if (size >= 150)
                        dsize = -1;
                    if (size <= 0)
                        dsize = 1;
                    size += dsize;

                    repaint();
                }
            }).start();
        }

        @Override
        public Dimension getPreferredSize() {

            return new Dimension(150, 150);
        };

        @Override
        public void paintComponent(Graphics g) {

            super.paintComponent(g);
            g.setColor(Color.BLACK);
            g.drawRect(0, 0, size, size);
        }
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {

                new CopyOfSimFrame();
            }
        });
    }
}

Upvotes: 1

Related Questions