Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
375 views
in Technique[技术] by (71.8m points)

smooth animation in java for fast moving objects

I am creating simple animation of ball moving from one side of the screen to the other with different speed. The problem is that with higher speeds of the ball I can see noticeable flickering of the ball, actually it is hard to explain but something like I could see repaints when part of ball is still in previous step.

I have tried number of things including:

  1. native swing animation using first thread/sleep/repain, then moved to timers

  2. switched to javafx canvas/pane inside swing jframe. Tried both transitions and AnimationTimer

  3. tinkering with CreateBufferStrategy, for 1,2,3 - to be honest haven't seen any difference (maybe I was doing something wrong...)

My question how can I improve smoothness and whether what I want to achieve is possible with native java or maybe it is better to use some external libraries ? and if so could you recommend something ?

below shown my example code for 2nd/3rd attempt.

import java.awt.Dimension;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;

import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
import javax.swing.JFrame;

public class FXTrackerPanel extends JFrame {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    public int crSize = 30;
    public double xPos = crSize;
    public double yPos = 100;
    public int xSize = 100;
    public int ySize = 100;
    public Circle r;
    int dir = 1;

    public void updateScreenSize() {
        int screen = 0;
        GraphicsEnvironment ge = GraphicsEnvironment
            .getLocalGraphicsEnvironment();
        GraphicsDevice[] gs = ge.getScreenDevices();
        if( screen > -1 && screen < gs.length )
        {           
            xSize = gs[screen].getDisplayMode().getWidth();
            ySize = gs[screen].getDisplayMode().getHeight();
        }
        else if( gs.length > 0 )
        {
            xSize = gs[0].getDisplayMode().getWidth();
            ySize = gs[0].getDisplayMode().getHeight();
        }
        else
        {
            throw new RuntimeException( "No Screens Found" );
        }

        yPos = ySize / 2;
    }

    private void initFXPanel(JFXPanel fxPanel) {
        updateScreenSize();
        xPos = crSize;

        Group root = new Group();

        double speed = 5;

        int repeats = Timeline.INDEFINITE;

        r = new javafx.scene.shape.Circle(xPos, yPos, crSize / 2, Color.RED);
        TranslateTransition tt = new TranslateTransition(Duration.seconds(speed), r);
        tt.setFromX(xPos);
        tt.setToX(xSize - crSize * 3);
        tt.setCycleCount(repeats);
        tt.setAutoReverse(true);
        tt.setInterpolator(Interpolator.EASE_BOTH);
        tt.play();

        root.getChildren().add(r);

//      new AnimationTimer() {
//          
//          @Override
//          public void handle(long now) {
//              double speed = 20;
//              try {
//                  speed = Double.valueOf(TETSimple.mp.speedSinus.getText());
//              }
//              catch (Exception ex) {
//                  speed = 20;
//              }
//              double xMov = (speed * 4 * Math.sin( xPos * Math.PI / xSize ) );
//              if (xMov <= 0) {
//                  xMov = 1;
//              }
//              if (dir == 1) {
//                  if (xPos >= xSize - crSize)
//                      dir = 0;
//                  xPos += xMov;
//              } else {
//                  if (xPos <= 1)
//                      dir = 1;
//                  xPos -= xMov;
//              }
//              
//              r.setTranslateX(xPos);              
//          }
//      }.start();

        fxPanel.setScene(new Scene(root));
    }

    public FXTrackerPanel() {
        updateScreenSize();
        this.setSize(new Dimension(xSize, ySize));
        this.setPreferredSize(new Dimension(xSize, ySize));
        this.setVisible(true);
        this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        JFXPanel fxPanel = new JFXPanel();
        this.add(fxPanel);
        this.createBufferStrategy(3);

        Platform.runLater(new Runnable() {

            @Override
            public void run() {
                initFXPanel(fxPanel);               
            }
        });
    }

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

And here example for swing code:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;

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

import java.lang.Math;
import java.util.Random;

public class TrackerPanel extends JPanel implements ActionListener {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    Shape cr;
    Color c;
    public int crSize = 30;
    public double xPos = crSize;
    public double yPos = 100;
    public double xPosPrev = crSize;
    public double yPosPrev = 100;
    public int xSize = 100;
    public int ySize = 100;
    int dir = 1; // left
    int timer = 50; // 50 - sins, 1500 - linear
    int method = 1; // 1 - jump, 2 - sinus
    int timeToChange = 1000;
    int passes = 0;
    Timer tt;
    boolean clickedClose = false;
    private int repeats = 0;

    // t - timer interval, m - method of ball movement: 1 - jump, 2 - sinus
    public TrackerPanel(int t, int m) {
        this.setPreferredSize(new Dimension(300, 200));
        this.timer = t;
        this.method = m;
        c = Color.red;
        repaint();
        this.updateScreenSize();
        tt = new Timer(t, null);
        tt.addActionListener(this);
        tt.start();
    }

    public void updateScreenSize() {
        int screen = TETSimple.suppMonitor;
        GraphicsEnvironment ge = GraphicsEnvironment
                .getLocalGraphicsEnvironment();
        GraphicsDevice[] gs = ge.getScreenDevices();
        if (screen > -1 && screen < gs.length) {
            xSize = gs[screen].getDisplayMode().getWidth();
            ySize = gs[screen].getDisplayMode().getHeight();
        } else if (gs.length > 0) {
            xSize = gs[0].getDisplayMode().getWidth();
            ySize = gs[0].getDisplayMode().getHeight();
        } else {
            throw new RuntimeException("No Screens Found");
        }

        yPos = ySize / 2;
        yPosPrev = ySize / 2;
    }

    public void actionPerformed(ActionEvent arg0) {
        if (method == 1)
            lineMovement();
        else
            sinusMovement();
        repaint(0, ySize / 2, xSize, crSize);
    }

    private Double parseText2Int(String literal) {
        try {
            return Double.valueOf(literal);
        } catch (Exception ex) {
            ex.printStackTrace();           
        }
        return 10.0;
    }

    private void checkFinishCondition() {
        if (passes + 1 > repeats && repeats != 0) {
            if (!clickedClose) {
                TETSimple.mp.bStop.doClick();
                clickedClose = true;
            }
            return;
        }
    }

    private void sinusMovement() {
        this.updateScreenSize();
        this.repeats = parseText2Int(TETSimple.mp.repeatsCount.getText()).intValue();
        checkFinishCondition();

        double speed = parseText2Int(TETSimple.mp.speedSinus.getText());
        double xMov = (speed * Math.sin(xPos * Math.PI / xSize));
        if (xMov <= 0) {
            xMov = 1;
        }
        if (dir == 1) {
            if (xPos >= xSize - crSize)
                dir = 0;
            xPosPrev = xPos;
            xPos += xMov;
        } else {
            if (xPos <= 1 + crSize) {
                dir = 1;
                passes++;
            }
            xPosPrev = xPos;
            xPos -= xMov;
        }
    }

    private void lineMovement() {
        this.repeats = parseText2Int(TETSimple.mp.repeatsCount.getText()).intValue();
        checkFinishCondition();

        double left = crSize;
        double center = xSize / 2 - crSize * 1.5;
        double right = xSize - crSize * 2;
        Random r = new Random();

        if (timeToChange <= 0) {
            passes++;
            if (xPos == left || xPos == right) {
                timeToChange = 300 + r.nextInt(12) * 100;
                xPos = center;
            } else if (xPos == center) {
                timeToChange = 300 + r.nextInt(7) * 100;
                if (r.nextBoolean())
                    xPos = left;
                else
                    xPos = right;
            }
        } else {
            timeToChange -= 100;
        }
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        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);
        g2d.setColor(Color.green);
        g2d.fill(new Ellipse2D.Double(xPos, yPos, crSize, crSize));
        g2d.dispose();
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It's difficult to know exactly what might be going wrong without a runnable example, but I would, where you can, avoid mixing JavaFX and Swing, as they have different rendering mechanisms.

The following is a VERY simple example, which simply increases the speed of the balls been animated by simply changing the amount by which they are moved on each update...

Balls

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class BouncyBall {

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

    public BouncyBall() {
        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 ControlPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class ControlPane extends JPanel {

        private JSlider speed;
        private JSlider quanity;

        private BallPitPane ballPitPane;

        public ControlPane() {
            setLayout(new BorderLayout());
            ballPitPane = new BallPitPane();
            add(ballPitPane);

            JPanel controls = new JPanel(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.anchor = GridBagConstraints.WEST;

            speed = new JSlider(1, 100, 4);
            quanity = new JSlider(1, 100, 1);

            controls.add(new JLabel("Speed:"), gbc);
            gbc.gridy++;
            controls.add(new JLabel("Quanity:"), gbc);

            gbc.gridx++;
            gbc.gridy = 0;
            gbc.weightx = 1;
            gbc.fill = GridBagConstraints.HORIZONTAL;

            controls.add(speed, gbc);
            gbc.gridy++;
            controls.add(quanity, gbc);
            add(controls, BorderLayout.SOUTH);

            speed.addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    ballPitPane.setSpeed(speed.getValue());
                }
            });

            quanity.addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    ballPitPane.setQuanity(quanity.getValue());
                }
            });
        }

    }

    public class BallPitPane extends JPanel {

        private List<Ball> balls;
        private int speed;

        public BallPitPane() {
            balls = new ArrayList<>(25);
            setSpeed(2);
            setQuanity(1);

            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    for (Ball ball : balls) {
                        ball.update(getWidth(), speed);
                    }
                    repaint();
                }
            });
            timer.start();
        }

        public void setSpeed(int speed) {
            this.speed = speed;
        }

        public void setQuanity(int quanity) {

            while (balls.size() > quanity) {
                balls.remove(0);
            }
            while (balls.size() < quanity) {
                int radius = 4 + (int) (Math.random() * 48);
                Ball ball = new Ball(
                        randomColor(),
                        (int) Math.abs(Math.random() * getWidth() - radius),
                        (int) Math.abs(Math.random() * getHeight() - radius),
                        radius
                );
                balls.add(ball);
            }

        }

        protected Color randomColor() {

            int red = (int) Math.abs(Math.random() * 255);
            int green = (int) Math.abs(Math.random() * 255);
            int blue = (int) Math.abs(Math.random() * 255);

            return new Color(red, green, blue);

        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            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);
            for (Ball ball : balls) {
                ball.paint(g2d);
            }
            g2d.dispose();
        }

        public class Ball {

            private Color color;
            private int x;
            private int y;
            private int radius;
            private int delta;

            public Ball(Color color, int x, int y, int radius) {
                this.color = color;
                this.x = x;
                this.y = y;
                this.radius = radius;
                delta = Math.random() > 0.5 ? 1 : -1;
            }

            public void update(int width, int speed) {
                x += speed * delta;
                if (x + radius >= width) {
                    x = width - radius;
                    delta *= -1;
                } else if (x < 0) {
                    x = 0;
                    delta *= -1;
                }
            }

            public void paint(Graphics g) {
                g.setColor(color);
                g.fillOval(x, y, radius, radius);
            }

        }

    }

}

This example works within the confines of Swing's painting process, if you need more control over the painting process you will need to use a BufferStrategy (to work within Swing)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...