在java中为快速移动的对象平滑动画

时间:2014-12-29 00:21:03

标签: java animation javafx

我正在创建一个简单的动画,球从屏幕的一侧移动到另一侧以不同的速度。问题是,随着球的速度越来越快,我可以看到球的明显闪烁,实际上很难解释,但是当球的一部分仍然在前一步时,我可以看到重绘。

我尝试了很多东西,包括:

  1. 使用第一线程/睡眠/重拍的本机挥动动画,然后转移到计时器

  2. 切换到swing jframe内的javafx canvas / pane。试过两个过渡和AnimationTimer

  3. 修补CreateBufferStrategy,1,2,3 - 老实说没有看到任何差异(也许我做错了......)

  4. 我的问题是如何改善平滑度以及我想用本机java实现的目标是否可行?或者使用一些外部库可能更好?如果是这样你能推荐一些东西吗?

    下面显示了我的第二次/第三次尝试的示例代码。

    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();
        }
    }
    

    这里是摆码的例子:

    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();
        }
    }
    

2 个答案:

答案 0 :(得分:2)

我认为这个问题是由AWT中JFXPanel的渲染引起的:在幕后发生了一些复杂的事情,需要在两个不同的系统线程之间进行同步(AWT事件派发线程和FX应用程序线程)。

如果你可以把它写成一个“纯粹的”JavaFX应用程序(即没有Swing / AWT代码),它运行得更顺畅:

import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.util.Duration;

public class FXAnimationTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        Group root = new Group();

        double speed = 5;

        int repeats = Timeline.INDEFINITE;

        Screen screen = Screen.getPrimary();
        Rectangle2D screenBounds = screen.getBounds();
        double xSize = screenBounds.getWidth();
        double ySize = screenBounds.getHeight();

        double crSize = 30 ;
        double xPos = crSize ;
        double yPos = ySize / 2 ;

        Circle r = new 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);

        Scene scene = new Scene(root, xSize, ySize);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

如果你必须在Swing应用程序中嵌入JavaFX,并且你正在使用Java 8(我在8u20上测试过它),那么有一个系统属性可以在同一个线程上运行两个UI工具包。我认为这仍然是实验性的,所以请自担风险,但请尝试

java -Djavafx.embed.singleThread=true FXTrackerPanel

这稍微改善了一些事情,但它仍然非常闪烁,不如“纯JavaFX”版本好。

答案 1 :(得分:2)

如果没有可运行的示例,很难确切知道可能出现什么问题,但我会尽可能避免混合使用JavaFX和Swing,因为它们具有不同的渲染机制。

以下是一个非常简单的例子,它只是通过简单地更改每次更新时移动的数量来简单地增加球的速度......

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);
            }

        }

    }

}

此示例适用于Swing绘画过程的范围,如果您需要更多控制绘画过程,则需要使用BufferStrategy(在Swing中工作)