首页 > 解决方案 > 如何为我的 java Swing graphics2d 组件制作动画

问题描述

我正在制作一个重力模拟器,我需要它实时动画,以便用户可以观看它。我已经能够让它追踪对象将要走的路径。

生成的轨道图像

但正如您所看到的,它只是跟踪它然后显示窗口。我认为我的问题是因为所有这些都在构建 JPanel 的代码部分中,但我不知道如何正确更改它。

这是我正在为我的窗口做的事情:

import java.awt.*;
import javax.swing.*;
import java.lang.Math;


public class Universe {
      
    public static void main(String[] args) throws InterruptedException  {
        new Universe();       
    }

    public Universe() {
        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("Gravity Simulator");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
        }
        int paneWidth = 500;
        int paneHeight = 500;
               
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(paneWidth, paneHeight);
        }

        protected void paintComponent(Graphics g)  {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            int size = Math.min(getWidth()-4, getHeight()-4) / 10;
            int width = getWidth() - (size * 2);
            int height = getHeight() - (size * 2);
            int x0=paneWidth/2; int y0=paneHeight/2; int radius0=20;

            int y = (getHeight() - (size * 10)) / 2;
            for (int horz = 0; horz < 2; horz++) {
                int x = (getWidth() - (size * 10)) / 2;
                for (int vert = 0; vert < 10; vert++) {
                    g.drawRect(x, y, size, size);
                    
                    drawCircle(g, x+25, y+25, 5);//A massive object would go here this just proof of concept
                    x += size;
                }
                y += size;
            }
                         
            double[] velocity={5,-2};
            MassiveObject planet = new MassiveObject(g, 20, 50, velocity, 250, 150);
            planet.draw(g);
            MassiveObject rock = new MassiveObject(g, 2, 25, velocity, 275, 300);
            rock.draw(g);

            double sGravity = fGrav(planet, rock);
            //double dis = massDis(planet, rock);
            System.out.println("Distance: "+massDis(planet, rock));
            System.out.println("Gravity: "+sGravity+" Newtons of force(gravity is multiplied by "+1000000+")");

            double[] traj = objectTrajetory(planet, rock, rock.getMass());
            int t = 0;
            
            try {
                while(true) {
                    //double k = sGravity/dis;
                    //x and y components of motion
                    double xm = traj[0];
                    double ym = traj[1];               
                    
                    double[]  nVelocity= {xm,ym};
                    //////////////////////////////
                    //set new position of object
                    rock.setX(rock.getX()+(xm));
                    rock.setY(rock.getY()+(ym));
                    rock.setVelocity(nVelocity);
                    rock.draw(g);
                    t++;
                    System.out.println("position changed: "+rock.getCoords());
                    traj = objectTrajetory(planet, rock, 1);
                    Thread.sleep(100);
                    if (t> 15){break;}                    
                }
              }
              catch(Exception e) {
                
              }
               
            //System.out.println("Distance: "+massDis(planet, rock));
            //System.out.println("Gravity: "+fGrav(planet, rock)+" Newtons of force(gravity is multiplied by "+1000000+")");
              
            g2d.dispose();
        }

这是我的 MassiveObject 的绘制函数的代码:

    public void draw(Graphics g){
  Graphics2D g2d = (Graphics2D) g;
  Ellipse2D.Double circle = new Ellipse2D.Double(this.x0-(this.radius/2), this.y0-(this.radius/2), this.radius, this.radius);

  g2d.setColor(Color.GRAY);
  g2d.fill(circle);
}

所以基本上我要问的是如何让它运行该算法以在窗口已经拉起后将 MassiveObject 粘贴到它的新位置,这样用户就可以看到它的发生,而不是仅仅用它来构建窗口就可以了?

标签: javaswing

解决方案


动画的逻辑不应该在paintComponent()方法中。该paintComponent()方法应该只绘制动画的当前帧。里面的代码在paintComponent()一个专门处理所有 UI 绘制、响应点击等的特殊线程中运行。因此,只要paintComponent()正在运行,UI 中就不会发生任何其他事情,因此您的应用程序“停止运行”。

定期更新状态然后订购重绘的逻辑应该在单独的线程(或主线程)中。当它更新了状态并需要绘制下一帧时,它会调用面板的repaint()方法。因为您是在另一个线程中执行此操作,所以您会将其包围在SwingUtilities.invokeLater(). 这命令 Swing 回调到paintComponent()

 while (true) {
   // Update state used by the paintComponent() method
   updateObjectPositions();

   // Now draw the new animation frame
   SwingUtilities.invokeLater(() -> {
        universePanel.repaint(0, 0, universeWidth, universeHeight);
   });

   Thread.sleep(...);
 }

因为绘制和更新是在不同的线程中进行的,所以您需要确保数据以线程安全的方式在线程之间共享。如果您刚刚开始并且计算非常快,那么您可以将updateObjectPositions()方法放入其中,invokeLater()以便在 UI 线程中更新数据和重绘。但请记住,invokeLater()只要它运行,里面的代码就会阻塞 UI,所以它应该尽可能简短并且只处理一个帧。至关重要的是,您的while循环sleep不应进入任何UI 代码内部invokeLater()或内部,例如paintComponent().


推荐阅读