为了复习一下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
附件:http://down.51cto.com/data/2363159