最後一次更新於2019/07/08
修復問題:java
大一暑假拜讀學姐的一篇文章:我說這是一篇很嚴肅的技術文章你信嗎,本篇在她的基礎上加以改進。git
要實現小球運動,能夠從如下幾點切入:github
1. 小球都有那些具體特徵?
涉及動能定理就須要考慮質量了,除此以外常規的幾個變量也不能忘:方向、球的尺寸,所在位置以及當前速度。
2. 誰能初始小球的狀態?
小球的狀態無非兩種:(隨機)默認值、人工手動輸入。
3. 誰能控制小球的運動?
咱們控制小球是須要給予必定的指令的,就算是鼠標點擊也是簡單的指令之一,除此以外若是想要擁有稍微複雜一點的指令可使用按鈕來實現。框架
根據分析,咱們大概能構造出大概的類,無非是一個專門描述小球狀態的,一個控制全部命令的,一個構建出窗口和選項的。咱們還能夠在細化這三個類的功能:dom
由於窗口也是二維的,構造方法僅須要包含:質量,沿x、y軸的分速度,二維座標表示的位置,顏色,大小, 所在的畫板
。
小球的外在屬性:顏色,尺寸
。
小球的移動狀況:ide
代碼以下:函數
/** * 這個類主要是用來設置小球的各類屬性以及運動關係。 * @author Hephaest * @version 2019/7/5 * @since jdk_1.8.202 */ import java.awt.Color; import java.awt.Graphics; import java.util.ArrayList; public class Ball{ /** * 聲明小球的各類變量。 */ private int xPos, yPos, size, xSpeed, ySpeed,mass; private Color color; private BallFrame bf; /** * 球類的構造函數。 * @param xPos 小球在X軸上的位置。 * @param yPos 小球在Y軸上的位置。 * @param size 小球的直徑長度。 * @param xSpeed 小球在X軸上的分速度。 * @param ySpeed 小球在Y軸上的分速度。 * @param color 小球的顏色。 * @param mass 小球的質量。 * @param bf 當前小球所在的畫板。 */ public Ball(int xPos, int yPos, int size, int xSpeed, int ySpeed, Color color, int mass, BallFrame bf) { super(); this.xPos = xPos; this.yPos = yPos; this.size = size; this.xSpeed = xSpeed; this.ySpeed = ySpeed; this.color = color; this.mass = mass; this.bf = bf; } /** * 在畫板上繪製小球。 * @param g 當前小球。 */ public void drawBall(Graphics g) { if(xPos + size> bf.getWidth() - 4) xPos = bf.getWidth() - size - 4; else if(xPos < 4) xPos = 4; if(yPos < 4) yPos = 4; else if(yPos > bf.getHeight()) yPos = bf.getHeight() - size - 4; g.setColor(color); g.fillOval(xPos, yPos, size, size); } /** * 該方法是用來判斷下一秒小球的移動方向並計算當前小球的位置。 * @param bf 當前小球所在的畫板。 */ public void moveBall(BallFrame bf) { if (xPos + size + xSpeed > bf.getWidth() - 4 || xPos + xSpeed < 4) { xSpeed = -xSpeed; } if (yPos + ySpeed < 2 || yPos + size + ySpeed > bf.getHeight() - 163) { ySpeed = - ySpeed; } xPos += xSpeed; yPos += ySpeed; } /** * 該方法是用於判斷碰撞是否發生了,若是發生了,儘可能避免小球形狀之間的重疊。 * @param balls 全部小球。 */ public void collision(ArrayList<Ball> balls) { for (int i = 0; i < balls.size(); i++) { 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)) { if (this.xPos > ball.xPos) { xPos++; while(Math.sqrt(Math.pow(this.xPos - ball.xPos,2) + Math.pow(d2,2)) < this.size / 2 + ball.size / 2) xPos++; } else { ball.xPos++; while(Math.sqrt(Math.pow(ball.xPos - this.xPos,2) + Math.pow(d2,2)) < this.size / 2 + ball.size / 2) ball.xPos++; } /* 應用完美彈性碰撞的速度公式 */ 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); } } } } }
Play
文本框的信息被讀取,生成指定的小球。Stop
小球中止運動但不消失。Reset
文本框恢復默認值,用戶能夠選擇從新輸入。Continue
小球繼續剛剛的運動。Clear
小球中止運動且線程當即中斷。代碼以下:動畫
/** * 此類是用於監聽 BallFrame GUI 的文字輸入和按監聽的。 * 用戶能夠輸入參數而後點擊按鈕"Play"或者在畫板中指定區域單機鼠標生成小球。 * @author Hephaest * @version 2019/7/5 * @since jdk_1.8.202 */ import java.awt.Color; 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 java.util.regex.Pattern; import javax.swing.*; public class Listener extends MouseAdapter implements ActionListener,Runnable { /** * 聲明監聽器裏的全部變量。 * 須要注意什麼時候更改 clearFlag 和 pauseFlag 的布爾值。 */ private BallFrame bf; private Random rand = new Random(); private volatile boolean clearFlag = false, pauseFlag = false; private ArrayList<Ball> ball; Thread playing; /** * 監聽器的構造函數。 * @param bf BallFrame 類的實例。 * @param ball 全部小球組成的列表。 */ 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(); if(x + 50 > bf.getWidth() - 4) x = bf.getWidth() - 54; else if(x < 4) x = 4; if(y < 163) y = 163; else if(y + 50 > bf.getHeight()) y = bf.getHeight() - 46; Ball newBall = new Ball(x, y - 163, 50, (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, bf); ball.add(newBall); } @Override /** * 該方法是 Runnable 的重寫。 * 若是用戶選擇暫停的話,須要中止畫板刷新和新的繪製。 */ 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")) { if (checkValid(bf.massText.getText(), bf.sizeText.getText(), bf.xPositionText.getText(), bf.yPositionText.getText())) { startPlaying(); } else { JOptionPane.showMessageDialog(null, "Please enter correct numbers!"); } } if (command.equals("Stop")) { stopPlaying(); } if (command.equals("Reset")) { setAllFields(); } if (command.equals("Continue")) { continuePlaying(); } if (command.equals("Clear")) { clearPlaying(); } } /** * 該方法用來響應 "Reset" 按鈕。 * 每一個文本框都設置默認值。 * 重置完後沒法再點擊 "Reset" 或 "Continue"。 */ void setAllFields() { bf.massText.setText("1"); bf.xSpeedText.setText("1"); bf.xPositionText.setText("0"); bf.sizeText.setText("50"); bf.ySpeedText.setText("1"); bf.yPositionText.setText("0"); bf.reset.setEnabled(false); bf.play.setEnabled(true); bf.Continue.setEnabled(false); bf.clear.setEnabled(true); } /** * 該方法用來響應 "Play" 按鈕。 * 須要建立一個新的進程並設置 clearFlag 爲 false, 這樣 run() 函數能夠正常運行。 * 運行完後沒法再點擊 "play" again 或 "Continue"。 */ 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, bf); ball.add(myball); } /** * 該方法用來響應 "Stop" 按鈕。 * 這個不須要從新繪製。 * 用戶沒法再點擊 "Stop" 按鈕。 */ void stopPlaying() { bf.stop.setEnabled(false); bf.play.setEnabled(true); bf.reset.setEnabled(true); bf.Continue.setEnabled(true); bf.clear.setEnabled(true); pauseFlag=true; } /** * 該方法用來響應 "Continue" 按鈕。 * 須要設置 pauseFlag 的值用來一遍又一遍地重繪窗口。 * 須要記住線程 "Playing" 仍在工做! * 用戶沒法再點擊 "Continue" 按鈕。 */ void continuePlaying() { bf.stop.setEnabled(true); bf.play.setEnabled(true); bf.reset.setEnabled(true); bf.Continue.setEnabled(false); bf.clear.setEnabled(true); pauseFlag = false; } /** * 該方法用來響應 "Clear" 按鈕。 * 經過將線程聲明爲null來減小CPU的浪費。 * 須要清空全部小球並從新繪製。 * 用戶沒法再點擊 "Clear" 或 "Stop" 或 "Continue" 按鈕。 */ 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(); } /** * 覈查用戶在文本框裏的輸入是否正確。 * @param mass 小球的質量。 * @param size 小球的直徑。 * @param xPos 小球在X軸的位置。 * @param yPos 小球在Y軸的位置。 * @return 返回覈驗結果。 */ private boolean checkValid(String mass, String size, String xPos, String yPos) { Pattern pattern = Pattern.compile("[0-9]*"); if (!pattern.matcher(mass).matches() || !pattern.matcher(size).matches() || !pattern.matcher(xPos).matches() || !pattern.matcher(yPos).matches()) return false; else if (Integer.parseInt(mass) <= 0 || Integer.parseInt(size) <= 0 || Integer.parseInt(xPos) < 0 || Integer.parseInt(yPos) < 0) return false; else return true; } }
代碼以下this
/** * 該類主要用於繪製GUI。 * @author Hephaest * @version 2019/7/5 * @since jdk_1.8.202 */ 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 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> ball = new ArrayList<Ball>(); private Image img; private Graphics2D graph; /** * JPanel 用於一行一行的放置文本框和按鈕 */ JPanel row1 = new JPanel(); JLabel mass = new JLabel("mass:", JLabel.RIGHT); JTextField massText, xSpeedText, xPositionText, sizeText, ySpeedText, yPositionText; JLabel xSpeed = new JLabel("xSpeed:", JLabel.RIGHT); JLabel xPosition = new JLabel("xPosition:", JLabel.RIGHT); JLabel size = new JLabel("size:", JLabel.RIGHT); JLabel ySpeed = new JLabel("ySpeed:", JLabel.RIGHT); JLabel yPosition = new JLabel("yPosition:", JLabel.RIGHT); 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"); /** * BallFrame 類的構造函數。 */ public BallFrame() { super("BallGame"); setSize(600, 600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //使第一個模塊都是文本框。 row1.setLayout(new GridLayout(2, 3, 10, 10)); //把文本框和標籤加到row1。 row1.add(mass); massText = new JTextField("1"); row1.add(massText); row1.add(xSpeed); xSpeedText = new JTextField("1"); row1.add(xSpeedText); row1.add(xPosition); xPositionText = new JTextField("0"); row1.add(xPositionText); row1.add(size); sizeText = new JTextField("50"); row1.add(sizeText); row1.add(ySpeed); ySpeedText = new JTextField("1"); row1.add(ySpeedText); row1.add(yPosition); yPositionText = new JTextField("0"); row1.add(yPositionText); add(row1,"North"); //使按鈕居中。 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); } //主函數。 public static void main(String[] args) { BallFrame.setLookAndFeel(); BallFrame bf = new BallFrame(); bf.UI(); } /** * 添加監聽器。 */ public void UI() { Listener lis = new Listener(this, ball); 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(); } /** * 這種方法是爲了確保跨操做系統可以顯示窗口。 */ private static void setLookAndFeel() { try { UIManager.setLookAndFeel( "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel" ); } catch (Exception exc) { // 忽略。 } } /** * 該方法是用於重繪不一樣區域的畫布。 */ public void paint(Graphics g) { // Panel須要被重繪,否則沒法顯示。 row1.repaint(0,0,this.getWidth(), 80); row2.repaint(0,0,this.getWidth(), 42); img = this.createImage(this.getWidth(), this.getHeight()); graph = (Graphics2D)img.getGraphics(); //渲染使無鋸齒。 graph.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); graph.setBackground(getBackground()); //遍歷更新每個小球的運動狀況。 for (int i = 0; i < ball.size(); i++) { Ball myBall = ball.get(i); myBall.drawBall(graph); myBall.collision(ball); myBall.moveBall(this); } g.drawImage(img, 0, 150, this); } }
若是個人文章能夠幫到您,勞煩您點進源碼點個 ★ Star 哦!
https://github.com/Hephaest/B...spa