小球碰撞python代码_Java 实现小球碰撞GUI
最后一次更新于2019/07/08
修復(fù)問題:
錯(cuò)誤輸入未提醒問題
碰撞小球的圖形重疊問題
高速小球越界問題
感謝
大一暑假拜讀學(xué)姐的一篇文章:我說這是一篇很嚴(yán)肅的技術(shù)文章你信嗎,本篇在她的基礎(chǔ)上加以改進(jìn)。
效果演示圖
基本思路
要實(shí)現(xiàn)小球運(yùn)動(dòng),可以從以下幾點(diǎn)切入:
1. 小球都有那些具體特征?
涉及動(dòng)能定理就需要考慮質(zhì)量了,除此之外常規(guī)的幾個(gè)變量也不能忘:方向、球的尺寸,所在位置以及當(dāng)前速度。
2. 誰能初始小球的狀態(tài)?
小球的狀態(tài)無非兩種:(隨機(jī))默認(rèn)值、人工手動(dòng)輸入。
3. 誰能控制小球的運(yùn)動(dòng)?
我們控制小球是需要給予一定的指令的,就算是鼠標(biāo)點(diǎn)擊也是簡(jiǎn)單的指令之一,除此之外如果想要擁有稍微復(fù)雜一點(diǎn)的指令可以使用按鈕來實(shí)現(xiàn)。
根據(jù)分析,我們大概能構(gòu)造出大概的類,無非是一個(gè)專門描述小球狀態(tài)的,一個(gè)控制所有命令的,一個(gè)構(gòu)建出窗口和選項(xiàng)的。我們還可以在細(xì)化這三個(gè)類的功能:
球類
因?yàn)榇翱谝彩嵌S的,構(gòu)造方法僅需要包含:質(zhì)量,沿x、y軸的分速度,二維坐標(biāo)表示的位置,顏色,大小, 所在的畫板。
小球的外在屬性:顏色,尺寸。
小球的移動(dòng)情況:
遇到邊界直接反彈
移動(dòng)距離利用公式:距離 = 當(dāng)前距離 + 速度 × 時(shí)間(這邊直接簡(jiǎn)化成1)
小球之間的完全彈性碰撞公式:
$$\frac{{{\rm{(}}{{\rm{m}}_1} - {m_2}){v_{10}} + 2{m_2}{v_{20}}}}{{{{\rm{m}}_1} + {m_2}}}$$
$$\frac{{{\rm{(}}{{\rm{m}}_2} - {m_1}){v_{20}} + 2{m_1}{v_{10}}}}{{{{\rm{m}}_1} + {m_2}}}$$
碰撞的兩球形狀盡量不要重疊,當(dāng)程序檢測(cè)到這種意外時(shí)要盡可能拉開兩球之間的距離,直到兩球距離恰好等于兩球半徑之和。
代碼如下:
/**
* 這個(gè)類主要是用來設(shè)置小球的各種屬性以及運(yùn)動(dòng)關(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 小球的直徑長(zhǎng)度。
* @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)方向并計(jì)算當(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 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);
}
}
}
}
}
事件監(jiān)聽器
構(gòu)造方法中需要同時(shí)引入涉及文本框、按鈕和小球所在的類。
如果點(diǎn)擊鼠標(biāo),生成一個(gè)除了大小給定其他隨機(jī)的彩色小球。
如果點(diǎn) Play 文本框的信息被讀取,生成指定的小球。
如果點(diǎn) Stop 小球停止運(yùn)動(dòng)但不消失。
如果點(diǎn) Reset 文本框恢復(fù)默認(rèn)值,用戶可以選擇重新輸入。
如果點(diǎn) Continue 小球繼續(xù)剛剛的運(yùn)動(dòng)。
如果點(diǎn) Clear 小球停止運(yùn)動(dòng)且線程立即中斷。
代碼如下:
/**
* 此類是用于監(jiān)聽 BallFrame GUI 的文字輸入和按監(jiān)聽的。
* 用戶可以輸入?yún)?shù)然后點(diǎn)擊按鈕"Play"或者在畫板中指定區(qū)域單機(jī)鼠標(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)聽器里的所有變量。
* 需要注意何時(shí)更改 clearFlag 和 pauseFlag 的布爾值。
*/
private BallFrame bf;
private Random rand = new Random();
private volatile boolean clearFlag = false, pauseFlag = false;
private ArrayList ball;
Thread playing;
/**
* 監(jiān)聽器的構(gòu)造函數(shù)。
* @param bf BallFrame 類的實(shí)例。
* @param ball 所有小球組成的列表。
*/
public Listener(BallFrame bf, ArrayList ball) {
this.bf = bf;
this.ball = ball;
}
/**
* 每次點(diǎn)擊小球時(shí),只能直到生成小球的初始位置,但是它的速度分量都是隨機(jī)數(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" 按鈕。
* 每個(gè)文本框都設(shè)置默認(rèn)值。
* 重置完后無法再點(diǎ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" 按鈕。
* 需要?jiǎng)?chuàng)建一個(gè)新的進(jìn)程并設(shè)置 clearFlag 為 false, 這樣 run() 函數(shù)可以正常運(yùn)行。
* 運(yùn)行完后無法再點(diǎn)擊 "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" 按鈕。
* 這個(gè)不需要重新繪制。
* 用戶無法再點(diǎn)擊 "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" 仍在工作!
* 用戶無法再點(diǎn)擊 "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的浪費(fèi)。
* 需要清空所有小球并重新繪制。
* 用戶無法再點(diǎn)擊 "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 返回核驗(yàn)結(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;
}
}
GUI框架
需要文本框?qū)崿F(xiàn)輸入,兩行,每行3個(gè)變量。
需要5個(gè)按鈕,分別代表 "Play"、"Stop"、"Reset"、"Continue"、"Clear"。
需要一個(gè)畫布,可以顯示出球類的動(dòng)畫。
代碼如下
/**
* 該類主要用于繪制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);
//使第一個(gè)模塊都是文本框。
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();
}
/**
* 這種方法是為了確保跨操作系統(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());
//遍歷更新每一個(gè)小球的運(yùn)動(dòng)情況。
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);
}
}
源碼
如果我的文章可以幫到您,勞煩您點(diǎn)進(jìn)源碼點(diǎn)個(gè) ★ Star 哦!
總結(jié)
以上是生活随笔為你收集整理的小球碰撞python代码_Java 实现小球碰撞GUI的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android如何获取唯一ID
- 下一篇: matlab拟合函数参数,matlab怎