Search code examples
javaswinganimationgraphicsevent-dispatch-thread

Animation: objects hidden while moving (JAVA, GRAPHICS, ANIMATION)


I am build a graph, with a while circle moving along a line, and stoping over for a few seconds at 3 points of the path. I have managed to do it, however, it does not display the circle moving, it displays only when the circle stops... I can't understand why.

Many thanks in advance for your help

Here is my code:

public class Robot0 extends JFrame implements ActionListener {

    public Robot0(String nom, int larg, int haut) {
        setTitle(nom); 
        setSize(larg, haut); 
        setLocationRelativeTo(null); 
        setResizable(false); 
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);                     
        setVisible(true); 
    }

    Timer tm = new Timer(10, this);

    private int posX = 0; 
    private int posY = 0;
    private int velX = 1;

    public int getPosX() {
        return posX;
    }

    public void setPosX(int posX) {
        this.posX = posX;
    }

    public int getPosY() {
        return posY;
    }

    public void setPosY(int posY) {
        this.posY = posY;
    }

    public void paint(Graphics g) {
        super.paint(g);
        g.setColor(Color.BLACK);
        // Draw the pathway 
        int xt[] = { 50, 50, 250, 250, 350, 350 };
        int yt[] = { 50, 150, 150, 50, 50, 150 };
        g.drawPolyline(xt, yt, 6);

        // On the pathway, draw 3 squares (the 3 rooms)
        g.setColor(Color.GREEN);
        g.drawRect(35, 135, 30, 30);
        g.drawRect(235, 35, 30, 30);
        g.drawRect(335, 135, 30, 30);

        g.setColor(Color.WHITE);
        g.fillOval(40 + posX, 40 + posY, 20, 20);

        g.setColor(Color.RED);
        g.drawLine(45 + posX, 50 + posY, 55 + posX, 50 + posY);
        g.drawLine(50 + posX, 45 + posY, 50 + posX, 55 + posY);

        tm.start();
    }

    int segment = 0;

    public void actionPerformed(ActionEvent e) {

        // move along the 1st segment
        if (posY < 100 && segment == 0) {
            setPosY(posY + velX);
        }

        if (posY == 100 && posX == 0) {
            segment = 1;
            try {
                Thread.sleep(15000);
            } catch (InterruptedException ex) {
                Logger.getLogger(Robot0.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        // move along the second segment
        if (posX <= 200 && segment == 1) {
            setPosX(posX + velX);
        }

        if (posX == 200 && posY == 100) {
            segment = 2;
        }

        // move along the third segment
        if (posY > 0 && segment == 2) {
            setPosY(posY - velX);
        }

        if (posX == 200 && posY == 0) {
            segment = 3;
            try {
                Thread.sleep(15000);
            } catch (InterruptedException ex) {
                Logger.getLogger(Robot0.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        // move along the fourth segment
        if (posX < 300 && segment == 3) {
            setPosX(posX + velX);
        }

        if (posX == 300 && posY == 0) {
            segment = 4;
        }

        // move along the fifth segment
        if (posY < 100 && segment == 4) {
            setPosY(posY + velX);
        }

        if (posX == 300 && posY == 100) {
            segment = 6;
            try {
                Thread.sleep(15000);
            } catch (InterruptedException ex) {
                Logger.getLogger(Robot0.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        repaint();
    }

// Build the Panel
    public static void main(String[] args) {
        Robot0 r = new Robot0("Robot0", 800, 600);
    }

}

Solution

  • Swing is a single Thread library. All painting tasks are executed in the Event Dispatcher Thread (EDT).
    As commented by Andrew Thompson, running long processes (such as sleep) on the EDT makes keeps this thread busy, so it does not do other things like updating the gui.
    The gui becomes unresponsive (freezes). So the first thing to do is to remove all sleeping.
    To park at each stop, use a second timer:

    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JFrame;
    import javax.swing.SwingUtilities;
    import javax.swing.Timer;
    
    public class Robot0 extends JFrame implements ActionListener {
    
        private static final int PARKING_TIME = 15000;
        Timer moveTimer , waitTimer;
        private boolean isParking = false;
    
        public Robot0(String nom, int larg, int haut) {
            moveTimer = new Timer(10, this);
            waitTimer = new Timer(PARKING_TIME, e-> isParking = false);
            waitTimer.setRepeats(false);
    
            setTitle(nom);
            setSize(larg, haut);
            setLocationRelativeTo(null);
            setResizable(false);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setVisible(true);
        }
    
        private int posX = 0;
        private int posY = 0;
        private final int velX = 1;
    
        public int getPosX() {
            return posX;
        }
    
        public void setPosX(int posX) {
            this.posX = posX;
        }
    
        public int getPosY() {
            return posY;
        }
    
        public void setPosY(int posY) {
            this.posY = posY;
        }
    
        @Override
        public void paint(Graphics g) {
            super.paint(g);
            g.setColor(Color.BLACK);
            // Draw the pathway
            int xt[] = { 50, 50, 250, 250, 350, 350 };
            int yt[] = { 50, 150, 150, 50, 50, 150 };
            g.drawPolyline(xt, yt, 6);
    
            // On the pathway, draw 3 squares (the 3 rooms)
            g.setColor(Color.GREEN);
            g.drawRect(35, 135, 30, 30);
            g.drawRect(235, 35, 30, 30);
            g.drawRect(335, 135, 30, 30);
    
            g.setColor(Color.WHITE);
            g.fillOval(40 + posX, 40 + posY, 20, 20);
    
            g.setColor(Color.RED);
            g.drawLine(45 + posX, 50 + posY, 55 + posX, 50 + posY);
            g.drawLine(50 + posX, 45 + posY, 50 + posX, 55 + posY);
    
            moveTimer.start();
        }
    
        int segment = 0;
    
        @Override
        public void actionPerformed(ActionEvent e) {
    
            if(isParking) return; //execute only when not parking 
    
            // move along the 1st segment
            if (posY < 100 && segment == 0) {
                setPosY(posY + velX);
            }
    
            if (posY == 100 && posX == 0 && segment != 1) { //!=1 so it will not be invoked again 
                segment = 1;
                isParking = true; //flag that robot is parking 
                waitTimer.start();
                return;
            }
    
            // move along the second segment
            if (posX <= 200 && segment == 1) {
                setPosX(posX + velX);
            }
    
            if (posX == 200 && posY == 100) {
                segment = 2;
            }
    
            // move along the third segment
            if (posY > 0 && segment == 2) {
                setPosY(posY - velX);
            }
    
            if (posX == 200 && posY == 0 && segment !=3) {
                segment = 3;
                isParking = true;
                waitTimer.start();
                return;
            }
    
            // move along the fourth segment
            if (posX < 300 && segment == 3) {
                setPosX(posX + velX);
            }
    
            if (posX == 300 && posY == 0) {
                segment = 4;
            }
    
            // move along the fifth segment
            if (posY < 100 && segment == 4) {
                setPosY(posY + velX);
            }
    
            if (posX == 300 && posY == 100 && segment !=6) {
                segment = 6;
                isParking = true;
                waitTimer.start();
                return;
            }
            repaint();
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(()->new Robot0("Robot0", 800, 600));
        }
    }
    

    TODO:

    1. Implement custom painting on JPanel.
    2. Simplify actionPerformed logic

    Edit: the following is an implementation with some improvements not necessarily related to question asked:

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    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 Robot0 extends JFrame {
    
        public Robot0(String nom) {
            setTitle(nom);
            setLocationRelativeTo(null);
            setResizable(false);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            add(new Floor());
            pack();
            setVisible(true);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(()->new Robot0("Robot0"));
        }
    }
    
    class Floor extends JPanel implements ActionListener{
    
        private static final int PARKING_TIME = 5000, REPAINT_TIME = 10, W = 400, H = 200;
        private static final int ROOM_SIZE = 30, ROBOT_SIZE = 20, CROSS_SIZE = 10;
    
        private final Timer moveTimer , waitTimer;
        private boolean isParking = false;
    
        private int posX = 0, posY = 0;
        private final int velX = 1;
    
        // pathway
        private static final int PATH_X[] = { 50,  50, 250, 250, 350,  350 };
        private static final int PATH_Y[] = { 50, 150, 150,  50,  50,  150 };
        //rooms
        private static final Point[] ROOM_CENTERS = {new Point(PATH_X[1],PATH_Y[1]),
                                                     new Point(PATH_X[3],PATH_Y[3]),
                                                     new Point(PATH_X[5],PATH_Y[5]) };
    
        Floor() {
            moveTimer = new Timer(REPAINT_TIME, this);
            waitTimer = new Timer(PARKING_TIME, e-> isParking = false);
            waitTimer.setRepeats(false);
            posX = PATH_X[0]; posY = PATH_Y[0];
            setPreferredSize(new Dimension(W, H));
            moveTimer.start(); //no need to restart with every paint
        }
    
        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.BLACK);
    
            g.drawPolyline(PATH_X, PATH_Y, 6);
    
            // draw rooms
            g.setColor(Color.GREEN);
            for(Point center : ROOM_CENTERS){
                drawSquareAround(center, g);
            }
    
            //robot
            g.setColor(Color.WHITE);
            g.fillOval( posX - ROBOT_SIZE/2 , posY - ROBOT_SIZE/2 , ROBOT_SIZE, ROBOT_SIZE);
    
            //cross
            g.setColor(Color.RED);
            g.drawLine(posX - CROSS_SIZE/2, posY, posX + CROSS_SIZE/2, posY);
            g.drawLine(posX, posY - CROSS_SIZE/2, posX,  posY + CROSS_SIZE/2);
        }
    
    
        private void drawSquareAround(Point center, Graphics g) {
            g.drawRect(center.x - ROOM_SIZE/2, center.y - ROOM_SIZE/2, ROOM_SIZE, ROOM_SIZE);
        }
    
        @Override
        public void actionPerformed(ActionEvent e) {
    
            if(isParking) return; //execute only when not parking
    
            if (posX <= PATH_X[0]  &&  posY < PATH_Y[1]) {// move along the 1st segment
                setPosY(posY + velX);
            }else if (posX < PATH_X[2] && posY == PATH_Y[1]) { //move along the second segment
                setPosX(posX + velX);
            }else  if (posX == PATH_X[2] && posY > PATH_Y[3]) { //move along the third segment
                setPosY(posY - velX);
            }else if (posY == PATH_Y[3] && posX < PATH_X[4]) {// move along the fourth segment
                    setPosX(posX + velX);
            }else if (posX == PATH_X[4] && posY < PATH_Y[5]){// move along the fifth segment
                setPosY(posY + velX);
            }else {
                moveTimer.stop(); //move finished, stop repainting
                return;
            }
    
            //park if at room center
            if(isRoomCeter()){
                park();
            }
            repaint();
        }
    
        private void park() {
          isParking = true; //flag that robot is parking
          waitTimer.start();
        }
    
        private boolean isRoomCeter() {
            for (Point center : ROOM_CENTERS){
                if(posX == center.x && posY == center.y) return true;
            }
            return false;
        }
    
        public int getPosX() {
            return posX;
        }
    
        public void setPosX(int posX) {
            this.posX = posX;
        }
    
        public int getPosY() {
            return posY;
        }
    
        public void setPosY(int posY) {
            this.posY = posY;
        }
    }