Skipping and Tearing in Java Animation

2019-07-07 05:16发布

问题:

the following code draws a square with two smaller square rotating inside it. whenever you click an arrow on the keyboard, the whole system will move in that direction. however i'm having some problems with the image tearing and at times skipping (its small but still there). i was wondering if anybody knew how i could fix these issues w/o massively altering the code.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import static java.awt.Color.*;

public class GUI extends JPanel implements ActionListener, KeyListener
{
    int x, y, x1, y1, x2, y2, changeX, changeY, changeX2, changeY2;
    JFrame frame;
    Runtime r;
    public static void main(String[] args)
    {
        new GUI();
    }
    public GUI()
    {
        try
        {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e)
        {
            e.printStackTrace();
        }

        setSize(1020,770);
        setBackground(WHITE);
        setOpaque(true);
        setVisible(true);

        x = 0;
        y = 0;
        x1 = 0;
        y1 = 0;
        x2 = 0;
        y2 = 0;
        changeX=1;
        changeY=0;
        changeX2=1;
        changeY2=0;
        r = Runtime.getRuntime();

        frame = new JFrame();
        frame.setSize(1020,819);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setJMenuBar(createMenuBar());
        frame.validate();
        frame.setBackground(WHITE);
        frame.addKeyListener(this);
        frame.setTitle("GUI");
        frame.setContentPane(this);
        frame.setVisible(true);
        frame.createBufferStrategy(2);

        Timer t = new Timer(100,this);
        t.setActionCommand("Draw");
        t.start();

        repaint();
    }
    public JMenuBar createMenuBar()
    {
        JMenuBar menuBar = new JMenuBar();

        JMenu fileMenu = new JMenu("File");
        JMenuItem save = new JMenuItem("Save");
        save.setMnemonic(KeyEvent.VK_S);
        save.setContentAreaFilled(false);
        save.setOpaque(false);
        save.addActionListener(this);
        JMenuItem load = new JMenuItem("Load");
        load.setMnemonic(KeyEvent.VK_L);
        load.setContentAreaFilled(false);
        load.setOpaque(false);
        load.addActionListener(this);
        JMenuItem quit = new JMenuItem("Quit");
        quit.setMnemonic(KeyEvent.VK_Q);
        quit.setContentAreaFilled(false);
        quit.setOpaque(false);
        quit.addActionListener(this);
        fileMenu.add(save);
        fileMenu.add(load);
        fileMenu.addSeparator();
        fileMenu.add(quit);
        fileMenu.setContentAreaFilled(false);
        fileMenu.setBorderPainted(false);
        fileMenu.setOpaque(false);

        JMenu editMenu = new JMenu("Edit");
        JMenuItem undo = new JMenuItem("Undo");
        undo.setMnemonic(KeyEvent.VK_U);
        undo.setContentAreaFilled(false);
        undo.setOpaque(false);
        undo.addActionListener(this);
        JMenuItem redo = new JMenuItem("Redo");
        redo.setMnemonic(KeyEvent.VK_R);
        redo.setContentAreaFilled(false);
        redo.setOpaque(false);
        redo.addActionListener(this);
        editMenu.add(undo);
        editMenu.add(redo);
        editMenu.setContentAreaFilled(false);
        editMenu.setBorderPainted(false);
        editMenu.setOpaque(false);

        JMenu helpMenu = new JMenu("Help");
        JMenuItem controls = new JMenuItem("Controls");
        controls.setMnemonic(KeyEvent.VK_C);
        controls.setContentAreaFilled(false);
        controls.setOpaque(false);
        controls.addActionListener(this);
        JMenuItem about = new JMenuItem("About");
        about.setMnemonic(KeyEvent.VK_A);
        about.setContentAreaFilled(false);
        about.setOpaque(false);
        about.addActionListener(this);
        helpMenu.add(controls);
        helpMenu.addSeparator();
        helpMenu.add(about);
        helpMenu.setContentAreaFilled(false);
        helpMenu.setBorderPainted(false);
        helpMenu.setOpaque(false);

        menuBar.add(fileMenu);
        menuBar.add(editMenu);
        menuBar.add(helpMenu);
        return menuBar;
    }
    public void paintComponent(Graphics g)
    {
        g.clearRect(0, 0, 1020, 770);
        g.setColor(BLACK);
        g.fillRect(x,y,100,100);
        g.setColor(RED);
        g.fillRect(x1,y1,50,50);
        g.setColor(BLUE);
        g.fillRect(x2,y2,25,25);
        g.dispose();
    }
    public void change()
    {
        if(x1>=x+50&&changeY==0&&changeX==1)
        {
            changeX=0;
            changeY=1;
        }
        else if(y1>=y+50&&changeX==0&&changeY==1)
        {
            changeX=-1;
            changeY=0;
        }
        else if(x1<=x&&changeX==-1&&changeY==0)
        {
            changeX=0;
            changeY=-1;
        }
        else if(y1<=y&&changeY==-1&&changeX==0)
        {
            changeX=1;
            changeY=0;
        }
        x1+=changeX*5;
        y1+=changeY*5;
    }
    public void change2()
    {
        if(x2>=x1+25&&changeY2==0&&changeX2==1)
        {
            changeX2=0;
            changeY2=1;
        }
        else if(y2>=y1+25&&changeX2==0&&changeY2==1)
        {
            changeX2=-1;
            changeY2=0;
        }
        else if(x2<=x1&&changeX2==-1&&changeY2==0)
        {
            changeX2=0;
            changeY2=-1;
        }
        else if(y2<=y1&&changeY2==-1&&changeX2==0)
        {
            changeX2=1;
            changeY2=0;
        }
        x2+=changeX2*2;
        y2+=changeY2*2;
    }
    public void actionPerformed(ActionEvent e)
    {
        if(e.getActionCommand().equalsIgnoreCase("Draw"))
        {
            r.runFinalization();
            r.gc();
            change();
            change2();
            repaint();
        }
    }
    public void keyPressed(KeyEvent e)
    {
        if(e.getKeyCode()==KeyEvent.VK_UP)
        {
            if(y-10>=0)
            {
                y-=10;
                y1-=10;
                y2-=10;
            }
        }
        if(e.getKeyCode()==KeyEvent.VK_DOWN)
        {
            if(y+110<=getHeight())
            {
                y+=10;
                y1+=10;
                y2+=10;
            }
        }
        if(e.getKeyCode()==KeyEvent.VK_LEFT)
        {
            if(x-10>=0)
            {
                x-=10;
                x1-=10;
                x2-=10;
            }
        }
        if(e.getKeyCode()==KeyEvent.VK_RIGHT)
        {
            if(x+110<=getWidth())
            {
                x+=10;
                x1+=10;
                x2+=10;
            }
        }
        repaint();
    }
    public void keyReleased(KeyEvent e)
    {
    }
    public void keyTyped(KeyEvent e)
    {
    }
}

回答1:

You are not constructing on the EDT. An instance of javax.swing.Timer makes this easy, as the action event handler executes on the EDT.

Addendum 1: You may decide you need double buffering, but first compare the code below to yours and see. If you go that route, you might look at this tutorial.

Addendum 2: The example below shows how to maintain an offscreen buffer, but it is sometimes easier to use the JPanel(boolean isDoubleBuffered) constructor for a similar effect.

import java.awt.event.KeyAdapter;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import static java.awt.Color.*;

/** @see http://stackoverflow.com/questions/2114455 */
public class GUI extends JPanel implements ActionListener {

    int x, y, x1, y1, x2, y2, changeY, changeY2;
    int changeX = 1; int changeX2 = 1;
    Timer t = new Timer(100, this);

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new GUI(true).display();
            }
        });
    }

    public GUI(boolean doubleBuffered) {
        super(doubleBuffered);
        this.setPreferredSize(new Dimension(320, 240));
    }

    private void display() {
        JFrame frame = new JFrame("GUI");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.addKeyListener(new KeyListener());
        frame.add(this);
        frame.pack();
        frame.setVisible(true);
        t.start();
    }

    @Override
    public void paintComponent(Graphics g) {
        g.setColor(WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setColor(BLACK);
        g.fillRect(x, y, 100, 100);
        g.setColor(RED);
        g.fillRect(x1, y1, 50, 50);
        g.setColor(BLUE);
        g.fillRect(x2, y2, 25, 25);
    }

    public void change() {
        if (x1 >= x + 50 && changeY == 0 && changeX == 1) {
            changeX = 0;
            changeY = 1;
        } else if (y1 >= y + 50 && changeX == 0 && changeY == 1) {
            changeX = -1;
            changeY = 0;
        } else if (x1 <= x && changeX == -1 && changeY == 0) {
            changeX = 0;
            changeY = -1;
        } else if (y1 <= y && changeY == -1 && changeX == 0) {
            changeX = 1;
            changeY = 0;
        }
        x1 += changeX * 5;
        y1 += changeY * 5;
    }

    public void change2() {
        if (x2 >= x1 + 25 && changeY2 == 0 && changeX2 == 1) {
            changeX2 = 0;
            changeY2 = 1;
        } else if (y2 >= y1 + 25 && changeX2 == 0 && changeY2 == 1) {
            changeX2 = -1;
            changeY2 = 0;
        } else if (x2 <= x1 && changeX2 == -1 && changeY2 == 0) {
            changeX2 = 0;
            changeY2 = -1;
        } else if (y2 <= y1 && changeY2 == -1 && changeX2 == 0) {
            changeX2 = 1;
            changeY2 = 0;
        }
        x2 += changeX2 * 2;
        y2 += changeY2 * 2;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        change();
        change2();
        repaint();
    }

    private class KeyListener extends KeyAdapter {

        @Override
        public void keyPressed(KeyEvent e) {
            int d = 5;
            if (e.getKeyCode() == KeyEvent.VK_UP) {
                if (y - d >= 0) {
                    y -= d;
                    y1 -= d;
                    y2 -= d;
                }
            }
            if (e.getKeyCode() == KeyEvent.VK_DOWN) {
                if (y + 100 + d <= getHeight()) {
                    y += d;
                    y1 += d;
                    y2 += d;
                }
            }
            if (e.getKeyCode() == KeyEvent.VK_LEFT) {
                if (x - d >= 0) {
                    x -= d;
                    x1 -= d;
                    x2 -= d;
                }
            }
            if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
                if (x + 100 + d <= getWidth()) {
                    x += d;
                    x1 += d;
                    x2 += d;
                }
            }
        }
    }
}


回答2:

I would take a look at double buffering. Basically, you draw to an offscreen graphics object and then draw the offscreen graphics object onto the JPanel's graphics object.

~Bolt