摘要:球類的構造函數。如果點文本框的信息被讀取,生成指定的小球。所有小球組成的列表??蚣苄枰谋究驅崿F輸入,兩行,每行個變量。用于一行一行的放置文本框和按鈕類的構造函數。
最后一次更新于2019/07/08
修復問題:
錯誤輸入未提醒問題
碰撞小球的圖形重疊問題
高速小球越界問題
感謝大一暑假拜讀學姐的一篇文章:我說這是一篇很嚴肅的技術文章你信嗎,本篇在她的基礎上加以改進。
效果演示圖 基本思路要實現小球運動,可以從以下幾點切入:
1. 小球都有那些具體特征?
涉及動能定理就需要考慮質量了,除此之外常規的幾個變量也不能忘:方向、球的尺寸,所在位置以及當前速度。
2. 誰能初始小球的狀態?
小球的狀態無非兩種:(隨機)默認值、人工手動輸入。
3. 誰能控制小球的運動?
我們控制小球是需要給予一定的指令的,就算是鼠標點擊也是簡單的指令之一,除此之外如果想要擁有稍微復雜一點的指令可以使用按鈕來實現。
根據分析,我們大概能構造出大概的類,無非是一個專門描述小球狀態的,一個控制所有命令的,一個構建出窗口和選項的。我們還可以在細化這三個類的功能:
球類因為窗口也是二維的,構造方法僅需要包含:質量,沿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}}}$$
碰撞的兩球形狀盡量不要重疊,當程序檢測到這種意外時要盡可能拉開兩球之間的距離,直到兩球距離恰好等于兩球半徑之和。
代碼如下:
/** * 這個類主要是用來設置小球的各種屬性以及運動關系。 * @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事件監聽器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 ArrayListGUI框架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; } }
需要文本框實現輸入,兩行,每行3個變量。
需要5個按鈕,分別代表 "Play"、"Stop"、"Reset"、"Continue"、"Clear"。
需要一個畫布,可以顯示出球類的動畫。
代碼如下
/** * 該類主要用于繪制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 = new ArrayList (); 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(); } /** * 這種方法是為了確??绮僮飨到y能夠顯示窗口。 */ 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...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69031.html
摘要:在彈一彈游戲中,小球不能向上發射。這里又有一個坑彈一彈游戲中,剛射擊出去的小球是不受重力影響的不然瞄準還有什么意義。 前言 半年前用js和canvas仿了熱血傳奇網游(地址),基本功能寫完之后,剩下的都是堆數據、堆時間才能完成的任務了,沒什么新鮮感,因此進度極慢。這次看到微信《彈一彈》比較火,因為涉及到物理引擎(為了真實),于是動手試了一下。一共用了10個小時,不僅完成了這個demo,...
摘要:我們需要使用到的方法有第一步繪制一個小球畫布的寬畫布的高定義圓心坐標定義圓心坐標定義半徑清除畫布開始繪制畫圓圓的填充顏色閉合路徑填充在線預覽第二步讓小球動起來讓小球動起來的原理就是,不斷地改變小球的坐標位置并進行重繪。 我們需要使用到Canvas的方法有: context.arc(x, y, r, sAngle, eAngle, counterclockwise); 第一步:繪制一個小...
摘要:最近在學習碰撞檢測相關的知識,但說實話,寫的不咋地。因為真本來是正方形所以檢測的不是很準確。下面來批評一下這個的代碼。碰撞檢測的代碼因為碰撞可以分為這四個角度。因為使用了幾乎一直在重排,所以性能不是最好的,但效果基本上實現了。 最近在學習碰撞檢測相關的知識,但說實話,寫的不咋地。但是因為鄙人學識淺薄,所以覺得基本上還行,但是挺追求我完美的,所以想拿出來讓大神們批評批評。 先來看一下效果...
閱讀 1849·2023-04-26 01:58
閱讀 1982·2019-08-30 11:26
閱讀 2730·2019-08-29 12:51
閱讀 3495·2019-08-29 11:11
閱讀 1183·2019-08-26 11:54
閱讀 2096·2019-08-26 11:48
閱讀 3478·2019-08-26 10:23
閱讀 2384·2019-08-23 18:30