2012-11-24 133 views
2

我正在用Java製作一個簡單的動畫,我正在儘可能使它變得流暢。如何讓線條動畫更流暢?

我只使用每個Shape對象的* .Double內部類,並在Graphics2D對象上設置抗鋸齒。只要我只使用fill()方法,它就可以工作,但如果我也使用draw()方法在同一個Shape的周圍繪製線條,則這些線條的動畫會逐漸變形 - 逐個像素。

畫布上的每個矩形都有這種方法來繪製自己。它每20ms移動一次,整個畫布使用Timer和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); 
     } 
    } 
} 

有什麼辦法解決這個問題嗎?我環顧了很長時間。

+0

你啓用了雙緩衝? – sjr

回答

5

我把這個小測試放在一起,並沒有得到任何重要的問題,我基本上能夠保持50fps,即使有1000個矩形都以隨機方向隨機移動。

enter image description here

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

    } 

} 

UPDATE

你要面對交易的事實,屏幕只能在整數的最大問題...

private class Animator implements ActionListener { 

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

我相信這兩個方格實際上都是「抖動」,但是因爲這個方格有明顯的身體缺陷,它更突出。我以大約24fps的速度運行這個測試,沒有任何問題。

+0

嗯,這是一個相當複雜的代碼,我的代碼要簡單得多,而且不太正確。我只是使用一個簡單的Timer並每20ms對畫布進行重繪()。但是試着製作2個簡單的正方形,一個填充並且只繪製一個,然後在你的代碼中移動它們慢得多。以約2px/sec的速度移動它們。這樣你就會看到AA使填充的正方形看起來模糊不僵,但是畫出的正方形會變得尖銳但不連貫。 – user1562293

+0

我會嘗試複製您的問題,但這就是爲什麼一個工作示例如此重要的原因 – MadProgrammer

+0

我編輯了原始問題並提供了一個工作代碼。隨意對此發表評論。你可以清楚地看到左邊的平移運動平穩,右邊的平移運動波濤洶涌。 – user1562293

0

您有:

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

而且

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 += 1; 
      squares[1].y += 1; 
      repaint(); 
     } 
    } 

而且

public void startAnimation() { 
    t = new Timer(300, new Animator()); 
    t.start(); 
} 
達到同樣的

這樣做相同,但循環次數少10次。

我沒有測試代碼,但它是值得一試。

MadProgrammer提供了正確的解決方案,通過它測量延遲和偏移量。