用java模擬多球運動及碰撞

哈哈...經過上兩篇文章以後,現在可以模擬出Vista七彩泡泡屏保的效果了(雖然模擬出來的效果跟屏保的效果暫時還相差很大,但原理是出來啦,而且更重要的是,我們能夠模擬出來了),下面是代碼.

/*
 * @(#)Ball.java 2008年2月27日
 */
import java.awt.Graphics;
import java.awt.Color;

/**
 * Ball類代表一個球
 * @author Bird
 * @version 1.0 2008年2月27日
 * @since jdk1.6.0
 */
public class Ball {
 
 private final int RADIUS = 40;     //球的半徑
 private int ballX, ballY;         //球目前的位置
 private double xSpeed, ySpeed;         //球在X軸及Y軸的分速度
 private int screenWidth, screenHeight;   //屏寬,高
 
 public Ball(double xSpeed, double ySpeed, int w, int h){
  this.xSpeed = xSpeed;
  this.ySpeed = ySpeed;
  screenWidth = w;
  screenHeight = h;
 }
 /**
  * 畫出當前球
  * @param g
  */
 public void paint(Graphics g){
  g.setColor(Color.RED);
  g.fillArc(ballX, ballY, RADIUS*2, RADIUS*2, 0, 360);
 }
 /**
  * 取得當前球的x座標
  * @return x座標
  */
 public int getBallX(){
  return ballX;
 }
 /**
  * 取得當前球的y座標
  * @return y座標
  */
 public int getBallY(){
  return ballY;
 }
 /**
  * 設置新的x座標
  * @param x
  */
 public void setBallPos(int x, int y){
  ballX = x;
  ballY = y;
 }
 /**
  * 取得X軸上的分速度
  * @return
  */
 public double getXSpeed(){
  return xSpeed;
 }
 /**
  * 取得y軸上的分速度
  * @return
  */
 public double getYSpeed(){
  return ySpeed;
 }
 /**
  * 設置新的x軸分速度
  * @param xs
  */
 public void setSpeed(double xs, double ys){
  xSpeed = xs;
  ySpeed = ys;
 }
 
 /**
  * 重新設置屏幕大小
  */
 public void setScreen(int w, int h){
  screenWidth = w;
  screenHeight = h;
 }
 
 /**
  * 球運動
  */
 public void ballMove(){
  if(ballX + xSpeed + 2*RADIUS > screenWidth ||
    ballX + xSpeed < 0){         //當在X軸上碰到牆時,X軸行進方向改變
   xSpeed*=-1;
  }else{
   ballX += xSpeed;                 //沒碰壁時繼續前進
  }
  if(ballY + ySpeed + 2*RADIUS > screenHeight ||
    ballY + ySpeed < 0){         //當在Y軸上碰到牆時,Y軸行進方向改變
   ySpeed*=-1;
  }else{
   ballY += ySpeed;
  }
 }
 
 /**
  * 檢查兩球是否發生碰撞,若碰撞進行碰撞處理
  * @param ball 另一個球s
  */
 public void ballCollide(Ball ball){
  
  int bx = ball.getBallX();               //位置
  int by = ball.getBallY();
  
  double bXSpeed = ball.getXSpeed();       //速度
  double bYSpeed = ball.getYSpeed();
  
  double sx = ballX - bx;                  //兩球距離
  double sy = ballY - by;
  
  int s = (int)Math.hypot(sx, sy);
  if(s > 2.*RADIUS) return;          //不發生碰撞
  
  // 求出s1
  double s1x = sx / Math.sqrt(sx*sx + sy*sy) ;
  double s1y = sy / Math.sqrt(sx*sx + sy*sy) ;
  // 求出t'
  double tx = -sy ;
  double ty = sx ;
  // 求出t1
  double t1x = tx / Math.sqrt(tx*tx + ty*ty) ;
  double t1y = ty / Math.sqrt(tx*tx + ty*ty) ;
  // 求v1a在s1上的投影v1s
  double v1s = xSpeed * s1x + ySpeed * s1y ;
  // 求v1a在t1上的投影v1t
  double v1t = xSpeed * t1x + ySpeed * t1y ;
  // 求v2a在s1上的投影v2s
  double v2s = bXSpeed * s1x + bYSpeed * s1y ;
  // 求v2a在t1上的投影v2t
  double v2t = bXSpeed * t1x + bYSpeed * t1y ;
  // 用公式求出v1sf和v2sf
  double v1sf = v2s ;
  double v2sf = v1s ;
  // 最後一步,注意這裏我們簡化一下,直接將v1sf,v1t和v2sf,v2t投影到x,y軸上,也就是v1'和v2'在x,y軸上的分量
  // 先將v1sf和v1t轉化爲向量
  double nsx = v1sf * s1x ;
  double nsy = v1sf * s1y ;
  double ntx = v1t * t1x ;
  double nty = v1t * t1y ;
  // 投影到x軸和y軸
  
  xSpeed = nsx + ntx ;
  ySpeed = nsy + nty ;
  // 然後將v2sf和v2t轉化爲向量
  nsx = v2sf * s1x ;
  nsy = v2sf * s1y ;
  ntx = v2t * t1x ;
  nty = v2t * t1y ;
  // 投影到x軸和y軸
 
  bXSpeed = nsx + ntx ;
  bYSpeed = nsy + nty ;
  
  while(Math.sqrt((ballX-bx)*(ballX-bx) +              //仍舊處於碰撞時的位置
    (ballY-by)*(ballY-by)) < 2*RADIUS){
   if(ballX + xSpeed + 2*RADIUS > screenWidth ||
     ballX + xSpeed < 0){         //當在X軸上碰到牆時,X軸行進方向改變
    xSpeed*=-1;
   }else{
    ballX += xSpeed;                 //沒碰壁時繼續前進
   }
   if(ballY + ySpeed + 2*RADIUS > screenHeight ||
     ballY + ySpeed < 0){         //當在Y軸上碰到牆時,Y軸行進方向改變
    ySpeed*=-1;
   }else{
    ballY += ySpeed;
   }
   if(bx + bXSpeed + 2*RADIUS > screenWidth ||
     bx + bXSpeed < 0){         //當在X軸上碰到牆時,X軸行進方向改變
    bXSpeed*=-1;
   }else{
    bx += bXSpeed;                 //沒碰壁時繼續前進
   }
   if(by + bYSpeed + 2*RADIUS > screenHeight ||
     by + bYSpeed < 0){         //當在Y軸上碰到牆時,Y軸行進方向改變
    bYSpeed*=-1;
   }else{
    by += bYSpeed;
   }
  }
  ball.setSpeed(bXSpeed, bYSpeed);   //重新設置速度及位置
  ball.setBallPos(bx, by);          
 }
}
/*
 * @(#)ManyBalls.java 2008年2月27日
 */
import java.util.Random;
import java.awt.Graphics;

/**
 * ManyBalls類表示一羣球
 * @author Bird
 * @version 1.0 2008年2月27日
 * @since jdk1.6.0
 */
public class ManyBalls extends Thread {
 
 private final int BALL_NUMBER = 10;         //球的個數
 private final int RADIUS = 40;              //球的半徑
 private Ball b[] = new Ball[BALL_NUMBER];
 
 private Random r;
 
 private boolean move = true;
 
 public ManyBalls(int w, int h){
  r = new Random();
  for(int i =0; i < b.length; i ++){
   b[i] = new Ball(r.nextInt(8), r.nextInt(7),w,h);
   b[i].setBallPos((2*i+2)*RADIUS, h/2);         //此處確保各個球不會發生碰撞
  }
 }
 /**
  * 將所有球畫到畫面上
  * @param g
  */
 public void paint(Graphics g){
  for(int i = 0; i < b.length; i++){
   b[i].paint(g);
  }
 }
 public void run(){
  while(move){
   for(int i = 0; i < b.length; i++){
    b[i].ballMove();
    for(int j = 0; j< b.length; j++){
     if(i != j){
      b[i].ballCollide(b[j]);
     }
    }
   }
   try{
    Thread.sleep(5);
   }catch(InterruptedException e){
    
   }
  }
 }
 /**
  * 重設屏幕大小
  * @param w 屏寬
  * @param h 屏高
  */
 public void screenResize(int w, int h){
  for(int i = 0; i < b.length; i++){
   b[i].setScreen(w, h);
  }
 }
 /**
  * 退出程序
  */
 public void exit(){
  move = false;
 }
}
/*
 * @(#)BallCanvas.java 2008年2月27日
 */
import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Image;

/**
 * BallCanvas類是畫布類
 * @author Bird
 * @version 1.0 2008年2月27日
 * @since jdk1.6.0
 */
public class BallCanvas extends Canvas implements Runnable {
 
 private ManyBalls balls;
 private int screenWidth, screenHeight;           //屏寬及高
 private boolean running = true;                 //標誌程序是否運行
 private Image image;                            //雙緩衝區
 private Graphics bg;
 
 public BallCanvas(int w, int h){
  screenWidth = w;
  screenHeight =h;
  balls = new ManyBalls(w,h);
  balls.start();
 }
 /**
  * 爲了避免屏幕閃動,用雙緩衝
  * 此處重載update()而不是repaint(),重載repaint()仍然會使屏幕閃爍
  */
 public void update(Graphics g){
  if(image == null){
   image = this.createImage(screenWidth, screenHeight);
   bg = image.getGraphics();           //噹噹前組件不可視時,bg將返回null,
                                       //所以若在構造子中用這兩條語句將導致程序異常
  }
  bg.setColor(this.getBackground());
  bg.fillRect(0, 0, screenWidth, screenHeight);
  balls.paint(bg);
  g.drawImage(image, 0, 0, this);
  /*g.setColor(Color.WHITE);
  g.fillRect(0, 0, screenWidth, screenHeight);
  balls.paint(g);*/
 }
 
 public void run() {
  while(running){
   repaint();
   try{
    Thread.sleep(5);
   }catch(InterruptedException e){
    
   }
  }
 }
 /**
  * 退出遊戲
  */
 public void exit(){
  running =false;
  balls.exit();
 }
 /**
  * 重新設置屏幕大小
  */
 public void canvasResize(){
  screenWidth = this.getWidth();
  screenHeight =this.getHeight();
  balls.screenResize(screenWidth, screenHeight);
 }

}
/*
 * @(#)Main.java 2008年2月27日
 */
import javax.swing.JFrame;
import java.awt.Container;
import java.awt.event.WindowAdapter;
import java.awt.event.ComponentAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.ComponentEvent;

/**
 * Main類是程序的主類
 * @author Bird
 * @version 2008年2月27日
 * @since jdk1.6.0
 *
 */
public class Main extends JFrame{
 
 private int screenWidth,screenHeight;
 private BallCanvas canvas = null;
 
 public Main(){
  
  setTitle("運動的球");
  setSize(1000,700);
  Container pane = this.getContentPane();
  
  screenWidth = this.getWidth();
  screenHeight = this.getHeight();
  
  canvas = new BallCanvas(screenWidth, screenHeight);
  //canvas = new BallCanvas();
  Thread t = new Thread(canvas);
  t.start();
  //當退出時,先結束Cavans所在線程
  this.addWindowListener(new WindowAdapter(){
    public void windowClosed(WindowEvent e){
     canvas.exit();
    }
  });
  //監聽窗口大小改變
  this.addComponentListener(new ComponentAdapter(){
   public void componentResized(ComponentEvent e){
    resize();
   }
  });
  pane.add(canvas);
  this.setVisible(true);
 }
 /**
  * 主函數
  */
 public void main(){
  Main m = new Main();
 }
 /**
  * 改變窗口的大小
  */
 public void resize(){
  screenWidth = this.getWidth();   //獲得新的窗口大小
  screenHeight = this.getHeight();
  canvas.canvasResize();            //通知Canvas改變其大小
 }
}
 這次貼出全部代碼,代碼是比較多,而且在畫布上畫圖的時候用到了雙緩衝,這樣可以有效地防止畫面抖動,可是也正是因爲雙緩衝的關係導致了模擬出來的效果比較不真實.而且球的運動速度也比較慢(原因在於有大量的計算及畫圖工作)但速度是可以提高的,這個只能等到以後再慢慢改進了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章