为了复习一下SurfaceView的使用,在此写了一个经典的小球碰撞检测例子程序,希望能够够帮助正在学习游戏的人。

先看一下效果图:

095724635.png

   下面我们就来逐一分析一下它的实现过程:

1.启动入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import  android.os.Bundle;
import  android.app.Activity;
import  android.view.Window;
import  android.view.WindowManager;
public  class  MainActivity  extends  Activity {
     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         //全屏设置
         requestWindowFeature(Window.FEATURE_NO_TITLE);
         this .getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                 WindowManager.LayoutParams.FLAG_FULLSCREEN);
         //将画布放进去
         GameView gameView =  new  GameView( this );
         setContentView(gameView);
     }
}


2.小球类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import  android.graphics.Canvas;
import  android.graphics.Color;
import  android.graphics.Paint;
/**
  * 小球实例
  * @author ZHF
  *
  */
public  class  Ball {
     int  x, y;   //小球的实时位置
     int  startX, startY;    //小球的初始位置
     float  vX, vY;     //小球的速度
     int  r;  //小球的半径
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
     double  startTimeX;   //开始时间
     double  startTimeY;   //开始时间
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
     BallThread ballThread;   //小球移动线程
     Paint paint =  new  Paint();   //画笔
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
     public  Ball( int  x,  int  y,  float  vX,  float  vY,  int  r) {
         this .x = x;
         this .y = y;
         this .startX = x;
         this .startY = y;
         this .vX = vX;
         this .vY = vY;
         this .r = r;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
         //为每个小球实例化一个独立的线程,在抬手时开启线程
         ballThread =  new  BallThread( this );
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
         paint.setColor(Color.RED);   //小球为红色实心
     }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
     /**绘画方法**/
     public  void  drawSelf(Canvas canvas) {
         canvas.drawCircle(x, y, r, paint);
     }
}


3.障碍物类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import  android.graphics.Canvas;
import  android.graphics.Color;
import  android.graphics.Paint;
/**
  * 障碍物
  *
  * @author ZHF
  *
  */
public  class  Obstruction {
     int  x, y;
     int  hWeight;   //宽度和高度一样
     Paint paint =  new  Paint();
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
     public  Obstruction( int  x,  int  y,  int  hWeight) {
         this .x = x;
         this .y = y;
         this .hWeight = hWeight;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
         paint.setColor(Color.GREEN);   //设置画笔颜色
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
     }
     public  void  drawSelf(Canvas canvas) {
         canvas.drawRect(x - hWeight, y - hWeight, x + hWeight, y + hWeight, paint);
     }
}

以上代码比较简单,在此不多做解释,下面主要来看一下两个主要线程类:


4.小球移动线程(碰撞检测):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
  * 小球移动和碰撞检测线程
  * @author ZHF
  *
  */
public  class  BallThread  extends  Thread {
     boolean  flag;    //标记线程是否开启
     Ball ball;   //小球
     double  currentTime;   //当前时间
                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
     public  BallThread(Ball ball) {
         flag =  true ;
         this .ball = ball;
     }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
     @Override
     public  void  run() {
         while (flag) {
             //调试:碰撞检测开始时间
             long  startTime = System.currentTimeMillis();
                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
              //计算出小球移动的时间片:将每次刷新分成若干时间小片段,用于计算每次时间小片段小球移动的距离
             currentTime = System.nanoTime();
             double  timeSpanX = (currentTime - ball.startTimeX) / 1000  / 1000  / 1000 ;
             double  timeSpanY = (currentTime - ball.startTimeY) / 1000  / 1000  / 1000 ;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
             int  xBackup = ball.x;  //保存小球的碰撞前的位置
             int  yBackup = ball.y;
             ball.x = ( int ) (ball.startX + ball.vX * timeSpanX); //小球移动的距离
             ball.y = ( int ) (ball.startY + ball.vY * timeSpanY);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
             //边界碰撞检测
             if ((ball.vX >  0  && (ball.x + ball.r) >=  479 ) || (ball.vX <  0  && (ball.x - ball.r) <=  0 )) {
                 ball.x = xBackup;
                 ball.vX =  0  - ball.vX;   //速度反向
                 ball.startTimeX = System.nanoTime();   //重新记录开始时间
                 ball.startX = ball.x;   //重新记录开始位置
             }
             if ((ball.vY >  0  && (ball.y + ball.r) >=  799 ) || (ball.vY <  0  && (ball.y - ball.r) <=  0 )) {
                 ball.y = yBackup;
                 ball.vY =  0  - ball.vY;    //速度反向
                 ball.startTimeY = System.nanoTime();    //重新记录开始时间
                 ball.startY = ball.y;    //重新记录开始位置
             }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
             //障碍物碰撞检测
             for ( int  i =  0 ; i < GameView.obstructList.size(); i++) {
                 Obstruction o = GameView.obstructList.get(i);
                 if (Math.abs(ball.x - o.x) < (ball.r + o.hWeight) && Math.abs(ball.y - o.y) < (ball.r + o.hWeight)){
                     if (Math.abs(xBackup - o.x) >= (ball.r + o.hWeight)) {
                         ball.x = xBackup;
                         ball.vX =   0  - ball.vX;
                         ball.startTimeX = System.nanoTime();
                         ball.startX = ball.x;
                     }
                     if (Math.abs(yBackup - o.y) >= (ball.r + o.hWeight)) {
                         ball.y = yBackup;
                         ball.vY =  0  - ball.vY;
                         ball.startTimeY = System.nanoTime();
                         ball.startY = ball.y;
                     }
                     break //跳出循环
                 }
             }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
             //调试:碰撞检测结束时间     实验证明碰撞加测基本不耗时间
             long  endTime = System.currentTimeMillis();
             System.out.println(endTime +  "----"  + startTime +  "= "  +(endTime - startTime));
                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
             try  {
                 Thread.sleep( 10 );
             catch  (Exception e) {
                 e.printStackTrace();
             }
         }
     }
}

分析:

   1.我们将刷新时间分割成:将每次刷新时间分成若干时间小片段timeSpanX和timeSpanY,用于计算每次时间小片段小球移动的距离.

   2.我们在小球与边界碰撞之前,记录一下时间startTime,在其与边界碰撞之后,我们将其x轴、y轴方向上做一系列的操作(方向取反,回到碰撞前位置,重新记录开始时间)其实,我通过调试发现碰撞时间基本可以忽略.

110014205.png

   3.我们这里的碰撞检测是边界检测,只考虑小球与障碍物、边界的碰撞,没有考虑小球之间的碰撞,有兴趣的同学可以自行研究一下。


5.绘画线程:

2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import  android.graphics.Canvas;
import  android.util.Log;
import  android.view.SurfaceHolder;
/**
  * 绘画主界面线程
  * @author ZHF
  *
  */
public  class  DrawThread  extends  Thread {
                                                                                                                                                                                                                                                                                              
     boolean  flag;   //标记线程是否开启
     GameView gameView;
     SurfaceHolder holder;
     Canvas canvas;
                                                                                                                                                                                                                                                                                              
     public  DrawThread(GameView gameView) {
         flag =  true ;
         this .gameView = gameView;
         holder = gameView.getHolder();   //获取画布锁
     }
     @Override
     public  void  run() {
         while (flag) {
             //获取当前绘画开始时间
             long  startTime = System.currentTimeMillis();
             synchronized (holder) {
                 canvas = holder.lockCanvas();  //获取当前被锁住的画布
                 if (canvas !=  null ) {
                     gameView.draw(canvas);  //对画布进行操作
                     holder.unlockCanvasAndPost(canvas);  //释放画布
                 }
             }
             long  endTime = System.currentTimeMillis();
             int  diffTime = ( int ) (endTime - startTime);
             Log.d( "DrawTime" , diffTime+ "" );
                                                                                                                                                                                                                                                                                                      
             while (diffTime <=  2 ) {
                 diffTime = ( int ) (System.currentTimeMillis() - startTime);
                 Thread.yield();  //将线程的所有权交给另一个线程
             }
         }
     }
}

   分析:

     1. 首先,我们将画布锁住之后,对其进行绘画,画完之后自然要释放画布啦

2. 为了优化程序,我们计算出绘画所用时间,当绘画时间过长时,暂停当前正在执行的线程对象,通知CPU来执行其他线程(注意:这里的其他也包含当前线程)

6.主界面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import  java.util.ArrayList;
import  java.util.Random;
import  android.content.Context;
import  android.graphics.Canvas;
import  android.graphics.Color;
import  android.graphics.Paint;
import  android.view.MotionEvent;
import  android.view.SurfaceHolder;
import  android.view.SurfaceView;
/**
  * 游戏主界面
  * @author ZHF
  *
  */
public  class  GameView  extends  SurfaceView  implements  SurfaceHolder.Callback {
     SurfaceHolder holder;
     DrawThread drawThread;   //绘画线程
                                                                                                                                                                                                
     Ball[] ballArray =  new  Ball[ 5 ];   //装小球的数组
     int  ballPointer =  0 ;   //当前指向数组中第几个球
                                                                                                                                                                                                
     static  ArrayList<Obstruction> obstructList =  new  ArrayList<Obstruction>();    //装障碍物的集合
                                                                                                                                                                                                
     int  xDown, yDown;   //记录手指按下时的坐标
                                                                                                                                                                                                
     public  GameView(Context context) {
         super (context);
         holder = getHolder();   //获取画布锁
         holder.addCallback( this );   //添加回调
                                                                                                                                                                                                    
         //初始化障碍物
         Random random =  new  Random();
         for ( int  i =  0 ; i <  3 ; i++) {
             Obstruction o =  new  Obstruction(random.nextInt( 380 ) +  50 , random.nextInt( 700 ) +  50 50 );
             obstructList.add(o);  //将创出的障碍物对象添加到集合中去
         }
     }
     @Override
     public  void  surfaceCreated(SurfaceHolder holder) {
         drawThread =  new  DrawThread( this );
         drawThread.start();     //开启绘画线程
     }
     @Override
     public  void  surfaceChanged(SurfaceHolder holder,  int  format,  int  width,
             int  height) {
         //画布发生变化,eg:转屏操作,处理画布操作
     }
     @Override
     public  void  surfaceDestroyed(SurfaceHolder holder) {
         //销毁画布操作
         drawThread.flag =  false ;   //停掉线程
         drawThread =  null //GC会及时发现并处理掉该对象
     }
     public  void  draw(Canvas canvas) {
         canvas.drawColor(Color.BLACK);   //背景颜色
         Paint paint =  new  Paint();
         paint.setTextSize( 25 );
         paint.setColor(Color.WHITE);   //文字颜色
         canvas.drawText( "小球碰撞检测" 50 20 , paint);
                                                                                                                                                                                                    
         //画出小球
         for ( int  i =  0 ; i <  5 ; i++) {
             if (ballArray[i] !=  null ) {
                 ballArray[i].drawSelf(canvas);   //当前小球绘画出自己
             }
         }
         //画出障碍物
         for ( int  i =  0 ; i < obstructList.size(); i++) {
             obstructList. get (i).drawSelf(canvas);
         }
     }
                                                                                                                                                                                                
     @Override
     public  boolean onTouchEvent(MotionEvent event) {
         int  x = ( int ) event.getX();
         int  y = ( int ) event.getY();
                                                                                                                                                                                                    
         if (event.getAction() ==  0 ) {   //按下
             //记录按下时X,Y的坐标
             xDown = x;
             yDown = y;
             //生成第一个球
             Ball ball =  new  Ball(x, y,  0 0 20 );
             if (ballArray[ballPointer] !=  null ) {
                 ballArray[ballPointer].ballThread.flag =  false ;   //关闭小球移动线程
                 ballArray[ballPointer].ballThread =  null ;
             }
             ballArray[ballPointer] = ball;
                                                                                                                                                                                                        
         else  if (event.getAction() ==  1 ) {  //抬起
             int  xOffset = x - xDown;
             int  yOffset = y - yDown;
             double sin = yOffset / Math.sqrt(xOffset * xOffset + yOffset * yOffset);
             double cos = xOffset / Math.sqrt(xOffset * xOffset + yOffset * yOffset);
                                                                                                                                                                                                        
             ballArray[ballPointer].startTimeX = System.nanoTime();  //当前小球开始时间
             ballArray[ballPointer].startTimeY = System.nanoTime();
             ballArray[ballPointer].vX = (float) ( 500  * cos);   //当前小球的速度
             ballArray[ballPointer].vY = (float) ( 500  * sin);
             ballArray[ballPointer].ballThread.start();    //开启小球移动线程
                                                                                                                                                                                                        
             ballPointer ++;   //下一个小球
             if (ballPointer >=  5 ) {
                 ballPointer =  0 ;
             }
         }
         return  true ;
     }
}

分析:

   1.这里我们启动小球移动线程方式:采用手指触屏滑动,记录按下、抬起位置,通过计算角度得出算出发射方向。

   2.每次发出小球后下标ballPointer ++指向下一个小球,当到达数组上限后,重新返回到下标0.



   ok! 到此功能已经实现,想要完整源码在此下载:http://download.csdn.net/detail/zhf651555765/5775035