為了複習一下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 > </code><code>0</code> <code>&& (ball.x + ball.r) >= </code><code>479</code><code>) || (ball.vX < </code><code>0</code> <code>&& (ball.x - ball.r) <= </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 > </code><code>0</code> <code>&& (ball.y + ball.r) >= </code><code>799</code><code>) || (ball.vY < </code><code>0</code> <code>&& (ball.y - ball.r) <= </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 < 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) < (ball.r + o.hWeight) && Math.abs(ball.y - o.y) < (ball.r + o.hWeight)){</code>
<code> </code><code>if</code><code>(Math.abs(xBackup - o.x) >= (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) >= (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 <= </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<Obstruction> obstructList = </code><code>new</code> <code>ArrayList<Obstruction>(); </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 < </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 < </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 < 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 >= </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,如需轉載請自行聯系原作者