Swing animation running extremely slow

前端 未结 2 1680
轮回少年
轮回少年 2020-11-22 03:28

I have a problem with my current animation that I\'m running using Java Swing. It is a discrete event simulation and the text based simulation is working fine, I\'m just hav

2条回答
  •  醉话见心
    2020-11-22 03:50

    I couldn't resist...

    enter image description here

    I got 500 cars running on the screen with little slow down (it wasn't the fastest...about 200-300 was pretty good...

    This uses panels to represent each vehicle. If you want to get better performance, your probably need to look at using a backing buffer of some kind.

    public class TestAnimation10 {
    
        public static void main(String[] args) {
            new TestAnimation10();
        }
    
        public TestAnimation10() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (Exception ex) {
                    }
    
                    final TrackPane trackPane = new TrackPane();
                    JSlider slider = new JSlider(1, 500);
                    slider.addChangeListener(new ChangeListener() {
                        @Override
                        public void stateChanged(ChangeEvent e) {
                            trackPane.setCongestion(((JSlider)e.getSource()).getValue());
                        }
                    });
                    slider.setValue(5);
    
                    JFrame frame = new JFrame("Test");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setLayout(new BorderLayout());
                    frame.add(trackPane);
                    frame.add(slider, BorderLayout.SOUTH);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
    
                }
            });
        }
    
        public class TrackPane extends JPanel {
    
            private List cars;
            private int maxCars = 1;
    
            private List points;
    
            private Ellipse2D areaOfEffect;
    
            public TrackPane() {
    
                points = new ArrayList<>(25);
    
                cars = new ArrayList<>(25);
                setLayout(null);
    
                Timer timer = new Timer(40, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
    
                        Rectangle bounds = areaOfEffect.getBounds();
                        List tmp = new ArrayList<>(cars);
                        for (Car car : tmp) {
                            car.move();
                            if (!bounds.intersects(car.getBounds())) {
                                remove(car);
                                cars.remove(car);
                            }
                        }
                        updatePool();
                        repaint();
                    }
                });
    
                timer.setRepeats(true);
                timer.setCoalesce(true);
                timer.start();
    
                updateAreaOfEffect();
            }
    
            protected void updateAreaOfEffect() {
                double radius = Math.max(getWidth(), getHeight()) * 1.5d;
                double x = (getWidth() - radius) / 2d;
                double y = (getHeight() - radius) / 2d;
                areaOfEffect = new Ellipse2D.Double(x, y, radius, radius);
            }
    
            @Override
            public void invalidate() {
                super.invalidate();
                updateAreaOfEffect();
            }
    
            protected void updatePool() {
                while (cars.size() < maxCars) {
    //            if (cars.size() < maxCars) {
                    Car car = new Car();
                    double direction = car.getDirection();
                    double startAngle = direction - 180;
    
                    double radius = areaOfEffect.getWidth();
                    Point2D startPoint = getPointAt(radius, startAngle);
    
                    int cx = getWidth() / 2;
                    int cy = getHeight() / 2;
    
                    double x = cx + (startPoint.getX() - car.getWidth() / 2);
                    double y = cy + (startPoint.getY() - car.getHeight() / 2);
                    car.setLocation((int)x, (int)y);
    
                    Point2D targetPoint = getPointAt(radius, direction);
    
                    points.add(new Point2D[]{startPoint, targetPoint});
    
                    add(car);
    
                    cars.add(car);
                }
            }
    
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                Font font = g.getFont();
                font = font.deriveFont(Font.BOLD, 48f);
                FontMetrics fm = g.getFontMetrics(font);
                g.setFont(font);
                g.setColor(Color.RED);
                String text = Integer.toString(maxCars);
                int x = getWidth() - fm.stringWidth(text);
                int y = getHeight() - fm.getHeight() + fm.getAscent();
                g.drawString(text, x, y);
                text = Integer.toString(getComponentCount());
                x = getWidth() - fm.stringWidth(text);
                y -= fm.getHeight();
                g.drawString(text, x, y);
                text = Integer.toString(cars.size());
                x = getWidth() - fm.stringWidth(text);
                y -= fm.getHeight();
                g.drawString(text, x, y);
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(400, 400);
            }
    
            public void setCongestion(int value) {
                maxCars = value;
            }
        }
    
        protected static Point2D getPointAt(double radius, double angle) {
    
            double x = Math.round(radius / 2d);
            double y = Math.round(radius / 2d);
    
            double rads = Math.toRadians(-angle);
    
            double fullLength = Math.round((radius / 2d));
    
            double xPosy = (Math.cos(rads) * fullLength);
            double yPosy = (Math.sin(rads) * fullLength);
    
            return new Point2D.Double(xPosy, yPosy);
    
        }
    
        public class Car extends JPanel {
    
            private double direction;
            private double speed;
            private BufferedImage background;
    
            public Car() {
                setOpaque(false);
                direction = Math.random() * 360;
                speed = 5 + (Math.random() * 10);
                int image = 1 + (int) Math.round(Math.random() * 5);
                try {
                    String name = "/Car0" + image + ".png";
                    background = ImageIO.read(getClass().getResource(name));
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                setSize(getPreferredSize());
    //            setBorder(new LineBorder(Color.RED));
            }
    
            public void setDirection(double direction) {
                this.direction = direction;
                revalidate();
                repaint();
            }
    
            public double getDirection() {
                return direction;
            }
    
            public void move() {
                Point at = getLocation();
                at.x += (int)(speed * Math.cos(Math.toRadians(-direction)));
                at.y += (int)(speed * Math.sin(Math.toRadians(-direction)));
                setLocation(at);
            }
    
            @Override
            public Dimension getPreferredSize() {
                Dimension size = super.getPreferredSize();
                if (background != null) {
                    double radian = Math.toRadians(direction);
                    double sin = Math.abs(Math.sin(radian)), cos = Math.abs(Math.cos(radian));
                    int w = background.getWidth(), h = background.getHeight();
                    int neww = (int) Math.floor(w * cos + h * sin);
                    int newh = (int) Math.floor(h * cos + w * sin);
                    size = new Dimension(neww, newh);
                }
                return size;
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                int x = (getWidth() - background.getWidth()) / 2;
                int y = (getHeight() - background.getHeight()) / 2;
                g2d.rotate(Math.toRadians(-(direction + 180)), getWidth() / 2, getHeight() / 2);
                g2d.drawImage(background, x, y, this);
                g2d.dispose();
    
    //            Debug graphics...
    //            int cx = getWidth() / 2;
    //            int cy = getHeight() / 2;
    //
    //            g2d = (Graphics2D) g.create();
    //            g2d.setColor(Color.BLUE);
    //            double radius = Math.min(getWidth(), getHeight());
    //            Point2D pointAt = getPointAt(radius, direction);
    //            g2d.draw(new Ellipse2D.Double(cx - (radius / 2d), cy - (radius / 2d), radius, radius));
    //            
    //            double xo = cx;
    //            double yo = cy;
    //            double xPos = cx + pointAt.getX();
    //            double yPos = cy + pointAt.getY();
    //            
    //            g2d.draw(new Line2D.Double(xo, yo, xPos, yPos));
    //            g2d.draw(new Ellipse2D.Double(xPos - 2, yPos - 2, 4, 4));
    //            g2d.dispose();
            }
        }
    }
    

    Updated with optimized version

    I did a little bit of code optimisation with the creation of the car objects (there's still room for improvement) and ehanched the graphics ouput (made it look nicer).

    Basically, now, when a car leaves the screen, it's placed in a pool. When another car is required, if possible, it's pulled from the pool, otherwise a new car is made. This has reduced the overhead of creating and destorying so many (relativly) short lived objects, which makes the memory usage a little more stable.

    On my 2560x1600 resolution screen (running maximised), I was able to get 4500 cars running simultaneously. Once the object creation was reduced, it ran relatively smoothly (it's never going to run as well as 10, but it didn't suffer from a significant reduction in speed).

    public class TestAnimation10 {
    
        public static void main(String[] args) {
            new TestAnimation10();
        }
    
        public TestAnimation10() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (Exception ex) {
                    }
    
                    final TrackPane trackPane = new TrackPane();
                    JSlider slider = new JSlider(1, 5000);
                    slider.addChangeListener(new ChangeListener() {
                        @Override
                        public void stateChanged(ChangeEvent e) {
                            trackPane.setCongestion(((JSlider) e.getSource()).getValue());
                        }
                    });
                    slider.setValue(5);
    
                    JFrame frame = new JFrame("Test");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setLayout(new BorderLayout());
                    frame.add(trackPane);
                    frame.add(slider, BorderLayout.SOUTH);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
    
                }
            });
        }
    
        public class TrackPane extends JPanel {
    
            private List activeCarList;
            private List carPool;
            private int maxCars = 1;
            private List points;
            private Ellipse2D areaOfEffect;
    
            public TrackPane() {
    
                points = new ArrayList<>(25);
    
                activeCarList = new ArrayList<>(25);
                carPool = new ArrayList<>(25);
                setLayout(null);
    
                Timer timer = new Timer(40, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
    
                        Rectangle bounds = areaOfEffect.getBounds();
                        List tmp = new ArrayList<>(activeCarList);
                        for (Car car : tmp) {
                            car.move();
                            if (!bounds.intersects(car.getBounds())) {
                                remove(car);
                                activeCarList.remove(car);
                                carPool.add(car);
                            }
                        }
                        updatePool();
                        repaint();
                    }
                });
    
                timer.setRepeats(true);
                timer.setCoalesce(true);
                timer.start();
    
                updateAreaOfEffect();
            }
    
            protected void updateAreaOfEffect() {
                double radius = Math.max(getWidth(), getHeight()) * 1.5d;
                double x = (getWidth() - radius) / 2d;
                double y = (getHeight() - radius) / 2d;
                areaOfEffect = new Ellipse2D.Double(x, y, radius, radius);
            }
    
            @Override
            public void invalidate() {
    //            super.invalidate();
                updateAreaOfEffect();
            }
    
            protected void updatePool() {
                if (activeCarList.size() < maxCars) {
                    int count = Math.min(maxCars - activeCarList.size(), 10);
                    for (int index = 0; index < count; index++) {
                        Car car = null;
    
                        if (carPool.isEmpty()) {
                            car = new Car();
                        } else {
                            car = carPool.remove(0);
                        }
    
                        double direction = car.getDirection();
                        double startAngle = direction - 180;
    
                        double radius = areaOfEffect.getWidth();
                        Point2D startPoint = getPointAt(radius, startAngle);
    
                        int cx = getWidth() / 2;
                        int cy = getHeight() / 2;
    
                        double x = cx + (startPoint.getX() - car.getWidth() / 2);
                        double y = cy + (startPoint.getY() - car.getHeight() / 2);
                        car.setLocation((int) x, (int) y);
    
                        Point2D targetPoint = getPointAt(radius, direction);
    
                        points.add(new Point2D[]{startPoint, targetPoint});
    
                        add(car);
    
                        activeCarList.add(car);
                    }
                }
            }
    
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                Font font = g.getFont();
                font = font.deriveFont(Font.BOLD, 48f);
                FontMetrics fm = g.getFontMetrics(font);
                g.setFont(font);
                g.setColor(Color.RED);
                String text = Integer.toString(maxCars);
                int x = getWidth() - fm.stringWidth(text);
                int y = getHeight() - fm.getHeight() + fm.getAscent();
                g.drawString(text, x, y);
                text = Integer.toString(getComponentCount());
                x = getWidth() - fm.stringWidth(text);
                y -= fm.getHeight();
                g.drawString(text, x, y);
                text = Integer.toString(activeCarList.size());
                x = getWidth() - fm.stringWidth(text);
                y -= fm.getHeight();
                g.drawString(text, x, y);
                text = Integer.toString(carPool.size());
                x = getWidth() - fm.stringWidth(text);
                y -= fm.getHeight();
                g.drawString(text, x, y);
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(400, 400);
            }
    
            public void setCongestion(int value) {
                maxCars = value;
            }
    
            @Override
            public void validate() {
            }
    
            @Override
            public void revalidate() {
            }
    
    //        @Override
    //        public void repaint(long tm, int x, int y, int width, int height) {
    //        }
    //
    //        @Override
    //        public void repaint(Rectangle r) {
    //        }
    //        public void repaint() {
    //        }
            @Override
            protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
                System.out.println(propertyName);
    //            // Strings get interned...
    //            if (propertyName == "text"
    //                            || propertyName == "labelFor"
    //                            || propertyName == "displayedMnemonic"
    //                            || ((propertyName == "font" || propertyName == "foreground")
    //                            && oldValue != newValue
    //                            && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) {
    //
    //                super.firePropertyChange(propertyName, oldValue, newValue);
    //            }
            }
    
            @Override
            public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
            }
        }
    
        protected static Point2D getPointAt(double radius, double angle) {
    
            double x = Math.round(radius / 2d);
            double y = Math.round(radius / 2d);
    
            double rads = Math.toRadians(-angle);
    
            double fullLength = Math.round((radius / 2d));
    
            double xPosy = (Math.cos(rads) * fullLength);
            double yPosy = (Math.sin(rads) * fullLength);
    
            return new Point2D.Double(xPosy, yPosy);
    
        }
    
        public class Car extends JPanel {
    
            private double direction;
            private double speed;
            private BufferedImage background;
    
            public Car() {
                setOpaque(false);
                direction = Math.random() * 360;
                speed = 5 + (Math.random() * 10);
                int image = 1 + (int) Math.round(Math.random() * 5);
                try {
                    String name = "/Car0" + image + ".png";
                    background = ImageIO.read(getClass().getResource(name));
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                setSize(getPreferredSize());
    //            setBorder(new LineBorder(Color.RED));
            }
    
            public void setDirection(double direction) {
                this.direction = direction;
                revalidate();
                repaint();
            }
    
            public double getDirection() {
                return direction;
            }
    
            public void move() {
                Point at = getLocation();
                at.x += (int) (speed * Math.cos(Math.toRadians(-direction)));
                at.y += (int) (speed * Math.sin(Math.toRadians(-direction)));
                setLocation(at);
            }
    
            @Override
            public Dimension getPreferredSize() {
                Dimension size = super.getPreferredSize();
                if (background != null) {
                    double radian = Math.toRadians(direction);
                    double sin = Math.abs(Math.sin(radian)), cos = Math.abs(Math.cos(radian));
                    int w = background.getWidth(), h = background.getHeight();
                    int neww = (int) Math.floor(w * cos + h * sin);
                    int newh = (int) Math.floor(h * cos + w * sin);
                    size = new Dimension(neww, newh);
                }
                return size;
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
                g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
                int x = (getWidth() - background.getWidth()) / 2;
                int y = (getHeight() - background.getHeight()) / 2;
                g2d.rotate(Math.toRadians(-(direction + 180)), getWidth() / 2, getHeight() / 2);
                g2d.drawImage(background, x, y, this);
                g2d.dispose();
    
    //            Debug graphics...
    //            int cx = getWidth() / 2;
    //            int cy = getHeight() / 2;
    //
    //            g2d = (Graphics2D) g.create();
    //            g2d.setColor(Color.BLUE);
    //            double radius = Math.min(getWidth(), getHeight());
    //            Point2D pointAt = getPointAt(radius, direction);
    //            g2d.draw(new Ellipse2D.Double(cx - (radius / 2d), cy - (radius / 2d), radius, radius));
    //            
    //            double xo = cx;
    //            double yo = cy;
    //            double xPos = cx + pointAt.getX();
    //            double yPos = cy + pointAt.getY();
    //            
    //            g2d.draw(new Line2D.Double(xo, yo, xPos, yPos));
    //            g2d.draw(new Ellipse2D.Double(xPos - 2, yPos - 2, 4, 4));
    //            g2d.dispose();
            }
    
            @Override
            public void invalidate() {
            }
    
            @Override
            public void validate() {
            }
    
            @Override
            public void revalidate() {
            }
    
            @Override
            public void repaint(long tm, int x, int y, int width, int height) {
            }
    
            @Override
            public void repaint(Rectangle r) {
            }
    
            @Override
            public void repaint() {
            }
    
            @Override
            protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
    //            System.out.println(propertyName);
    //            // Strings get interned...
    //            if (propertyName == "text"
    //                            || propertyName == "labelFor"
    //                            || propertyName == "displayedMnemonic"
    //                            || ((propertyName == "font" || propertyName == "foreground")
    //                            && oldValue != newValue
    //                            && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) {
    //
    //                super.firePropertyChange(propertyName, oldValue, newValue);
    //            }
            }
    
            @Override
            public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
            }
        }
    }
    

    ps - I should add 1- My 10 month old loved it 2- It reminded me of the run to work :P

提交回复
热议问题