摘要:球類的構(gòu)造函數(shù)。如果點文本框的信息被讀取,生成指定的小球。所有小球組成的列表??蚣苄枰谋究?qū)崿F(xiàn)輸入,兩行,每行個變量。用于一行一行的放置文本框和按鈕類的構(gòu)造函數(shù)。
最后一次更新于2019/07/08
修復(fù)問題:
錯誤輸入未提醒問題
碰撞小球的圖形重疊問題
高速小球越界問題
感謝大一暑假拜讀學(xué)姐的一篇文章:我說這是一篇很嚴(yán)肅的技術(shù)文章你信嗎,本篇在她的基礎(chǔ)上加以改進。
效果演示圖 基本思路要實現(xiàn)小球運動,可以從以下幾點切入:
1. 小球都有那些具體特征?
涉及動能定理就需要考慮質(zhì)量了,除此之外常規(guī)的幾個變量也不能忘:方向、球的尺寸,所在位置以及當(dāng)前速度。
2. 誰能初始小球的狀態(tài)?
小球的狀態(tài)無非兩種:(隨機)默認(rèn)值、人工手動輸入。
3. 誰能控制小球的運動?
我們控制小球是需要給予一定的指令的,就算是鼠標(biāo)點擊也是簡單的指令之一,除此之外如果想要擁有稍微復(fù)雜一點的指令可以使用按鈕來實現(xiàn)。
根據(jù)分析,我們大概能構(gòu)造出大概的類,無非是一個專門描述小球狀態(tài)的,一個控制所有命令的,一個構(gòu)建出窗口和選項的。我們還可以在細(xì)化這三個類的功能:
球類因為窗口也是二維的,構(gòu)造方法僅需要包含:質(zhì)量,沿x、y軸的分速度,二維坐標(biāo)表示的位置,顏色,大小, 所在的畫板。
小球的外在屬性:顏色,尺寸。
小球的移動情況:
遇到邊界直接反彈
移動距離利用公式:距離 = 當(dāng)前距離 + 速度 × 時間(這邊直接簡化成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}}}$$
碰撞的兩球形狀盡量不要重疊,當(dāng)程序檢測到這種意外時要盡可能拉開兩球之間的距離,直到兩球距離恰好等于兩球半徑之和。
代碼如下:
/** * 這個類主要是用來設(shè)置小球的各種屬性以及運動關(guān)系。 * @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; /** * 球類的構(gòu)造函數(shù)。 * @param xPos 小球在X軸上的位置。 * @param yPos 小球在Y軸上的位置。 * @param size 小球的直徑長度。 * @param xSpeed 小球在X軸上的分速度。 * @param ySpeed 小球在Y軸上的分速度。 * @param color 小球的顏色。 * @param mass 小球的質(zhì)量。 * @param bf 當(dāng)前小球所在的畫板。 */ 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 當(dāng)前小球。 */ 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); } /** * 該方法是用來判斷下一秒小球的移動方向并計算當(dāng)前小球的位置。 * @param bf 當(dāng)前小球所在的畫板。 */ 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; } /** * 該方法是用于判斷碰撞是否發(fā)生了,如果發(fā)生了,盡量避免小球形狀之間的重疊。 * @param balls 所有小球。 */ public void collision(ArrayList事件監(jiān)聽器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++; } /* 應(yīng)用完美彈性碰撞的速度公式 */ 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); } } } } }
構(gòu)造方法中需要同時引入涉及文本框、按鈕和小球所在的類。
如果點擊鼠標(biāo),生成一個除了大小給定其他隨機的彩色小球。
如果點 Play 文本框的信息被讀取,生成指定的小球。
如果點 Stop 小球停止運動但不消失。
如果點 Reset 文本框恢復(fù)默認(rèn)值,用戶可以選擇重新輸入。
如果點 Continue 小球繼續(xù)剛剛的運動。
如果點 Clear 小球停止運動且線程立即中斷。
代碼如下:
/** * 此類是用于監(jiān)聽 BallFrame GUI 的文字輸入和按監(jiān)聽的。 * 用戶可以輸入?yún)?shù)然后點擊按鈕"Play"或者在畫板中指定區(qū)域單機鼠標(biāo)生成小球。 * @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 { /** * 聲明監(jiān)聽器里的所有變量。 * 需要注意何時更改 clearFlag 和 pauseFlag 的布爾值。 */ private BallFrame bf; private Random rand = new Random(); private volatile boolean clearFlag = false, pauseFlag = false; private ArrayListGUI框架ball; Thread playing; /** * 監(jiān)聽器的構(gòu)造函數(shù)。 * @param bf BallFrame 類的實例。 * @param ball 所有小球組成的列表。 */ public Listener(BallFrame bf, ArrayList ball) { this.bf = bf; this.ball = ball; } /** * 每次點擊小球時,只能直到生成小球的初始位置,但是它的速度分量都是隨機數(shù)。 */ 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(); } } } } /** * 該方法用來響應(yīng)不同按鈕的需求。 */ 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(); } } /** * 該方法用來響應(yīng) "Reset" 按鈕。 * 每個文本框都設(shè)置默認(rèn)值。 * 重置完后無法再點擊 "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); } /** * 該方法用來響應(yīng) "Play" 按鈕。 * 需要創(chuàng)建一個新的進程并設(shè)置 clearFlag 為 false, 這樣 run() 函數(shù)可以正常運行。 * 運行完后無法再點擊 "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); } /** * 該方法用來響應(yīng) "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; } /** * 該方法用來響應(yīng) "Continue" 按鈕。 * 需要設(shè)置 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; } /** * 該方法用來響應(yīng) "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 小球的質(zhì)量。 * @param size 小球的直徑。 * @param xPos 小球在X軸的位置。 * @param yPos 小球在Y軸的位置。 * @return 返回核驗結(jié)果。 */ 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; } }
需要文本框?qū)崿F(xiàn)輸入,兩行,每行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 類的構(gòu)造函數(shù)。 */ public BallFrame() { super("BallGame"); setSize(600, 600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //使第一個模塊都是文本框。 row1.setLayout(new GridLayout(2, 3, 10, 10)); //把文本框和標(biāo)簽加到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); } //主函數(shù)。 public static void main(String[] args) { BallFrame.setLookAndFeel(); BallFrame bf = new BallFrame(); bf.UI(); } /** * 添加監(jiān)聽器。 */ 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(tǒng)能夠顯示窗口。 */ private static void setLookAndFeel() { try { UIManager.setLookAndFeel( "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel" ); } catch (Exception exc) { // 忽略。 } } /** * 該方法是用于重繪不同區(qū)域的畫布。 */ 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...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/69031.html
摘要:在彈一彈游戲中,小球不能向上發(fā)射。這里又有一個坑彈一彈游戲中,剛射擊出去的小球是不受重力影響的不然瞄準(zhǔn)還有什么意義。 前言 半年前用js和canvas仿了熱血傳奇網(wǎng)游(地址),基本功能寫完之后,剩下的都是堆數(shù)據(jù)、堆時間才能完成的任務(wù)了,沒什么新鮮感,因此進度極慢。這次看到微信《彈一彈》比較火,因為涉及到物理引擎(為了真實),于是動手試了一下。一共用了10個小時,不僅完成了這個demo,...
摘要:我們需要使用到的方法有第一步繪制一個小球畫布的寬畫布的高定義圓心坐標(biāo)定義圓心坐標(biāo)定義半徑清除畫布開始繪制畫圓圓的填充顏色閉合路徑填充在線預(yù)覽第二步讓小球動起來讓小球動起來的原理就是,不斷地改變小球的坐標(biāo)位置并進行重繪。 我們需要使用到Canvas的方法有: context.arc(x, y, r, sAngle, eAngle, counterclockwise); 第一步:繪制一個小...
摘要:最近在學(xué)習(xí)碰撞檢測相關(guān)的知識,但說實話,寫的不咋地。因為真本來是正方形所以檢測的不是很準(zhǔn)確。下面來批評一下這個的代碼。碰撞檢測的代碼因為碰撞可以分為這四個角度。因為使用了幾乎一直在重排,所以性能不是最好的,但效果基本上實現(xiàn)了。 最近在學(xué)習(xí)碰撞檢測相關(guān)的知識,但說實話,寫的不咋地。但是因為鄙人學(xué)識淺薄,所以覺得基本上還行,但是挺追求我完美的,所以想拿出來讓大神們批評批評。 先來看一下效果...
閱讀 1872·2023-04-26 01:58
閱讀 1994·2019-08-30 11:26
閱讀 2737·2019-08-29 12:51
閱讀 3505·2019-08-29 11:11
閱讀 1192·2019-08-26 11:54
閱讀 2107·2019-08-26 11:48
閱讀 3489·2019-08-26 10:23
閱讀 2392·2019-08-23 18:30