Android游戏开发基础part7--碰撞检测
Android游戏开发基础--碰撞检测
在游戏当中碰撞无时不在,比如在射击游戏当中碰撞更是家常便饭,游戏主角在跟敌机发生碰撞,游戏主角跟敌机子弹发生碰撞,或者反过来敌机跟游戏主角发生碰撞,敌机更主角子弹发生碰撞等等。
一般我们都会有碰撞检测,发生碰撞时应该发生什么状况是我们需要设计的,比如主角的血量减少,或者敌机发生爆炸等等。只有通过碰撞的检测,我们才能进而触发相应的事件。
《Android游戏编程之从零开始》就介绍了三种最常用的检测碰撞的方式,分别是:矩形碰撞、圆形碰撞和像素碰撞。
首先是矩形碰撞:所谓矩形碰撞就是利用两个矩形之间的位置关系来进行判断,如果一个矩形的像素在另外一个矩形之中,或者之上都可以认为这两个矩形发生了碰撞。
其实两个矩形不发生碰撞的情况就只有四种,其他情况就是发生碰撞了
创建实例:RectCollion, 游戏框架为SurfaceView
项目效果图:
项目代码:
==>MySurfaceView.java
package com.rectCollsion; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; public class MySurfaceView extends SurfaceView implements Callback,Runnable{ //用于控制SurfaceView private SurfaceHolder sfh; //声明一个画笔 private Paint paint; //声明一个线程 private Thread th; //线程消亡的标志位 private boolean flag; //声明一个画布 private Canvas canvas; //声明屏幕的宽高 private int screenW,screenH; //定义两个矩形的宽高坐标 private int x1 = 10,y1 = 110,w1 = 40,h1 = 40; private int x2 = 10,y2 = 110,w2 = 40,h2 = 40; //便于观察是否发生碰撞设置一个标识位 private boolean isCollsion; /** * SurfaceView初始化函数 */ public MySurfaceView(Context context){ super(context); //实例SurfaceHolder sfh = this.getHolder(); //为SurfaceView添加状态监听 sfh.addCallback(this); //实例一个画笔 paint = new Paint(); //设置画笔颜色为白色 paint.setColor(Color.WHITE); //设置焦点 setFocusable(true); } @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub screenW = this.getWidth(); screenH = this.getHeight(); flag = true; //实例线程 th = new Thread(this); //启动线程 th.start(); } /** * 游戏绘图 */ public void myDraw(){ try { canvas = sfh.lockCanvas(); if(canvas != null){ //刷屏 canvas.drawColor(Color.BLACK); //判断是否发生了碰撞 if(isCollsion){//发生碰撞 paint.setColor(Color.RED); paint.setTextSize(20); canvas.drawText("Collision!", 0, 30, paint); } else{//没发生碰撞 paint.setColor(Color.WHITE); } //绘制两个矩形 canvas.drawRect(x1, y1, x1 + w1, y1 + h1, paint); canvas.drawRect(x2, y2, x2 + w2, y2 + h2, paint); } }catch(Exception e){ }finally{ if(canvas != null) sfh.unlockCanvasAndPost(canvas); } } /** * 触屏事件监听 */ @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub //让矩形1随着触屏位置移动(触屏点设为此矩形的中心点) x1 = (int) event.getX() - w1/2; y1 = (int) event.getY() - h1/2; //当矩形之间发生碰撞 if(isCollsionWithRect(x1,y1,w1,h1,x2,y2,w2,h2)){ isCollsion = true;//设置标志位为真 //当矩形之间没有发生碰撞 }else{ isCollsion = false; //设置标志位为假 } return true; } /** * 矩形碰撞的函数 * @param x1 第一个矩形的X坐标 * @param y1 第一个矩形的Y坐标 * @param w1 第一个矩形的宽 * @param h1 第一个矩形的高 * @param x2 第二个矩形的X坐标 * @param y2 第二个矩形的Y坐标 * @param w2 第二个矩形的宽 * @param h2 第二个矩形的高 */ public boolean isCollsionWithRect(int x1,int y1,int w1,int h1,int x2,int y2,int w2,int h2){ //当矩形1位于矩形2的左侧 if (x1 >= x2 && x1>= x2 + w2){ return false; //当矩形1位于矩形2的右侧 } else if (x1<= x2 && x1 + w1 <= x2){ return false; //当矩形1位于矩形2的上方 } else if (y1 >= y2 && y1>= y2 + h2){ return false; } else if (y1 <= y2 && y1 + h1 <= y2){ return false; } //所有不会发生碰撞都不满足时,肯定就是碰撞了 return true; } /** * 按键事件监听 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // TODO Auto-generated method stub return super.onKeyDown(keyCode, event); } /** * 游戏逻辑 */ private void logic(){ } @Override public void run() { // TODO Auto-generated method stub while(flag){ long start = System.currentTimeMillis(); myDraw(); logic(); long end = System.currentTimeMillis(); try { if (end - start < 50) { Thread.sleep(50 - (end - start)); } } catch(InterruptedException e){ e.printStackTrace(); } } } /** * Surfaceview视图状态发生改变,响应此函数 */ @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } /** * Surfaceview视图消亡,响应此函数 */ @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub } }
==>RectCollsionActivity.java
package com.rectCollsion; import android.app.Activity; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; public class RectCollsionActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.requestWindowFeature(Window.FEATURE_NO_TITLE); this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(new MySurfaceView(this)); } }
圆形碰撞:主要是利用两圆形的圆心距进行判定的;当两圆的圆心距小于两圆半径之和,判定发生了碰撞。
创建实例:CircleCollsoin
项目效果图:
==>MySurfaceView.java
package com.circleCollsion; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; public class MySurfaceView extends SurfaceView implements Callback,Runnable{ //用于控制SurfaceView private SurfaceHolder sfh; //声明一个画笔 private Paint paint; //声明一个线程 private Thread th; //线程消亡的标志位 private boolean flag; //声明一个画布 private Canvas canvas; //声明屏幕的宽高 private int screenW,screenH; //定义两个圆的半径和坐标 private int r1 = 20, r2 = 20; private int x1 = 50, y1 = 100, x2 = 150, y2 = 100; //便于观察是否发生碰撞设置一个标识位 private boolean isCollsion; /** * SurfaceView初始化函数 */ public MySurfaceView(Context context){ super(context); //实例SurfaceHolder sfh = this.getHolder(); //为SurfaceView添加状态监听 sfh.addCallback(this); //实例一个画笔 paint = new Paint(); //设置画笔颜色为白色 paint.setColor(Color.WHITE); //设置焦点 setFocusable(true); } @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub screenW = this.getWidth(); screenH = this.getHeight(); flag = true; //实例线程 th = new Thread(this); //启动线程 th.start(); } /** * 游戏绘图 */ public void myDraw(){ try { canvas = sfh.lockCanvas(); if(canvas != null){ //刷屏 canvas.drawColor(Color.BLACK); //判断是否发生了碰撞 if(isCollsion){//发生碰撞 paint.setColor(Color.RED); paint.setTextSize(20); canvas.drawText("Collision!", 0, 30, paint); } else{//没发生碰撞 paint.setColor(Color.WHITE); } //绘制两个圆 canvas.drawCircle(x1, y1, r1, paint); canvas.drawCircle(x2, y2, r2, paint); } }catch(Exception e){ }finally{ if(canvas != null) sfh.unlockCanvasAndPost(canvas); } } /** * 触屏事件监听 */ @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub x1 = (int) event.getX(); y1 = (int) event.getY() ; //当圆形之间发生碰撞 if(isCollsionWithCircle(x1,y1,x2,y2,r1,r2)){ isCollsion = true;//设置标志位为真 //当矩形之间没有发生碰撞 }else{ isCollsion = false; //设置标志位为假 } return true; } /** * 圆形碰撞 * @param x1圆形1的圆心x坐标 * @param y1圆形1的圆心y坐标 * @param x2圆心2的圆形x坐标 * @param y2圆形2的圆形y坐标 * @param r1圆形1的半径 * @param r2圆形2的半径 */ private boolean isCollsionWithCircle(int x1,int y1,int x2,int y2,int r1,int r2){ //Math.sqrt:开平方 //Math.pow(double x,double y):X的Y次方 if(Math.sqrt(Math.pow(x1 - x2, 2)+ Math.pow(y1 - y2,2))<= r1 + r2){ //如果两圆的圆心距小于或等于两圆半径则认为发生碰撞 return true; } return false; } /** * 按键事件监听 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // TODO Auto-generated method stub return super.onKeyDown(keyCode, event); } /** * 游戏逻辑 */ private void logic(){ } @Override public void run() { // TODO Auto-generated method stub while(flag){ long start = System.currentTimeMillis(); myDraw(); logic(); long end = System.currentTimeMillis(); try { if (end - start < 50) { Thread.sleep(50 - (end - start)); } } catch(InterruptedException e){ e.printStackTrace(); } } } /** * Surfaceview视图状态发生改变,响应此函数 */ @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } /** * Surfaceview视图消亡,响应此函数 */ @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub } }
像素碰撞:在游戏开发中是不推荐使用的,虽然它很精确,但是会造成代码的效率降低。因为有可以代替像素碰撞检测的方法:多矩形、多圆形的检测方式。下面进行介绍
多矩形碰撞:顾名思义就是设置多个矩形碰撞区域
下面创建一个实例:MoreRectCollsion项目
效果图:
==>MySurfaceView.java
package com.rectCollsion; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; public class MySurfaceView extends SurfaceView implements Callback,Runnable{ //用于控制SurfaceView private SurfaceHolder sfh; //声明一个画笔 private Paint paint; //声明一个线程 private Thread th; //线程消亡的标志位 private boolean flag; //声明一个画布 private Canvas canvas; //声明屏幕的宽高 private int screenW,screenH; //定义两个矩形的宽高坐标 private int rectX1 = 10,rectY1 = 10,rectW1 = 40,rectH1 = 40; private int rectX2 = 100,rectY2 = 110,rectW2 = 40,rectH2 = 40; //便于观察是否发生碰撞设置一个标识位 private boolean isCollsion; //定义第一个矩形的矩形碰撞数组 private Rect clipRect1 = new Rect(0,0,15,15); private Rect clipRect2 = new Rect(rectW1 - 15, rectH1 - 15,rectW1,rectH1); private Rect[] arrayRect1 = new Rect[]{clipRect1,clipRect2}; //定义第二个矩形的矩形碰撞数组 private Rect clipRect3 = new Rect(0,0,15,15); private Rect clipRect4 = new Rect(rectW2 - 15,rectH2 - 15,rectW2,rectH2); private Rect[] arrayRect2 = new Rect[]{clipRect3,clipRect4}; /** * SurfaceView初始化函数 */ public MySurfaceView(Context context){ super(context); //实例SurfaceHolder sfh = this.getHolder(); //为SurfaceView添加状态监听 sfh.addCallback(this); //实例一个画笔 paint = new Paint(); //设置画笔颜色为白色 paint.setColor(Color.WHITE); //设置焦点 setFocusable(true); } @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub screenW = this.getWidth(); screenH = this.getHeight(); flag = true; //实例线程 th = new Thread(this); //启动线程 th.start(); } /** * 游戏绘图 */ public void myDraw(){ try { canvas = sfh.lockCanvas(); if(canvas != null){ //刷屏 canvas.drawColor(Color.BLACK); //判断是否发生了碰撞 if(isCollsion){//发生碰撞 paint.setColor(Color.RED); paint.setTextSize(20); canvas.drawText("Collision!", 0, 30, paint); } else{//没发生碰撞 paint.setColor(Color.WHITE); } //绘制两个矩形 canvas.drawRect(rectX1, rectY1, rectX1 + rectW1, rectY1 + rectH1, paint); canvas.drawRect(rectX2, rectY2, rectX2 + rectW2, rectY2 + rectH2, paint); //----绘制碰撞区为非填充,并设置画笔为白色 paint.setStyle(Style.STROKE); paint.setColor(Color.WHITE); //---绘制第一个矩形所有矩形碰撞区域 for(int i=0; i < arrayRect1.length; i++){ canvas.drawRect(arrayRect1[i].left + this.rectX1, arrayRect1[i].top + this.rectY1, arrayRect1[i].right + this.rectX1, arrayRect1[i].bottom + this.rectY1, paint); } //---绘制第二个矩形所有矩形碰撞区域 for(int i=0;i < arrayRect2.length; i++){ canvas.drawRect(arrayRect2[i].left + this.rectX2, arrayRect2[i].top + this.rectY2, arrayRect2[i].right + this.rectX2,arrayRect2[i].bottom + this.rectY2, paint); } } }catch(Exception e){ }finally{ if(canvas != null) sfh.unlockCanvasAndPost(canvas); } } /** * 触屏事件监听 */ @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub //让矩形1随着触屏位置移动(触屏点设为此矩形的中心点) rectX1 = (int) event.getX() - rectW1/2; rectY1 = (int) event.getY() - rectH1/2; //当矩形之间发生碰撞 if(isCollsionWithRect(arrayRect1, arrayRect2)){ isCollsion = true;//设置标志位为真 //当矩形之间没有发生碰撞 }else{ isCollsion = false; //设置标志位为假 } return true; } /** * 矩形碰撞的函数 * @param left 表示矩形左上角坐标的X坐标 * @param top 表示矩形左上角坐标Y坐标 * @param right 表示矩形右下角坐标X坐标 * @param buttom 表示矩形右下角坐标Y坐标 */ public boolean isCollsionWithRect(Rect[] rectArray, Rect[] rect2Array){ Rect rect = null; Rect rect2 = null; for(int i = 0; i < rectArray.length; i++){ //依次取出第一个矩形数组的每个矩形实例 rect = rectArray[i]; //获取到第一个矩形数组中每个矩形元素的属性值 int x1 = rect.left + this.rectX1; int y1 = rect.top + this.rectY1; int w1 = rect.right - rect.left; int h1 = rect.bottom - rect.top; for(int j = 0; j < rect2Array.length; j++){ //依次取出第二个矩形数组的每个矩形实例 rect2 = rect2Array[i]; //获取到第二个矩形数组中每个矩形的属性值 int x2 = rect2.left + this.rectX1; int y2 = rect2.top + this.rectY2; int w2 = rect2.right - rect2.left; int h2 = rect2.bottom - rect2.top; //进行循环遍历两个矩形碰撞数组所有元素之间的位置关系 if(x1 >= x2 && x1 > x2 + w2){ }else if(x1 <= x2 && x1 + w1 <= x2) { }else if(y1 >=y2 && y1 >= y2 + h2){ }else if(y1 <=y2 && y1 + h1 <= y2){ }else { //只要有一个碰撞矩形数组与另一个碰撞矩形数组发生碰撞则认为碰撞 return true; } } } return false; } /** * 按键事件监听 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // TODO Auto-generated method stub return super.onKeyDown(keyCode, event); } /** * 游戏逻辑 */ private void logic(){ } @Override public void run() { // TODO Auto-generated method stub while(flag){ long start = System.currentTimeMillis(); myDraw(); logic(); long end = System.currentTimeMillis(); try { if (end - start < 50) { Thread.sleep(50 - (end - start)); } } catch(InterruptedException e){ e.printStackTrace(); } } } /** * Surfaceview视图状态发生改变,响应此函数 */ @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } /** * Surfaceview视图消亡,响应此函数 */ @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub } }
多圆形碰撞跟多矩形碰撞是类似的,下面还有一种碰撞:Region碰撞,Region是一个类,这个类比较常用的方法就是用于判断一个点是否在矩形区域内,其方法是使是Regions类中的contains(int x, int y)函数
创建实例:RegionCollsion
项目运行效果:
==>MySurfaceView.java
package com.rectCollsion; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; public class MySurfaceView extends SurfaceView implements Callback,Runnable{ //用于控制SurfaceView private SurfaceHolder sfh; //声明一个画笔 private Paint paint; //声明一个线程 private Thread th; //线程消亡的标志位 private boolean flag; //声明一个画布 private Canvas canvas; //声明屏幕的宽高 private int screenW,screenH; //定义两个矩形的宽高坐标 private int rectX1 = 10,rectY1 = 10,rectW1 = 40,rectH1 = 40; private int rectX2 = 100,rectY2 = 110,rectW2 = 40,rectH2 = 40; //便于观察是否发生碰撞设置一个标识位 private boolean isCollsion; //定义第一个矩形的矩形碰撞数组 private Rect clipRect1 = new Rect(0,0,15,15); private Rect clipRect2 = new Rect(rectW1 - 15, rectH1 - 15,rectW1,rectH1); private Rect[] arrayRect1 = new Rect[]{clipRect1,clipRect2}; //定义第二个矩形的矩形碰撞数组 private Rect clipRect3 = new Rect(0,0,15,15); private Rect clipRect4 = new Rect(rectW2 - 15,rectH2 - 15,rectW2,rectH2); private Rect[] arrayRect2 = new Rect[]{clipRect3,clipRect4}; /** * SurfaceView初始化函数 */ public MySurfaceView(Context context){ super(context); //实例SurfaceHolder sfh = this.getHolder(); //为SurfaceView添加状态监听 sfh.addCallback(this); //实例一个画笔 paint = new Paint(); //设置画笔颜色为白色 paint.setColor(Color.WHITE); //设置焦点 setFocusable(true); } @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub screenW = this.getWidth(); screenH = this.getHeight(); flag = true; //实例线程 th = new Thread(this); //启动线程 th.start(); } /** * 游戏绘图 */ public void myDraw(){ try { canvas = sfh.lockCanvas(); if(canvas != null){ //刷屏 canvas.drawColor(Color.BLACK); //判断是否发生了碰撞 if(isCollsion){//发生碰撞 paint.setColor(Color.RED); paint.setTextSize(20); canvas.drawText("Collision!", 0, 30, paint); } else{//没发生碰撞 paint.setColor(Color.WHITE); } //绘制两个矩形 canvas.drawRect(rectX1, rectY1, rectX1 + rectW1, rectY1 + rectH1, paint); canvas.drawRect(rectX2, rectY2, rectX2 + rectW2, rectY2 + rectH2, paint); //----绘制碰撞区为非填充,并设置画笔为白色 paint.setStyle(Style.STROKE); paint.setColor(Color.WHITE); //---绘制第一个矩形所有矩形碰撞区域 for(int i=0; i < arrayRect1.length; i++){ canvas.drawRect(arrayRect1[i].left + this.rectX1, arrayRect1[i].top + this.rectY1, arrayRect1[i].right + this.rectX1, arrayRect1[i].bottom + this.rectY1, paint); } //---绘制第二个矩形所有矩形碰撞区域 for(int i=0;i < arrayRect2.length; i++){ canvas.drawRect(arrayRect2[i].left + this.rectX2, arrayRect2[i].top + this.rectY2, arrayRect2[i].right + this.rectX2,arrayRect2[i].bottom + this.rectY2, paint); } } }catch(Exception e){ }finally{ if(canvas != null) sfh.unlockCanvasAndPost(canvas); } } /** * 触屏事件监听 */ @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub //让矩形1随着触屏位置移动(触屏点设为此矩形的中心点) rectX1 = (int) event.getX() - rectW1/2; rectY1 = (int) event.getY() - rectH1/2; //当矩形之间发生碰撞 if(isCollsionWithRect(arrayRect1, arrayRect2)){ isCollsion = true;//设置标志位为真 //当矩形之间没有发生碰撞 }else{ isCollsion = false; //设置标志位为假 } return true; } /** * 矩形碰撞的函数 * @param left 表示矩形左上角坐标的X坐标 * @param top 表示矩形左上角坐标Y坐标 * @param right 表示矩形右下角坐标X坐标 * @param buttom 表示矩形右下角坐标Y坐标 */ public boolean isCollsionWithRect(Rect[] rectArray, Rect[] rect2Array){ Rect rect = null; Rect rect2 = null; for(int i = 0; i < rectArray.length; i++){ //依次取出第一个矩形数组的每个矩形实例 rect = rectArray[i]; //获取到第一个矩形数组中每个矩形元素的属性值 int x1 = rect.left + this.rectX1; int y1 = rect.top + this.rectY1; int w1 = rect.right - rect.left; int h1 = rect.bottom - rect.top; for(int j = 0; j < rect2Array.length; j++){ //依次取出第二个矩形数组的每个矩形实例 rect2 = rect2Array[i]; //获取到第二个矩形数组中每个矩形的属性值 int x2 = rect2.left + this.rectX1; int y2 = rect2.top + this.rectY2; int w2 = rect2.right - rect2.left; int h2 = rect2.bottom - rect2.top; //进行循环遍历两个矩形碰撞数组所有元素之间的位置关系 if(x1 >= x2 && x1 > x2 + w2){ }else if(x1 <= x2 && x1 + w1 <= x2) { }else if(y1 >=y2 && y1 >= y2 + h2){ }else if(y1 <=y2 && y1 + h1 <= y2){ }else { //只要有一个碰撞矩形数组与另一个碰撞矩形数组发生碰撞则认为碰撞 return true; } } } return false; } /** * 按键事件监听 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // TODO Auto-generated method stub return super.onKeyDown(keyCode, event); } /** * 游戏逻辑 */ private void logic(){ } @Override public void run() { // TODO Auto-generated method stub while(flag){ long start = System.currentTimeMillis(); myDraw(); logic(); long end = System.currentTimeMillis(); try { if (end - start < 50) { Thread.sleep(50 - (end - start)); } } catch(InterruptedException e){ e.printStackTrace(); } } } /** * Surfaceview视图状态发生改变,响应此函数 */ @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } /** * Surfaceview视图消亡,响应此函数 */ @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub } }