如何使线条动画更流畅? [英] How to make line animation smoother?

查看:29
本文介绍了如何使线条动画更流畅?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在用 Java 制作一个简单的动画,并试图让它尽可能流畅.

I am making a simple animation in Java and I am trying to make it as smooth as possible.

我只使用每个 Shape 对象的 *.Double 内部类,并且我在 Graphics2D 对象中设置了抗锯齿.只要我只使用 fill() 方法,这一切都可以工作,但如果我也使用 draw() 方法在同一个 Shape 周围绘制线条,这些线条的动画就会断断续续 - 一个像素一个像素.

I use only *.Double inner classes of each Shape object and I set antialiasing in the Graphics2D objects on. It all works as long as I use only the fill() method but if I also use draw() method to draw lines around the same Shape the animation of these lines is choppy - pixel by pixel.

我在画布上的每个矩形都有这个方法来绘制自己.它每 20 毫秒移动一次,并使用 Timer 和 TimerListener 重新绘制整个画布.

Each of my rectangles on the canvas has this method to paint itself. It is moved every 20ms and the whole canvas is repainted using Timer and TimerListener.

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class AnimationTest {
    public static void main(String[] args) {
        JFrame frm = new JFrame("Test");
        frm.setBounds(200, 200, 400, 400);
        frm.setResizable(false);
        frm.setLocationRelativeTo(null);

        AnimationCanvas a = new AnimationCanvas();
        frm.add(a);

        frm.setVisible(true);

        a.startAnimation();
    }
}

class AnimationCanvas extends JPanel {

    SimpleSquare[] squares = new SimpleSquare[2];

    AnimationCanvas() {

        squares[0] = new SimpleSquare(50, 80, true);
        squares[1] = new SimpleSquare(160, 80, false);

    }

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

        for (SimpleSquare c : squares) {
            c.paintSquare(g);
        }
    }

    Timer t;
    public void startAnimation() {
        t = new Timer(30, new Animator());
        t.start();
    }

    private class Animator implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            squares[0].y += 0.10;
            squares[1].y += 0.10;
            repaint();
        }
    }
}


class SimpleSquare {
    double x;
    double y;
    Color color = Color.black;
    boolean fill;

    SimpleSquare(double x, double y, boolean fill) {
        this.x = x;
        this.y = y;
        this.fill = fill;
    }

    void paintSquare(Graphics g) {
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        Shape s = new Rectangle.Double(x, y, 100, 100);

        g.setColor(color);
        ((Graphics2D) g).setStroke(new BasicStroke(2));

        if (fill) {
            ((Graphics2D) g).fill(s);
        } else {
            ((Graphics2D) g).draw(s);
        }
    }
}

有什么办法可以解决这个问题吗?我环顾了一圈.

Is there any way to fix this problem? I looked around for quite a while.

推荐答案

我进行了这个小测试并没有发现任何重大问题,即使 1000 个矩形都以随机速度向随机方向移动,我基本上也能保持 50fps.

I put together this little test and got no significnt issues, I was basically capable of maintaining 50fps even with 1000 rectangles all moving at random speeds in random directions.

public class SimpleAnimationEngine {

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

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

                AnimationPane pane = new AnimationPane();

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(pane);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                pane.init();
                pane.start();
            }

        });
    }

    public static class AnimationPane extends JPanel implements AnimationCanvas {

        private AnimationModel model;

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

        public AnimationModel getModel() {
            return model;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            for (Animatable animatable : getModel().getAnimatables()) {
                animatable.paint(g2d);
            }
            g2d.dispose();
        }

        @Override
        public synchronized void updateState() {

            Runnable update = new Runnable() {
                @Override
                public void run() {
                    AnimationModel model = getModel();
                    for (Animatable animatable : model.getAnimatables()) {
                        animatable.copy();
                    }
                    repaint();
                }

            };

            if (EventQueue.isDispatchThread()) {
                update.run();
            } else {
                try {
                    EventQueue.invokeAndWait(update);
                } catch (InterruptedException | InvocationTargetException ex) {
                    ex.printStackTrace();
                }
            }
        }

        public void init() {
            model = new DefaultAnimationModel();
            for (int index = 0; index < 1000; index++) {
                model.add(new AnimatableRectangle(this));
            }
            updateState();
        }

        public void start() {
            AnimationEngine engine = new AnimationEngine(this, getModel());
            engine.start();
        }

    }

    public static interface Animatable {

        public void copy();

        public void update(AnimationCanvas canvas, float progress);

        public void paint(Graphics2D g2d);

    }

    public static class AnimationEngine extends Thread {

        private AnimationModel model;
        private AnimationCanvas canvas;

        public AnimationEngine(AnimationCanvas canvas, AnimationModel model) {
            setDaemon(true);
            setName("AnimationThread");
            this.model = model;
            this.canvas = canvas;
        }

        public AnimationCanvas getCanvas() {
            return canvas;
        }

        public AnimationModel getModel() {
            return model;
        }

        @Override
        public void run() {
            float progress = 0;
            long cylceStartTime = System.currentTimeMillis();
            long cylceEndTime = cylceStartTime + 1000;
            int updateCount = 0;
            while (true) {
                long frameStartTime = System.currentTimeMillis();
                getModel().update(getCanvas(), progress);
                getCanvas().updateState();
                long frameEndTime = System.currentTimeMillis();
                long delay = 20 - (frameEndTime - frameStartTime);
                if (delay > 0) {
                    try {
                        sleep(delay);
                    } catch (InterruptedException ex) {
                    }
                }
                long now = System.currentTimeMillis();
                long runtime = now - cylceStartTime;
                progress = (float)runtime / (float)(1000);
                updateCount++;
                if (progress > 1.0) {
                    progress = 0f;
                    cylceStartTime = System.currentTimeMillis();
                    cylceEndTime = cylceStartTime + 1000;
                    System.out.println(updateCount + " updates in this cycle");
                    updateCount = 0;
                }
            }
        }

    }

    public interface AnimationCanvas {

        public void updateState();

        public Rectangle getBounds();

    }

    public static interface AnimationModel {

        public void update(AnimationCanvas canvas, float progress);

        public void add(Animatable animatable);

        public void remove(Animatable animatable);

        public Animatable[] getAnimatables();

    }

    public static class AnimatableRectangle implements Animatable {

        private Rectangle bounds;
        private int dx, dy;
        private Rectangle copyBounds;
        private Color foreground;
        private Color backColor;

        public AnimatableRectangle(AnimationCanvas canvas) {
            bounds = new Rectangle(10, 10);
            Rectangle canvasBounds = canvas.getBounds();
            bounds.x = canvasBounds.x + ((canvasBounds.width - bounds.width) / 2);
            bounds.y = canvasBounds.y + ((canvasBounds.height - bounds.height) / 2);

            dx = (getRandomNumber(10) + 1) - 5;
            dy = (getRandomNumber(10) + 1) - 5;

            dx = dx == 0 ? 1 : dx;
            dy = dy == 0 ? 1 : dy;

            foreground = getRandomColor();
            backColor = getRandomColor();

        }

        protected int getRandomNumber(int range) {
            return (int) Math.round(Math.random() * range);
        }

        protected Color getRandomColor() {
            return new Color(getRandomNumber(255), getRandomNumber(255), getRandomNumber(255));
        }

        @Override
        public void copy() {
            copyBounds = new Rectangle(bounds);
        }

        @Override
        public void update(AnimationCanvas canvas, float progress) {
            bounds.x += dx;
            bounds.y += dy;
            Rectangle canvasBounds = canvas.getBounds();
            if (bounds.x + bounds.width > canvasBounds.x + canvasBounds.width) {
                bounds.x = canvasBounds.x + canvasBounds.width - bounds.width;
                dx *= -1;
            }
            if (bounds.y + bounds.height > canvasBounds.y + canvasBounds.height) {
                bounds.y = canvasBounds.y + canvasBounds.height - bounds.height;
                dy *= -1;
            }
            if (bounds.x < canvasBounds.x) {
                bounds.x = canvasBounds.x;
                dx *= -1;
            }
            if (bounds.y < canvasBounds.y) {
                bounds.y = canvasBounds.y;
                dy *= -1;
            }
        }

        @Override
        public void paint(Graphics2D g2d) {
            g2d.setColor(backColor);
            g2d.fill(copyBounds);
            g2d.setColor(foreground);
            g2d.draw(copyBounds);
        }

    }

    public static class DefaultAnimationModel implements AnimationModel {

        private List<Animatable> animatables;

        public DefaultAnimationModel() {
            animatables = new ArrayList<>(25);
        }

        @Override
        public synchronized void update(AnimationCanvas canvas, float progress) {
            for (Animatable animatable : animatables) {
                animatable.update(canvas, progress);
            }
        }

        @Override
        public synchronized void add(Animatable animatable) {
            animatables.add(animatable);
        }

        @Override
        public synchronized void remove(Animatable animatable) {
            animatables.remove(animatable);
        }

        @Override
        public synchronized Animatable[] getAnimatables() {
            return animatables.toArray(new Animatable[animatables.size()]);
        }

    }

}

更新

您将面临的最大问题是屏幕只能显示整数...

The biggest problem you're going to face deals with the fact that the screen only works in whole numbers...

private class Animator implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        squares[0].y += 1;
        squares[1].y += 1;
        repaint();
    }
}

我相信这两个方块其实都在抖动",但是因为拉出的方块有这么明显的缺体,所以显得更加突出.我以大约 24fps 的速度运行此测试,没有任何问题.

I believe that both squares are actually "jittering", but because the drawen square has such an obvious lack of body, it stands out more. I run this test at roughly 24fps without any issue.

这篇关于如何使线条动画更流畅?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆