0

I am painting vehicle objects that I defined using the paintComponent(). Because the vehicles can move, I implement ActionListener and set a Timer() to trigger.

As a result, my vehicles can move. But it is kind of "shaking". When I keep resizing the window to call the paintComponent(), the movement becomes smooth. When I do not resize the window (not calling paintComponent), it gets skaking again. Why? How to fix it?

public class VehiclesComponent extends JComponent implements ActionListener{
    private Vehicle[] vehicles;
    private Timer timer;

    public VehiclesComponent(int n){
        vehicles = Vehicle.generateVehicle(n);
        timer = new Timer(5,this);
    } 

    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;

        for (int i=0; i<vehicles.length; i++) {
            vehicles[i].draw(g2);
        }

        // may change later
        timer.start();
    }

    @Override
    public void actionPerformed(ActionEvent e){

        //check collision in here
        for (Vehicle v : vehicles) {
            if (Vehicle.intersectsOther(v, vehicles)) {
                v.collisionSideEffect();
            }
        }

        //move all in here

        for (Vehicle v : vehicles ) {
            v.move();
        }

        repaint(); 
        //?? repaint slower than paintComponent
    }


} 
Fan
  • 217
  • 3
  • 14
  • `public void paintComponent(Graphics g){ ..` as mentioned around here **on a daily basis,** that should be `public void paintComponent(Graphics g){ super.paintComponent(g); ..` And 1) move `// may change later timer.start();` outside a method that we don't control when and how many times it is called. 2) Use a logical and consistent form of indenting code lines and blocks. The indentation is intended to make the flow of the code easier to follow! – Andrew Thompson Mar 03 '15 at 05:55
  • And it should remain `protected` – MadProgrammer Mar 03 '15 at 05:56
  • Start by taking a look at [Painting in AWT and Swing](http://www.oracle.com/technetwork/java/painting-140037.html)`. `repaint` makes a request to the `RepaintManager` which is responsible for scheduling paint events on the Event Queue. In order to improve performance, a `repaint` request may be consolidated into a single (or smaller number of) paint events. – MadProgrammer Mar 03 '15 at 05:57
  • Stop calling `timer.start();` inside your `paintComponent` method, this isn't going to help matters – MadProgrammer Mar 03 '15 at 05:58
  • Indentation has been improved. – Fan Mar 03 '15 at 06:15
  • @MadProgrammer What do you mean consolidate into a single events? – Fan Mar 03 '15 at 06:16
  • 1
    The number of paint events generated <= the number of calls to `repaint`... – MadProgrammer Mar 03 '15 at 06:18
  • 1
    Consider providing a [runnable example](https://stackoverflow.com/help/mcve) which demonstrates your problem. This is not a code dump, but an example of what you are doing which highlights the problem you are having. This will result in less confusion and better responses – MadProgrammer Mar 03 '15 at 06:36

1 Answers1

1

Start by taking a look at Painting in AWT and Swing. Remember, repaint is only a suggest made to the RepaintManager, the RepaintManager may choose to consolidate multiple repaint calls into a smaller number of actual paint events.

Make sure you are calling super.paintComponent, otherwise you will end up with no end of strange paint artifacts.

Don't, directly or indirectly, modify the state of the component or ant other components from within any paint method, this will result in a new repaint request been made, which could lead to a cycle of paint events which could consume your CPU cycles. This means, don't call timer.start()!

Without a runable example to go by, I hobbled this together. Now this is animating 10, 000 individual Vehicles (rectangles), so it's massively over kill, but it should provide the point...

Noise

(the gif is only running at 7fps, not your 200fps)

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new VehiclesComponent(10000));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class VehiclesComponent extends JComponent implements ActionListener {

        private Vehicle[] vehicles;
        private Timer timer;

        public VehiclesComponent(int n) {
            vehicles = Vehicle.generateVehicle(n, getPreferredSize());
            timer = new Timer(5, this);

            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

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

            for (int i = 0; i < vehicles.length; i++) {
                vehicles[i].draw(g2);
            }
        }

        @Override
        public void actionPerformed(ActionEvent e) {

            //check collision in here
//          for (Vehicle v : vehicles) {
//              if (Vehicle.intersectsOther(v, vehicles)) {
//                  v.collisionSideEffect();
//              }
//          }

        //move all in here
            for (Vehicle v : vehicles) {
                v.move(this.getSize());
            }

            repaint();
            //?? repaint slower than paintComponent
        }

    }

    public static class Vehicle {

        protected static final int SIZE = 5;
        protected static final Color[] COLORS = new Color[]{
            Color.BLACK,
            Color.BLUE,
            Color.CYAN,
            Color.DARK_GRAY,
            Color.GREEN,
            Color.MAGENTA,
            Color.ORANGE,
            Color.PINK,
            Color.RED,
            Color.WHITE,
            Color.YELLOW
        };

        private int x = 0;
        private int y = 0;

        private int xDelta;
        private int yDelta;

        private Shape car;
        private Color color;

        public static Vehicle[] generateVehicle(int count, Dimension bounds) {

            Vehicle[] vehicles = new Vehicle[count];
            for (int index = 0; index < vehicles.length; index++) {
                vehicles[index] = new Vehicle(bounds);
            }

            return vehicles;

        } 

        public Vehicle(Dimension size) {

            x = (int)(Math.random() * (size.width - SIZE));
            y = (int)(Math.random() * (size.height - SIZE));

            xDelta = (int)(Math.random() * 3) + 1;
            yDelta = (int)(Math.random() * 3) + 1;
            car = new Rectangle(SIZE, SIZE);

            color = COLORS[(int)(Math.random() * COLORS.length)];

        }

        public void move(Dimension size) {
            x += xDelta;
            y += yDelta;

            if (x < 0) {
                x = 0;
                xDelta *= -1;
            } else if (x + SIZE > size.width) {
                x = size.width - SIZE;
                xDelta *= -1;
            }
            if (y < 0) {
                y = 0;
                yDelta *= -1;
            } else if (y + SIZE > size.height) {
                y = size.height - SIZE;
                yDelta *= -1;
            }

        }

        public void draw(Graphics2D g2) {
            g2.translate(x, y);
            g2.setColor(color);
            g2.fill(car);
            g2.translate(-x, -y);
        }

    }

}

You could also take a look at this example which renders upwards of 4500 images in random directions and demonstrates some optimisation techniques.

You can also take a look at this example which is capable of animating both in direction and rotation, upwards of 10, 000 images

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • *"so it's massively over kill"* There's no kill like overkill. :) – Andrew Thompson Mar 03 '15 at 06:58
  • Although I see the point of doing only "few" modifications to the existing code (in order to provide an *answer*, and not just tell the asker that he is using the wrong approach), one should probably emphasize that **one should not abuse `Component`s as sprites**. A component is a component, and a sprite is a sprite. Painting 10000 simple rectangles (in an own component, in the `paintComponent` metehod) would be possible with far more than 7 fps... – Marco13 Mar 03 '15 at 09:28
  • @Marco13 The first two points are, call `super.paintComponent`, and don't call `timer.start` in the `paintComponent` method. The gif animation is at 7fps, hence the reason it looks a little "staggered", the example is capable of maxing out at 200fps. The first two points will make difference based on what the OP has provided. The OP has not provided any information with regards to what they are painting or how, so it's impossible to to provide more details. If you like I can post a link to an example which animates 4000-5000 random images as well... – MadProgrammer Mar 03 '15 at 09:57
  • @Marco13 And if I understand the comment correctly, as near as I can tell, the OP is a single component as the "paint" surface and is then rendering the vehicles in some other way (hence the `draw`) method. I'm hoping to encourage some more information out them at some point... – MadProgrammer Mar 03 '15 at 10:04