您好,登錄后才能下訂單哦!
為了復習一下SurfaceView的使用,在此寫了一個經典的小球碰撞檢測例子程序,希望能夠夠幫助正在學習游戲的人。
先看一下效果圖:
下面我們就來逐一分析一下它的實現過程:
1.啟動入口:
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.小球類
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.障礙物類:
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.小球移動線程(碰撞檢測):
/** * 小球移動和碰撞檢測線程 * @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軸方向上做一系列的操作(方向取反,回到碰撞前位置,重新記錄開始時間)其實,我通過調試發現碰撞時間基本可以忽略.
3.我們這里的碰撞檢測是邊界檢測,只考慮小球與障礙物、邊界的碰撞,沒有考慮小球之間的碰撞,有興趣的同學可以自行研究一下。
5.繪畫線程:
2
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.主界面:
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
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。