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

先看一下效果图:

095724635.png

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

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轴方向上做一系列的操作(方向取反,回到碰撞前位置,重新记录开始时间)其实,我通过调试发现碰撞时间基本可以忽略.

110014205.png

   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