您好,登錄后才能下訂單哦!
為了復(fù)習(xí)一下SurfaceView的使用,在此寫了一個(gè)經(jīng)典的小球碰撞檢測(cè)例子程序,希望能夠夠幫助正在學(xué)習(xí)游戲的人。
先看一下效果圖:
下面我們就來(lái)逐一分析一下它的實(shí)現(xiàn)過(guò)程:
1.啟動(dòng)入口:
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); //全屏設(shè)置 requestWindowFeature(Window.FEATURE_NO_TITLE); this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); //將畫布放進(jìn)去 GameView gameView = new GameView(this); setContentView(gameView); } }
2.小球類
import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; /** * 小球?qū)嵗? * @author ZHF * */ public class Ball { int x, y; //小球的實(shí)時(shí)位置 int startX, startY; //小球的初始位置 float vX, vY; //小球的速度 int r; //小球的半徑 double startTimeX; //開始時(shí)間 double startTimeY; //開始時(shí)間 BallThread ballThread; //小球移動(dòng)線程 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; //為每個(gè)小球?qū)嵗粋€(gè)獨(dú)立的線程,在抬手時(shí)開啟線程 ballThread = new BallThread(this); paint.setColor(Color.RED); //小球?yàn)榧t色實(shí)心 } /**繪畫方法**/ 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); //設(shè)置畫筆顏色 } public void drawSelf(Canvas canvas) { canvas.drawRect(x - hWeight, y - hWeight, x + hWeight, y + hWeight, paint); } }
以上代碼比較簡(jiǎn)單,在此不多做解釋,下面主要來(lái)看一下兩個(gè)主要線程類:
4.小球移動(dòng)線程(碰撞檢測(cè)):
/** * 小球移動(dòng)和碰撞檢測(cè)線程 * @author ZHF * */ public class BallThread extends Thread { boolean flag; //標(biāo)記線程是否開啟 Ball ball; //小球 double currentTime; //當(dāng)前時(shí)間 public BallThread(Ball ball) { flag = true; this.ball = ball; } @Override public void run() { while(flag) { //調(diào)試:碰撞檢測(cè)開始時(shí)間 long startTime = System.currentTimeMillis(); //計(jì)算出小球移動(dòng)的時(shí)間片:將每次刷新分成若干時(shí)間小片段,用于計(jì)算每次時(shí)間小片段小球移動(dòng)的距離 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);//小球移動(dòng)的距離 ball.y = (int) (ball.startY + ball.vY * timeSpanY); //邊界碰撞檢測(cè) 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(); //重新記錄開始時(shí)間 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(); //重新記錄開始時(shí)間 ball.startY = ball.y; //重新記錄開始位置 } //障礙物碰撞檢測(cè) 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; //跳出循環(huán) } } //調(diào)試:碰撞檢測(cè)結(jié)束時(shí)間 實(shí)驗(yàn)證明碰撞加測(cè)基本不耗時(shí)間 long endTime = System.currentTimeMillis(); System.out.println(endTime + "----" + startTime + "= " +(endTime - startTime)); try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } } } }
分析:
1.我們將刷新時(shí)間分割成:將每次刷新時(shí)間分成若干時(shí)間小片段timeSpanX和timeSpanY,用于計(jì)算每次時(shí)間小片段小球移動(dòng)的距離.
2.我們?cè)谛∏蚺c邊界碰撞之前,記錄一下時(shí)間startTime,在其與邊界碰撞之后,我們將其x軸、y軸方向上做一系列的操作(方向取反,回到碰撞前位置,重新記錄開始時(shí)間)其實(shí),我通過(guò)調(diào)試發(fā)現(xiàn)碰撞時(shí)間基本可以忽略.
3.我們這里的碰撞檢測(cè)是邊界檢測(cè),只考慮小球與障礙物、邊界的碰撞,沒(méi)有考慮小球之間的碰撞,有興趣的同學(xué)可以自行研究一下。
5.繪畫線程:
2
import android.graphics.Canvas; import android.util.Log; import android.view.SurfaceHolder; /** * 繪畫主界面線程 * @author ZHF * */ public class DrawThread extends Thread { boolean flag; //標(biāo)記線程是否開啟 GameView gameView; SurfaceHolder holder; Canvas canvas; public DrawThread(GameView gameView) { flag = true; this.gameView = gameView; holder = gameView.getHolder(); //獲取畫布鎖 } @Override public void run() { while(flag) { //獲取當(dāng)前繪畫開始時(shí)間 long startTime = System.currentTimeMillis(); synchronized(holder) { canvas = holder.lockCanvas(); //獲取當(dāng)前被鎖住的畫布 if(canvas != null) { gameView.draw(canvas); //對(duì)畫布進(jìn)行操作 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(); //將線程的所有權(quán)交給另一個(gè)線程 } } } }
分析:
1. 首先,我們將畫布鎖住之后,對(duì)其進(jìn)行繪畫,畫完之后自然要釋放畫布啦
2. 為了優(yōu)化程序,我們計(jì)算出繪畫所用時(shí)間,當(dāng)繪畫時(shí)間過(guò)長(zhǎng)時(shí),暫停當(dāng)前正在執(zhí)行的線程對(duì)象,通知CPU來(lái)執(zhí)行其他線程(注意:這里的其他也包含當(dāng)前線程)
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]; //裝小球的數(shù)組 int ballPointer = 0; //當(dāng)前指向數(shù)組中第幾個(gè)球 static ArrayList<Obstruction> obstructList = new ArrayList<Obstruction>(); //裝障礙物的集合 int xDown, yDown; //記錄手指按下時(shí)的坐標(biāo) public GameView(Context context) { super(context); holder = getHolder(); //獲取畫布鎖 holder.addCallback(this); //添加回調(diào) //初始化障礙物 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); //將創(chuàng)出的障礙物對(duì)象添加到集合中去 } } @Override public void surfaceCreated(SurfaceHolder holder) { drawThread = new DrawThread(this); drawThread.start(); //開啟繪畫線程 } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { //畫布發(fā)生變化,eg:轉(zhuǎn)屏操作,處理畫布操作 } @Override public void surfaceDestroyed(SurfaceHolder holder) { //銷毀畫布操作 drawThread.flag = false; //停掉線程 drawThread = null; //GC會(huì)及時(shí)發(fā)現(xiàn)并處理掉該對(duì)象 } public void draw(Canvas canvas) { canvas.drawColor(Color.BLACK); //背景顏色 Paint paint = new Paint(); paint.setTextSize(25); paint.setColor(Color.WHITE); //文字顏色 canvas.drawText("小球碰撞檢測(cè)", 50, 20, paint); //畫出小球 for(int i = 0; i < 5; i++) { if(ballArray[i] != null) { ballArray[i].drawSelf(canvas); //當(dāng)前小球繪畫出自己 } } //畫出障礙物 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) { //按下 //記錄按下時(shí)X,Y的坐標(biāo) xDown = x; yDown = y; //生成第一個(gè)球 Ball ball = new Ball(x, y, 0, 0, 20); if(ballArray[ballPointer] != null) { ballArray[ballPointer].ballThread.flag = false; //關(guān)閉小球移動(dòng)線程 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(); //當(dāng)前小球開始時(shí)間 ballArray[ballPointer].startTimeY = System.nanoTime(); ballArray[ballPointer].vX = (float) (500 * cos); //當(dāng)前小球的速度 ballArray[ballPointer].vY = (float) (500 * sin); ballArray[ballPointer].ballThread.start(); //開啟小球移動(dòng)線程 ballPointer ++; //下一個(gè)小球 if(ballPointer >= 5) { ballPointer = 0; } } return true; } }
分析:
1.這里我們啟動(dòng)小球移動(dòng)線程方式:采用手指觸屏滑動(dòng),記錄按下、抬起位置,通過(guò)計(jì)算角度得出算出發(fā)射方向。
2.每次發(fā)出小球后下標(biāo)ballPointer ++指向下一個(gè)小球,當(dāng)?shù)竭_(dá)數(shù)組上限后,重新返回到下標(biāo)0.
ok! 到此功能已經(jīng)實(shí)現(xiàn),想要完整源碼在此下載:http://download.csdn.net/detail/zhf651555765/5775035
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。