用java模拟两球的随机运动及碰撞

前面已经实现了一个球在一个窗口中随机运动,下面将在前面的基础上实现两个球的随机运动及碰撞,此次的代码中用到的算法参考了Twinsen写的<向量几何在游戏编程中的使用>一文中关于两球碰撞时的算法.程序仍旧有两个类,一个Ball类,一个BallCanvas类,Ball类与前面相同,而BallCanvas类作了修改,修改后的代码如下:
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;

public class BallCanvas extends Canvas implements Runnable {
 
 private int ballRadius = 40;   //球的半径
 
 private int ballAX,ballAY;        //当前A,B球的位置
 private int ballBX,ballBY;
 
 private double ballAXMoveLength= 7;     //A,B球当前速度在X,Y轴上的分速度
 private double ballAYMoveLength = 9;    //为了减少计算中数据丢失,用double型而不用int型
 private double ballBXMoveLength = 6;    // 
 private double ballBYMoveLength = 5;     //如果用int型两球最终将停止运动
 
 private Random r;
 private boolean move = true;       //标志球是否移动
 private int screenWidth, screenHeight;        //屏幕宽及高
 
 private int ballARX, ballARY;  //A球圆心位置
 private int ballBRX, ballBRY;   //B球圆心位置
 
 
 public BallCanvas(int screenWidth, int screenHeight){
  
  r = new Random();
  
  ballAX = r.nextInt(screenWidth – 2*ballRadius);     //随机初始化A球的初始位置
  ballAY = r.nextInt(screenHeight – 2*ballRadius);
  
  do{        //随机初始化B球的初始位置,且确定A球跟B球不发生碰撞
   ballBX = r.nextInt(screenWidth – 2*ballRadius);    
   ballBY = r.nextInt(screenHeight – 2*ballRadius);
  }while((int)Math.sqrt((ballAX-ballBX)*(ballAX-ballBX) +
  (ballAY-ballBY)*(ballAY-ballBY)) < 2*ballRadius);
  
  this.screenHeight = screenHeight;
  this.screenWidth = screenWidth;
 }
 
 public void paint(Graphics g){
  //g.setColor(Color.WHITE);            //此处使用背景色而不用白色,如果要自己清空屏幕,则需加上缓存,不然会产生画面抖动
  //g.fillRect(0, 0, screenWidth, screenHeight);
  g.setColor(Color.RED);
  g.fillArc((int)ballAX, (int)ballAY, (int)ballRadius*2, (int)ballRadius*2, 0, 360);
  g.fillArc((int)ballBX, (int)ballBY,(int) ballRadius*2, (int)ballRadius*2, 0, 360);
 }
 /**
  * 退出
  */
 public void exit(){
  move = false;
 }
 
 public void run(){
  while(move){
   //处理A球
   if(ballAX + ballAXMoveLength + 2*ballRadius > screenWidth ||
     ballAX + ballAXMoveLength < 0){         //当在X轴上碰到墙时,X轴行进方向改变
    ballAXMoveLength*=-1;
   }else{
    ballAX += ballAXMoveLength;                 //没碰壁时继续前进
   }
   if(ballAY + ballAYMoveLength + 2*ballRadius > screenHeight ||
     ballAY + ballAYMoveLength < 0){         //当在Y轴上碰到墙时,Y轴行进方向改变
    ballAYMoveLength*=-1;
   }else{
    ballAY += ballAYMoveLength;
   }
   //处理B球
   if(ballBX + ballBXMoveLength + 2*ballRadius > screenWidth ||
     ballBX + ballBXMoveLength < 0){         //当在X轴上碰到墙时,X轴行进方向改变
    ballBXMoveLength*=-1;
   }else{
    ballBX += ballBXMoveLength;                 //没碰壁时继续前进
   }
   if(ballBY + ballBYMoveLength + 2*ballRadius > screenHeight ||
     ballBY + ballBYMoveLength < 0){         //当在Y轴上碰到墙时,Y轴行进方向改变
    ballBYMoveLength*=-1;
   }else{
    ballBY += ballBYMoveLength;
   }
   ballsCollide();   //检查两球是否碰撞
   repaint();    //更新画面
   try{
    Thread.sleep(10);
   }catch(InterruptedException e){
    
   }
  }
 }
 /**
  * 检查并处理两球碰撞
  * 此处用向量来计算两球碰撞后的速度,具体算法分析请参见”向量几何在游戏编程中的使用”
  */
 public void ballsCollide(){
  if(Math.sqrt((ballAX-ballBX)*(ballAX-ballBX) +              //并没有产生碰撞
    (ballAY-ballBY)*(ballAY-ballBY)) > 2*ballRadius){
    return;
  }else{           //碰撞了
   ballARX = ballAX + ballRadius;  //A球圆心位置
   ballARY = ballAY + ballRadius;
   ballBRX = ballBX + ballRadius;   //B球圆心位置
   ballBRY = ballBY + ballRadius;
   // 求出s'(球心连线上的向量)
   double sx = ballARX – ballBRX ;
   double sy = ballARY – ballBRY ;
   // 求出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 = ballAXMoveLength * s1x + ballAYMoveLength * s1y ;
   // 求v1a在t1上的投影v1t
   double v1t = ballAXMoveLength * t1x + ballAYMoveLength * t1y ;
   // 求v2a在s1上的投影v2s
   double v2s = ballBXMoveLength * s1x + ballBYMoveLength * s1y ;
   // 求v2a在t1上的投影v2t
   double v2t = ballBXMoveLength * t1x + ballBYMoveLength * 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 ;
   
   ballAXMoveLength =(nsx + ntx) ;
   ballAYMoveLength =(nsy + nty) ;
   // 然后将v2sf和v2t转化为向量
   nsx = v2sf * s1x ;
   nsy = v2sf * s1y ;
   ntx = v2t * t1x ;
   nty = v2t * t1y ;
   
   ballBXMoveLength = (nsx + ntx );
   ballBYMoveLength =(nsy + nty );
   
   //碰撞之后两球速度变化了,可是两球的位置仍处于碰撞时的位置,此时可能导致程序误以后两球再次发生碰撞
   //于是再次处理碰撞,造成程序的死循环,故碰撞后,两球应该以碰撞后的速度迅速产生一段距离避免碰撞的状态
   while(Math.sqrt((ballAX-ballBX)*(ballAX-ballBX) +              //仍旧处于碰撞时的位置
     (ballAY-ballBY)*(ballAY-ballBY)) < 2*ballRadius){
    if(ballAX + ballAXMoveLength + 2*ballRadius > screenWidth ||
      ballAX + ballAXMoveLength < 0){         //当在X轴上碰到墙时,X轴行进方向改变
     ballAXMoveLength*=-1;
    }else{
     ballAX += ballAXMoveLength;                 //没碰壁时继续前进
    }
    if(ballAY + ballAYMoveLength + 2*ballRadius > screenHeight ||
      ballAY + ballAYMoveLength < 0){         //当在Y轴上碰到墙时,Y轴行进方向改变
     ballAYMoveLength*=-1;
    }else{
     ballAY += ballAYMoveLength;
    }
    if(ballBX + ballBXMoveLength + 2*ballRadius > screenWidth ||
      ballBX + ballBXMoveLength < 0){         //当在X轴上碰到墙时,X轴行进方向改变
     ballBXMoveLength*=-1;
    }else{
     ballBX += ballBXMoveLength;                 //没碰壁时继续前进
    }
    if(ballBY + ballBYMoveLength + 2*ballRadius > screenHeight ||
      ballBY + ballBYMoveLength < 0){         //当在Y轴上碰到墙时,Y轴行进方向改变
     ballBYMoveLength*=-1;
    }else{
     ballBY += ballBYMoveLength;
    }
   }
  }
    
    
 }
 /**
  * 重新调整屏幕大小
  */
 public void canvasResize(){
  screenWidth = this.getWidth();
  screenHeight = this.getHeight();
 }
}
 

点赞