资讯专栏INFORMATION COLUMN

利用多线程、简单GUI实现小球的动能定理的二维可视化

simpleapples / 3087人阅读

摘要:动能定理的实践出了问题,因为鼠标点击的小球生成是随机的,无法得知每个小球的情况,动能定理公式给定了,可能是算法不够精致。

要实现小球运动,可以从以下几点切入:

小球都有那些具体特征?

- 涉及动能定理就需要考虑质量了,除此之外常规的几个变量也不能忘:方向、球的尺寸,所在位置以及当前速度

谁能初始小球的状态?

-  小球的状态无非两种:(随机)默认值、人工手动输入

谁能控制小球的运动?

- 我们控制小球是需要给予一定的指令的,就算是鼠标点击也是简单的指令之一,除此之外如果想要拥有稍微复杂一点的指令可以使用按钮来实现

根据分析,我们大概能构造出大概的类,无非是一个专门描述小球状态的,一个控制所有命令的,一个构建出窗口和选项的。我们还可以在细化这三个类的功能:

球类

因为窗口也是二维的

构造方法仅需要包含:

- 质量
- 沿x、y轴的分速度
- 二维坐标表示的位置
- 颜色
- 大小

画个小球

颜色

尺寸

移动小球

遇到边界直接反弹

移动距离利用公式:距离=当前距离+速度×时间(这边直接简化成1)

小球之间的完全弹性碰撞,根据公式:

$$frac{{{ m{(}}{{ m{m}}_1} - {m_2}){v_{10}} + 2{m_2}{v_{20}}}}{{{{ m{m}}_1} + {m_2}}}$$
$$frac{{{ m{(}}{{ m{m}}_2} - {m_1}){v_{20}} + 2{m_1}{v_{10}}}}{{{{ m{m}}_1} + {m_2}}}$$

代码如下:

import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import javax.swing.JFrame;

public class Ball{
    /**
     * declare variables of ball
     */
    private int xPos, yPos, size, xSpeed, ySpeed,mass;
    private Color color;
    /**
     * Constructor for objects of class Ball
     */
    public Ball(int xPos, int yPos, int size, int xSpeed, int ySpeed, Color color,int mass) {
        super();
        this.xPos = xPos;
        this.yPos = yPos;
        this.size = size;
        this.xSpeed = xSpeed;
        this.ySpeed = ySpeed;
        this.color = color;
        this.mass=mass;
    }
    /**
     * use method of Graphics to create a colorful ball
     * @param g A ball
     */
    public void drawBall(Graphics g) {
        g.setColor(color);        
        g.fillOval(xPos, yPos, size, size);    
    }
    /**
     * This method tries to figure out
     * @param g A ball
     * if ball is going to hit the boundary,change its direction
     */
    public void moveBall(Graphics g,BallFrame bf) {
        if (xPos+size>bf.getWidth() || xPos <0) 
        {
            xSpeed = -xSpeed;
        }
        if (yPos<0 ||(yPos+size >bf.getHeight()-160)) 
        {
            ySpeed = -ySpeed;
        }
        xPos += xSpeed;        
        yPos += ySpeed;        
    }
    public void collision(Graphics g, ArrayList balls) {
        for (int i = 0; i < balls.size(); i++) {
            Ball ball = (Ball) balls.get(i);    
            if (ball != this) {        
                double d1 = Math.abs(this.xPos - ball.xPos);    
                double d2 = Math.abs(this.yPos - ball.yPos);    
                double d3 = Math.sqrt(Math.pow(d1,2) + Math.pow(d2,2));    
                if (d3 <= (this.size / 2 + ball.size / 2)) {
                /**
                 * Using perfectly elastic collision 
                 */
                    this.xSpeed=((this.mass-ball.mass)*this.xSpeed+2*ball.mass*ball.xSpeed)/(this.mass+ball.mass);
                    this.ySpeed=((this.mass-ball.mass)*this.ySpeed+2*ball.mass*ball.ySpeed)/(this.mass+ball.mass);
                    ball.xSpeed=((ball.mass-this.mass)*ball.xSpeed+2*this.mass*this.xSpeed)/(this.mass+ball.mass);
                    ball.ySpeed=((ball.mass-this.mass)*ball.ySpeed+2*this.mass*this.ySpeed)/(this.mass+ball.mass);
                }
            }
        }

    }

}

2.事件监听器(鼠标、按钮和文本框)

构造方法中需要同时引入涉及文本框、按钮和小球所在的类

如果点击鼠标,生成一个除了大小给定其他随机的彩色小球

如果点“Play” 文本框的信息被读取,生成指定的小球

如果点“Stop” 小球停止运动但不消失

如果点“Reset” 文本框恢复默认值,用户可以选择重新输入

如果点“Continue” 小球继续刚刚的运动

如果点“Clear” 小球停止运动且线程立即中断

代码如下:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.JFrame;

public class Listener extends MouseAdapter implements ActionListener,Runnable {

    private BallFrame bf;    
    private Graphics g;         
    private Random rand = new Random();
    private volatile boolean clearFlag = false, pauseFlag = false;
    private ArrayList ball = new ArrayList();
    Thread playing;
    public Listener(BallFrame bf, ArrayList ball) {
        this.bf = bf;
        this.ball = ball;
    }
    public void mousePressed(MouseEvent e) {
        int x = e.getX();    
        int y = e.getY();    
        Ball myball = new Ball(x, y, 30, (1+rand.nextInt(9)*(Math.random()>0.5?1:-1)),
                (1+rand.nextInt(9)*(Math.random()>0.5?1:-1)), 
                new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255)),rand.nextInt(9)+1);
        ball.add(myball);
    }
    @Override
    public void run() {
        while (!clearFlag) {
            if(!pauseFlag)
            {
                bf.repaint();    
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public void actionPerformed(ActionEvent event) {
        String command = event.getActionCommand();
        if (command.equals("Play")) {
            startPlaying();
        }
        if (command.equals("Stop")) {
            stopPlaying();
        }
        if (command.equals("Reset")) {
            setAllFields();
        }
        if (command.equals("Continue")) {
            continuePlaying();
        }
        if (command.equals("Clear")) {
            clearPlaying();
        }
    }
    void setAllFields() {
        bf.massText.setText("0");
        bf.xSpeedText.setText("0");
        bf.xPositionText.setText("0");
        bf.sizeText.setText("0");
        bf.ySpeedText.setText("0");
        bf.yPositionText.setText("0");
        bf.reset.setEnabled(false);
        bf.play.setEnabled(true);
        bf.Continue.setEnabled(false);
        bf.clear.setEnabled(true);
    }

    void startPlaying() {   
        playing = new Thread(this);
        playing.start();
        clearFlag = false;
        bf.play.setEnabled(false);
        bf.Continue.setEnabled(false);
        bf.stop.setEnabled(true);
        bf.reset.setEnabled(true);
        bf.clear.setEnabled(true);
        String xP=bf.xPositionText.getText();
        int x=Integer.parseInt(xP);
        String yP=bf.yPositionText.getText();
        int y=Integer.parseInt(yP);
        String Size=bf.sizeText.getText();
        int size=Integer.parseInt(Size);
        String Xspeed=bf.xSpeedText.getText();
        int xspeed=Integer.parseInt(Xspeed);
        String Yspeed=bf.ySpeedText.getText();
        int yspeed=Integer.parseInt(Yspeed);
        String Mass=bf.massText.getText();
        int mass=Integer.parseInt(Mass);
        Ball myball = new Ball(x, y, size, xspeed,yspeed, 
                new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255)),mass);
        ball.add(myball);
    }
    void stopPlaying() {
        bf.stop.setEnabled(false);
        bf.play.setEnabled(true);
        bf.reset.setEnabled(true);
        bf.Continue.setEnabled(true);
        bf.clear.setEnabled(true);
        pauseFlag=true;
    }
    void continuePlaying()
    {
        bf.stop.setEnabled(true);
        bf.play.setEnabled(true);
        bf.reset.setEnabled(true);
        bf.Continue.setEnabled(false);
        bf.clear.setEnabled(true);
        pauseFlag=false;
    }
    void clearPlaying()
    {
        bf.clear.setEnabled(false);
        bf.stop.setEnabled(false);
        bf.play.setEnabled(true);
        bf.reset.setEnabled(true);
        bf.Continue.setEnabled(false);
        playing = null;
        clearFlag = true;
        ball.clear();
        bf.repaint();
    }
}

GUI框架

需要文本框实现输入,两行,每行3个变量

需要5个按钮,分别代表"Play"、"Stop"、"Reset"、"Continue"、"Clear"

需要一个画布,可以显示出球类的动画

代码如下

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
public class BallFrame extends JFrame {
    private ArrayList ball = new ArrayList(); 
    private Image img;    
    private Graphics2D graph;
     // set up row 1
    JPanel row1 = new JPanel();
    JLabel mass = new JLabel("mass:", JLabel.RIGHT);
    JTextField massText = new JTextField("0");
    JLabel xSpeed = new JLabel("xSpeed:", JLabel.RIGHT);
    JTextField xSpeedText = new JTextField("0");
    JLabel xPosition = new JLabel("xPosition:", JLabel.RIGHT);
    JTextField xPositionText = new JTextField("0");
    JLabel size = new JLabel("size:", JLabel.RIGHT);
    JTextField sizeText = new JTextField("0");
    JLabel ySpeed = new JLabel("ySpeed:", JLabel.RIGHT);
    JTextField ySpeedText = new JTextField("0");
    JLabel yPosition = new JLabel("yPosition:", JLabel.RIGHT);
    JTextField yPositionText = new JTextField("0");
    // set up row 2
    JPanel row2 = new JPanel();
    JButton stop = new JButton("Stop");
    JButton Continue = new JButton("Continue");
    JButton clear = new JButton("Clear");
    JButton play = new JButton("Play");
    JButton reset = new JButton("Reset");
    public BallFrame()
    {
        super("BallGame!");
        setSize(600, 600);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        /**
         * set to BorderLayout
         * This borderLayout do nothing important except makes our rows at the top.
         * if user perfer bottom, all we need is to change "NORTH" into "South".
         */
        setLayout(new BorderLayout());
        add(row1, BorderLayout.NORTH);
        add(row2, BorderLayout.NORTH);
        //set to GridLayout whole framework
        GridLayout layout = new GridLayout(5,2,10,10);
        setLayout(layout);
        //specify how many text fields should be filled in one line.
        GridLayout layout2 = new GridLayout(2, 3, 10, 10);
        row1.setLayout(layout2);
        // the following process is to add them into window
        row1.add(mass);
        massText=new JTextField();
        row1.add(massText);
        row1.add(xSpeed);
        xSpeedText=new JTextField();
        row1.add(xSpeedText);
        row1.add(xPosition);
        xPositionText=new JTextField();
        row1.add(xPositionText);
        row1.add(size);
        sizeText=new JTextField();
        row1.add(sizeText);
        row1.add(ySpeed);
        ySpeedText=new JTextField();
        row1.add(ySpeedText);
        row1.add(yPosition);
        yPositionText=new JTextField();
        row1.add(yPositionText);
        add(row1);
        //make button in the center
        FlowLayout layout3 = new FlowLayout(FlowLayout.CENTER,
                10, 10);
        row2.setLayout(layout3);
        row2.add(play);
        row2.add(stop);
        row2.add(reset);
        row2.add(Continue);
        row2.add(clear);
        add(row2);
        
        setResizable(false);
        setVisible(true);
    }
    //Main function, nothing else need to explain...
    public static void main(String[] args) {
        BallFrame.setLookAndFeel();
        BallFrame bf = new BallFrame();
        bf.UI();
    }

    public void UI() {                               
        Listener lis = new Listener(this, ball);    
        // Add listeners
        this.addMouseListener(lis);    
        clear.addActionListener(lis);
        Continue.addActionListener(lis);
        stop.addActionListener(lis);
        play.addActionListener(lis);
        reset.addActionListener(lis);
        Thread current = new Thread(lis);     
        current.start();                        

    }
    /**
     * This method is to ensure across operation system could have a ability to show window
     */
    private static void setLookAndFeel() {
        try {
            UIManager.setLookAndFeel(
                "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"
            );
        } catch (Exception exc) {
            // ignore error
        }
    }
    
    public void paint(Graphics g) {
        img = this.createImage(this.getWidth(), this.getHeight());    
        graph = (Graphics2D)img.getGraphics();;        
        graph.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON); 
        for (int i = 0; i < ball.size(); i++) {
            Ball myball = (Ball) ball.get(i);            
            myball.moveBall(graph,this);                    
            myball.collision(graph,ball);
            myball.moveBall(graph,this);
            myball.drawBall(graph);                
        } 
        //If the canvas"s height over 160, it will hide our buttons.
        g.drawImage(img, 0, 160, this);    
    }
    
}

输出窗口的画面截图

总结

最近初学GUI,学校讲课用的是小球运动的例子,大一暑假拜读认识的学姐的一篇文章:我说这是一篇很严肃的技术文章你信吗,出于搞清楚GUI原理和好玩的目的,我就开始了我的改进之路。总体来说是符合我的预期的,但还是有两个问题:

忽视了错误输入可能造成异常的结果,暂时没学到在GUI如果输入错误怎么弹出界面,不过应该挺简单的,学到了再补。

动能定理的实践出了问题,因为鼠标点击的小球生成是随机的,无法得知每个小球的情况,动能定理公式给定了,可能是算法不够精致。

Github源码

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/23731.html

相关文章

  • Java 实现小球碰撞GUI

    摘要:球类的构造函数。如果点文本框的信息被读取,生成指定的小球。所有小球组成的列表。框架需要文本框实现输入,两行,每行个变量。用于一行一行的放置文本框和按钮类的构造函数。 最后一次更新于2019/07/08修复问题: 错误输入未提醒问题 碰撞小球的图形重叠问题 高速小球越界问题 感谢 大一暑假拜读学姐的一篇文章:我说这是一篇很严肃的技术文章你信吗,本篇在她的基础上加以改进。 效果演示图 ...

    Dogee 评论0 收藏0
  • 【带着canvas去流浪(8)】碰撞

    摘要:为了配合显示器刷新,我们可以使用另一个方法,这是中专门用来绘制逐帧动画的,它会配合显示器的刷新频率进行必要的图像更新,节省不必要的性能浪费。 showImg(https://segmentfault.com/img/bVbtnTv?w=700&h=256); 示例代码托管在:http://www.github.com/dashnowords/blogs博客园地址:《大史住在大前端》原创...

    xushaojieaaa 评论0 收藏0
  • js+canvas仿微信《弹一弹》小游戏

    摘要:在弹一弹游戏中,小球不能向上发射。这里又有一个坑弹一弹游戏中,刚射击出去的小球是不受重力影响的不然瞄准还有什么意义。 前言 半年前用js和canvas仿了热血传奇网游(地址),基本功能写完之后,剩下的都是堆数据、堆时间才能完成的任务了,没什么新鲜感,因此进度极慢。这次看到微信《弹一弹》比较火,因为涉及到物理引擎(为了真实),于是动手试了一下。一共用了10个小时,不仅完成了这个demo,...

    Invoker 评论0 收藏0
  • 【Hello CSS】第九章-如何画一个体验更好动画?

    摘要:那就是重力加速度的表现以及弹力球与空气,地面所产生的摩擦力的表现。弹力球下落时,由于重力加速度的原因,所以速度会越来越大,往上跳时速度会越来越小直至。 作者:陈大鱼头 github: KRISACHAN 在上一节中, 不走心 地画了一些 CSS图案 ,本节就继续不走心地 画动画 。 CSS的动画属性 在CSS中,animation 、 transition 跟 transform...

    BothEyes1993 评论0 收藏0
  • 梯度下降优化算法概述

    摘要:而基于梯度更新也意味着面临一些挑战选择恰当的初始学习率很困难,学习率太大会妨碍收敛,导致损失函数在最小值附近振荡甚至偏离最小值非凸的损失函数优化过程存在大量的局部最优解或鞍点参数更新采用相同的学习率。 感谢阅读「美图数据技术团队」的原创文章,关注我们持续获取美图最新数据技术动态。 平时我们说的训练神经网络就是最小化损失函数的过程,损失函数的值衡量了模型在给定数据集下的表现(拟合)能力。...

    cncoder 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<