天天看点

【移动开发】Android游戏开发SurfaceView应用----手指发动小球(小球碰撞检测例子)

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

先看一下效果图:

<a target="_blank" href="http://blog.51cto.com/attachment/201307/095724635.png"></a>

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

1.启动入口:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<code>import</code> <code>android.os.Bundle;</code>

<code>import</code> <code>android.app.Activity;</code>

<code>import</code> <code>android.view.Window;</code>

<code>import</code> <code>android.view.WindowManager;</code>

<code>public</code> <code>class</code> <code>MainActivity </code><code>extends</code> <code>Activity {</code>

<code>    </code><code>@Override</code>

<code>    </code><code>protected</code> <code>void</code> <code>onCreate(Bundle savedInstanceState) {</code>

<code>        </code><code>super</code><code>.onCreate(savedInstanceState);</code>

<code>        </code><code>//全屏设置</code>

<code>        </code><code>requestWindowFeature(Window.FEATURE_NO_TITLE);</code>

<code>        </code><code>this</code><code>.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,</code>

<code>                </code><code>WindowManager.LayoutParams.FLAG_FULLSCREEN);</code>

<code>        </code><code>//将画布放进去</code>

<code>        </code><code>GameView gameView = </code><code>new</code> <code>GameView(</code><code>this</code><code>);</code>

<code>        </code><code>setContentView(gameView);</code>

<code>    </code><code>}</code>

<code>}</code>

2.小球类

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

<code>import</code> <code>android.graphics.Canvas;</code>

<code>import</code> <code>android.graphics.Color;</code>

<code>import</code> <code>android.graphics.Paint;</code>

<code>/**</code>

<code> </code><code>* 小球实例</code>

<code> </code><code>* @author ZHF</code>

<code> </code><code>*</code>

<code> </code><code>*/</code>

<code>public</code> <code>class</code> <code>Ball {</code>

<code>    </code><code>int</code> <code>x, y;  </code><code>//小球的实时位置</code>

<code>    </code><code>int</code> <code>startX, startY;   </code><code>//小球的初始位置</code>

<code>    </code><code>float</code> <code>vX, vY;    </code><code>//小球的速度</code>

<code>    </code><code>int</code> <code>r; </code><code>//小球的半径</code>

<code>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     </code> 

<code>    </code><code>double</code> <code>startTimeX;  </code><code>//开始时间</code>

<code>    </code><code>double</code> <code>startTimeY;  </code><code>//开始时间</code>

<code>    </code><code>BallThread ballThread;  </code><code>//小球移动线程</code>

<code>    </code><code>Paint paint = </code><code>new</code> <code>Paint();  </code><code>//画笔</code>

<code>    </code><code>public</code> <code>Ball(</code><code>int</code> <code>x, </code><code>int</code> <code>y, </code><code>float</code> <code>vX, </code><code>float</code> <code>vY, </code><code>int</code> <code>r) {</code>

<code>        </code><code>this</code><code>.x = x;</code>

<code>        </code><code>this</code><code>.y = y;</code>

<code>        </code><code>this</code><code>.startX = x;</code>

<code>        </code><code>this</code><code>.startY = y;</code>

<code>        </code><code>this</code><code>.vX = vX;</code>

<code>        </code><code>this</code><code>.vY = vY;</code>

<code>        </code><code>this</code><code>.r = r;</code>

<code>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         </code> 

<code>        </code><code>//为每个小球实例化一个独立的线程,在抬手时开启线程</code>

<code>        </code><code>ballThread = </code><code>new</code> <code>BallThread(</code><code>this</code><code>);</code>

<code>        </code><code>paint.setColor(Color.RED);  </code><code>//小球为红色实心</code>

<code>    </code><code>/**绘画方法**/</code>

<code>    </code><code>public</code> <code>void</code> <code>drawSelf(Canvas canvas) {</code>

<code>        </code><code>canvas.drawCircle(x, y, r, paint);</code>

3.障碍物类:

<code> </code><code>* 障碍物</code>

<code>public</code> <code>class</code> <code>Obstruction {</code>

<code>    </code><code>int</code> <code>x, y;</code>

<code>    </code><code>int</code> <code>hWeight;  </code><code>//宽度和高度一样</code>

<code>    </code><code>Paint paint = </code><code>new</code> <code>Paint();</code>

<code>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  </code> 

<code>    </code><code>public</code> <code>Obstruction(</code><code>int</code> <code>x, </code><code>int</code> <code>y, </code><code>int</code> <code>hWeight) {</code>

<code>        </code><code>this</code><code>.hWeight = hWeight;</code>

<code>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      </code> 

<code>        </code><code>paint.setColor(Color.GREEN);  </code><code>//设置画笔颜色</code>

<code>        </code><code>canvas.drawRect(x - hWeight, y - hWeight, x + hWeight, y + hWeight, paint);</code>

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

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

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

<code> </code><code>* 小球移动和碰撞检测线程</code>

<code>public</code> <code>class</code> <code>BallThread </code><code>extends</code> <code>Thread {</code>

<code>    </code><code>boolean</code> <code>flag;   </code><code>//标记线程是否开启</code>

<code>    </code><code>Ball ball;  </code><code>//小球</code>

<code>    </code><code>double</code> <code>currentTime;  </code><code>//当前时间</code>

<code>                                                                                                                                                                                                                                                                                                                                                                                                                                                                     </code> 

<code>    </code><code>public</code> <code>BallThread(Ball ball) {</code>

<code>        </code><code>flag = </code><code>true</code><code>;</code>

<code>        </code><code>this</code><code>.ball = ball;</code>

<code>    </code><code>public</code> <code>void</code> <code>run() {</code>

<code>        </code><code>while</code><code>(flag) {</code>

<code>            </code><code>//调试:碰撞检测开始时间</code>

<code>            </code><code>long</code> <code>startTime = System.currentTimeMillis();</code>

<code>                                                                                                                                                                                                                                                                                                                                                                                                                                                                             </code> 

<code>             </code><code>//计算出小球移动的时间片:将每次刷新分成若干时间小片段,用于计算每次时间小片段小球移动的距离</code>

<code>            </code><code>currentTime = System.nanoTime();</code>

<code>            </code><code>double</code> <code>timeSpanX = (currentTime - ball.startTimeX) /</code><code>1000</code> <code>/</code><code>1000</code> <code>/</code><code>1000</code><code>;</code>

<code>            </code><code>double</code> <code>timeSpanY = (currentTime - ball.startTimeY) /</code><code>1000</code> <code>/</code><code>1000</code> <code>/</code><code>1000</code><code>;</code>

<code>            </code><code>int</code> <code>xBackup = ball.x; </code><code>//保存小球的碰撞前的位置</code>

<code>            </code><code>int</code> <code>yBackup = ball.y;</code>

<code>            </code><code>ball.x = (</code><code>int</code><code>) (ball.startX + ball.vX * timeSpanX);</code><code>//小球移动的距离</code>

<code>            </code><code>ball.y = (</code><code>int</code><code>) (ball.startY + ball.vY * timeSpanY);</code>

<code>            </code><code>//边界碰撞检测</code>

<code>            </code><code>if</code><code>((ball.vX &gt; </code><code>0</code> <code>&amp;&amp; (ball.x + ball.r) &gt;= </code><code>479</code><code>) || (ball.vX &lt; </code><code>0</code> <code>&amp;&amp; (ball.x - ball.r) &lt;= </code><code>0</code><code>)) {</code>

<code>                </code><code>ball.x = xBackup;</code>

<code>                </code><code>ball.vX = </code><code>0</code> <code>- ball.vX;  </code><code>//速度反向</code>

<code>                </code><code>ball.startTimeX = System.nanoTime();  </code><code>//重新记录开始时间</code>

<code>                </code><code>ball.startX = ball.x;  </code><code>//重新记录开始位置</code>

<code>            </code><code>}</code>

<code>            </code><code>if</code><code>((ball.vY &gt; </code><code>0</code> <code>&amp;&amp; (ball.y + ball.r) &gt;= </code><code>799</code><code>) || (ball.vY &lt; </code><code>0</code> <code>&amp;&amp; (ball.y - ball.r) &lt;= </code><code>0</code><code>)) {</code>

<code>                </code><code>ball.y = yBackup;</code>

<code>                </code><code>ball.vY = </code><code>0</code> <code>- ball.vY;   </code><code>//速度反向</code>

<code>                </code><code>ball.startTimeY = System.nanoTime();   </code><code>//重新记录开始时间</code>

<code>                </code><code>ball.startY = ball.y;   </code><code>//重新记录开始位置</code>

<code>            </code><code>//障碍物碰撞检测</code>

<code>            </code><code>for</code><code>(</code><code>int</code> <code>i = </code><code>0</code><code>; i &lt; GameView.obstructList.size(); i++) {</code>

<code>                </code><code>Obstruction o = GameView.obstructList.get(i);</code>

<code>                </code><code>if</code><code>(Math.abs(ball.x - o.x) &lt; (ball.r + o.hWeight) &amp;&amp; Math.abs(ball.y - o.y) &lt; (ball.r + o.hWeight)){</code>

<code>                    </code><code>if</code><code>(Math.abs(xBackup - o.x) &gt;= (ball.r + o.hWeight)) {</code>

<code>                        </code><code>ball.x = xBackup;</code>

<code>                        </code><code>ball.vX =  </code><code>0</code> <code>- ball.vX;</code>

<code>                        </code><code>ball.startTimeX = System.nanoTime();</code>

<code>                        </code><code>ball.startX = ball.x;</code>

<code>                    </code><code>}</code>

<code>                    </code><code>if</code><code>(Math.abs(yBackup - o.y) &gt;= (ball.r + o.hWeight)) {</code>

<code>                        </code><code>ball.y = yBackup;</code>

<code>                        </code><code>ball.vY = </code><code>0</code> <code>- ball.vY;</code>

<code>                        </code><code>ball.startTimeY = System.nanoTime();</code>

<code>                        </code><code>ball.startY = ball.y;</code>

<code>                    </code><code>break</code><code>; </code><code>//跳出循环</code>

<code>                </code><code>}</code>

<code>            </code><code>//调试:碰撞检测结束时间     实验证明碰撞加测基本不耗时间</code>

<code>            </code><code>long</code> <code>endTime = System.currentTimeMillis();</code>

<code>            </code><code>System.out.println(endTime + </code><code>"----"</code> <code>+ startTime + </code><code>"= "</code> <code>+(endTime - startTime));</code>

<code>            </code><code>try</code> <code>{</code>

<code>                </code><code>Thread.sleep(</code><code>10</code><code>);</code>

<code>            </code><code>} </code><code>catch</code> <code>(Exception e) {</code>

<code>                </code><code>e.printStackTrace();</code>

<code>        </code><code>}</code>

分析:

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

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

<a target="_blank" href="http://blog.51cto.com/attachment/201307/110014205.png"></a>

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

5.绘画线程:

<code>import</code> <code>android.util.Log;</code>

<code>import</code> <code>android.view.SurfaceHolder;</code>

<code> </code><code>* 绘画主界面线程</code>

<code>public</code> <code>class</code> <code>DrawThread </code><code>extends</code> <code>Thread {</code>

<code>                                                                                                                                                                                                                                                                                             </code> 

<code>    </code><code>boolean</code> <code>flag;  </code><code>//标记线程是否开启</code>

<code>    </code><code>GameView gameView;</code>

<code>    </code><code>SurfaceHolder holder;</code>

<code>    </code><code>Canvas canvas;</code>

<code>    </code><code>public</code> <code>DrawThread(GameView gameView) {</code>

<code>        </code><code>this</code><code>.gameView = gameView;</code>

<code>        </code><code>holder = gameView.getHolder();  </code><code>//获取画布锁</code>

<code>            </code><code>//获取当前绘画开始时间</code>

<code>            </code><code>synchronized</code><code>(holder) {</code>

<code>                </code><code>canvas = holder.lockCanvas(); </code><code>//获取当前被锁住的画布</code>

<code>                </code><code>if</code><code>(canvas != </code><code>null</code><code>) {</code>

<code>                    </code><code>gameView.draw(canvas); </code><code>//对画布进行操作</code>

<code>                    </code><code>holder.unlockCanvasAndPost(canvas); </code><code>//释放画布</code>

<code>            </code><code>int</code> <code>diffTime = (</code><code>int</code><code>) (endTime - startTime);</code>

<code>            </code><code>Log.d(</code><code>"DrawTime"</code><code>, diffTime+</code><code>""</code><code>);</code>

<code>                                                                                                                                                                                                                                                                                                     </code> 

<code>            </code><code>while</code><code>(diffTime &lt;= </code><code>2</code><code>) {</code>

<code>                </code><code>diffTime = (</code><code>int</code><code>) (System.currentTimeMillis() - startTime);</code>

<code>                </code><code>Thread.yield(); </code><code>//将线程的所有权交给另一个线程</code>

   分析:

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

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

6.主界面:

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

<code>import</code> <code>java.util.ArrayList;</code>

<code>import</code> <code>java.util.Random;</code>

<code>import</code> <code>android.content.Context;</code>

<code>import</code> <code>android.view.MotionEvent;</code>

<code>import</code> <code>android.view.SurfaceView;</code>

<code> </code><code>* 游戏主界面</code>

<code>public</code> <code>class</code> <code>GameView </code><code>extends</code> <code>SurfaceView </code><code>implements</code> <code>SurfaceHolder.Callback {</code>

<code>    </code><code>DrawThread drawThread;  </code><code>//绘画线程</code>

<code>                                                                                                                                                                                               </code> 

<code>    </code><code>Ball[] ballArray = </code><code>new</code> <code>Ball[</code><code>5</code><code>];  </code><code>//装小球的数组</code>

<code>    </code><code>int</code> <code>ballPointer = </code><code>0</code><code>;  </code><code>//当前指向数组中第几个球</code>

<code>    </code><code>static</code> <code>ArrayList&lt;Obstruction&gt; obstructList = </code><code>new</code> <code>ArrayList&lt;Obstruction&gt;();   </code><code>//装障碍物的集合</code>

<code>    </code><code>int</code> <code>xDown, yDown;  </code><code>//记录手指按下时的坐标</code>

<code>    </code><code>public</code> <code>GameView(Context context) {</code>

<code>        </code><code>super</code><code>(context);</code>

<code>        </code><code>holder = getHolder();  </code><code>//获取画布锁</code>

<code>        </code><code>holder.addCallback(</code><code>this</code><code>);  </code><code>//添加回调</code>

<code>                                                                                                                                                                                                   </code> 

<code>        </code><code>//初始化障碍物</code>

<code>        </code><code>Random random = </code><code>new</code> <code>Random();</code>

<code>        </code><code>for</code><code>(</code><code>int</code> <code>i = </code><code>0</code><code>; i &lt; </code><code>3</code><code>; i++) {</code>

<code>            </code><code>Obstruction o = </code><code>new</code> <code>Obstruction(random.nextInt(</code><code>380</code><code>) + </code><code>50</code><code>, random.nextInt(</code><code>700</code><code>) + </code><code>50</code><code>, </code><code>50</code><code>);</code>

<code>            </code><code>obstructList.add(o); </code><code>//将创出的障碍物对象添加到集合中去</code>

<code>    </code><code>public</code> <code>void</code> <code>surfaceCreated(SurfaceHolder holder) {</code>

<code>        </code><code>drawThread = </code><code>new</code> <code>DrawThread(</code><code>this</code><code>);</code>

<code>        </code><code>drawThread.start();    </code><code>//开启绘画线程</code>

<code>    </code><code>public</code> <code>void</code> <code>surfaceChanged(SurfaceHolder holder, </code><code>int</code> <code>format, </code><code>int</code> <code>width,</code>

<code>            </code><code>int</code> <code>height) {</code>

<code>        </code><code>//画布发生变化,eg:转屏操作,处理画布操作</code>

<code>    </code><code>public</code> <code>void</code> <code>surfaceDestroyed(SurfaceHolder holder) {</code>

<code>        </code><code>//销毁画布操作</code>

<code>        </code><code>drawThread.flag = </code><code>false</code><code>;  </code><code>//停掉线程</code>

<code>        </code><code>drawThread = </code><code>null</code><code>; </code><code>//GC会及时发现并处理掉该对象</code>

<code>    </code><code>public</code> <code>void</code> <code>draw(Canvas canvas) {</code>

<code>        </code><code>canvas.drawColor(Color.BLACK);  </code><code>//背景颜色</code>

<code>        </code><code>Paint paint = </code><code>new</code> <code>Paint();</code>

<code>        </code><code>paint.setTextSize(</code><code>25</code><code>);</code>

<code>        </code><code>paint.setColor(Color.WHITE);  </code><code>//文字颜色</code>

<code>        </code><code>canvas.drawText(</code><code>"小球碰撞检测"</code><code>, </code><code>50</code><code>, </code><code>20</code><code>, paint);</code>

<code>        </code><code>//画出小球</code>

<code>        </code><code>for</code><code>(</code><code>int</code> <code>i = </code><code>0</code><code>; i &lt; </code><code>5</code><code>; i++) {</code>

<code>            </code><code>if</code><code>(ballArray[i] != </code><code>null</code><code>) {</code>

<code>                </code><code>ballArray[i].drawSelf(canvas);  </code><code>//当前小球绘画出自己</code>

<code>        </code><code>//画出障碍物</code>

<code>        </code><code>for</code><code>(</code><code>int</code> <code>i = </code><code>0</code><code>; i &lt; obstructList.size(); i++) {</code>

<code>            </code><code>obstructList.</code><code>get</code><code>(i).drawSelf(canvas);</code>

<code>    </code><code>public</code> <code>boolean onTouchEvent(MotionEvent event) {</code>

<code>        </code><code>int</code> <code>x = (</code><code>int</code><code>) event.getX();</code>

<code>        </code><code>int</code> <code>y = (</code><code>int</code><code>) event.getY();</code>

<code>        </code><code>if</code><code>(event.getAction() == </code><code>0</code><code>) {  </code><code>//按下</code>

<code>            </code><code>//记录按下时X,Y的坐标</code>

<code>            </code><code>xDown = x;</code>

<code>            </code><code>yDown = y;</code>

<code>            </code><code>//生成第一个球</code>

<code>            </code><code>Ball ball = </code><code>new</code> <code>Ball(x, y, </code><code>0</code><code>, </code><code>0</code><code>, </code><code>20</code><code>);</code>

<code>            </code><code>if</code><code>(ballArray[ballPointer] != </code><code>null</code><code>) {</code>

<code>                </code><code>ballArray[ballPointer].ballThread.flag = </code><code>false</code><code>;  </code><code>//关闭小球移动线程</code>

<code>                </code><code>ballArray[ballPointer].ballThread = </code><code>null</code><code>;</code>

<code>            </code><code>ballArray[ballPointer] = ball;</code>

<code>                                                                                                                                                                                                       </code> 

<code>        </code><code>} </code><code>else</code> <code>if</code><code>(event.getAction() == </code><code>1</code><code>) { </code><code>//抬起</code>

<code>            </code><code>int</code> <code>xOffset = x - xDown;</code>

<code>            </code><code>int</code> <code>yOffset = y - yDown;</code>

<code>            </code><code>double sin = yOffset / Math.sqrt(xOffset * xOffset + yOffset * yOffset);</code>

<code>            </code><code>double cos = xOffset / Math.sqrt(xOffset * xOffset + yOffset * yOffset);</code>

<code>            </code><code>ballArray[ballPointer].startTimeX = System.nanoTime(); </code><code>//当前小球开始时间</code>

<code>            </code><code>ballArray[ballPointer].startTimeY = System.nanoTime();</code>

<code>            </code><code>ballArray[ballPointer].vX = (float) (</code><code>500</code> <code>* cos);  </code><code>//当前小球的速度</code>

<code>            </code><code>ballArray[ballPointer].vY = (float) (</code><code>500</code> <code>* sin);</code>

<code>            </code><code>ballArray[ballPointer].ballThread.start();   </code><code>//开启小球移动线程</code>

<code>            </code><code>ballPointer ++;  </code><code>//下一个小球</code>

<code>            </code><code>if</code><code>(ballPointer &gt;= </code><code>5</code><code>) {</code>

<code>                </code><code>ballPointer = </code><code>0</code><code>;</code>

<code>        </code><code>return</code> <code>true</code><code>;</code>

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

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

<a href="http://down.51cto.com/data/2363159" target="_blank">附件:http://down.51cto.com/data/2363159</a>

     本文转自zhf651555765 51CTO博客,原文链接:http://blog.51cto.com/smallwoniu/1251882,如需转载请自行联系原作者