前言
不久之前在论坛上有人发贴,使用java编写的超级马里奥如何实现碰撞检测,笔者自己以前
也做过Tank大战。里面同样涉及到碰撞检测,翻翻U盘里的东西还在,什么时候也给共享出来。
这篇文章就简单游戏中的碰撞检测做一个简单的总结。首先需声明的是这里只是2D的碰撞检测。
碰撞检测
对于形状之间如何来判断是否是碰撞的这要根据具体的形状来定。在新手练手的小游戏中,
物体形状一般可以设定为矩形区域,这类规则图形。它的碰撞检测可以通过java API中的
Rectangle类来实现碰撞的检测。
规则图形碰撞检测(Rectangle)
首先我们查看API关于Rectangle类的介绍:它就是指定坐标空间的一个区域,这个区域是通过
指定左上角x、y坐标和去高度和宽度来确定的。
接下来看起具体的方法public Rectangle intersection(Rectangle r),这个方法就是碰撞检测的
关键了,如果两个Rectangle对象有交集,那么他们就有碰撞了。而每个形状我们都可以得到他们
的Rectangle对象,这样图形的碰撞检测也就得以实现了。
看下具体实现源码:
- /* 判断子弹是否击中障碍物 */
- public boolean isHit(com.Alex.map.Map map) {
- boolean flag = true;// 代表没有撞到
- // 分类别的得到所有的障碍物
- List<Stuff> stuffList = new Vector<Stuff>();
- stuffList.addAll(map.getBricks());
- stuffList.addAll(map.getIrons());
- stuffList.addAll(map.getWaters());
- for (int i = 0; i < stuffList.size(); i++) {
- Stuff a = stuffList.get(i);
- Rectangle tankRectangle = new Rectangle(bullet2.getRec());
- Rectangle stuffRectangle = new Rectangle(a.getX(), a.getY(), 20, 20);
- if (stuffRectangle.intersects(tankRectangle)) {
- flag = false;// 撞到了
- break;
- }
- }
- return flag;
- }
上述这个例子就是判断Tank发出的子弹是否对地图中的障碍物有碰撞,如果有的话
就做相关的操作(子弹爆炸、障碍物消失)。上述代码中子弹对象有一个getRec()方法就是
得到子弹图形的Rectangle对象,具体实现就是根据其坐标和width、height来生成的。
采用此种方法进行碰撞检测需要注意,对于图片的实现处理应该尽量的去掉图标边角
的空白,不然实际效果可以产生肉眼可辨的误差。也就是说Rectangle尽量的包住图形
且Rectangle的区域尽量小。这种碰撞检测的方法被称之为多矩形碰撞。
一旦有一个矩形数组中的矩形与另外一个矩形数组的矩形发生碰撞就可认为发生了
多矩形碰撞。其中多圆形碰撞也是同样的道理,只是包裹的图形区域是圆形罢了。
不过仔细思考多矩形碰撞同样会有误差,虽然这种误差十分小。
像素级别的碰撞检测
像素级别的碰撞检测算得上是最精确的碰撞检测方法了。
首先遍历算出一张位图所有的像素点坐标,然后与另外一张位图上的所有点坐标进行对比,
一旦有一个像素点的坐标相同,就立刻取出这两个坐标相同的像素点,通过位运算取出这两个
像素点的最高位(透明度)进行对比,如果两个像素点都是非透明像素则判定这两张位图发生
碰撞。
介绍了像素碰撞之后可以得到两个结论:
1、像素碰撞很精确,不论位图之间是否带有透明像素,都可以精确判断;
2、正是因为像素碰撞的这种高精确判定,从而也会造成代码效率明显降低!
假设两张100×100 大小的位图利用像素级检测碰撞,仅是遍历两张位图的像素点就要循环
100×100×2=20000 句逻辑代码;况且还要对筛选出来的相同坐标的像素点进行遍历对比其
透明值!这种效率可想而知!
当然,这里的像素碰撞只是大致提供一种思路,肯定还可以进行代码优化;但是不论再优的
代码,使用像素级进行碰撞检测终会导致整个程序的运行效率大大降低。因此像素级别的碰
撞检测在游戏开发中是尽量避免使用的!
规则图形碰撞检测2
对于子弹和障碍物的碰撞检测,采用上述第一种方法就可以简单的实现了,不过子弹是圆形的
有没有更加精确的碰撞检测方法呢?也就是实现圆形和矩形的碰撞检测嘛。
这里我们需要简单的运行下几何数学的知识,给个简单的图就会明白了。
小圆有个运动轨迹,轨迹的线如果和他对着的正方形的相对某一象限的边有焦点,那么
就能碰撞,边就是那一个象限的边(还要把圆半径算进去),具体代码就不实现了,读者可
自己尝试着去实现。
不规则图形碰撞检测
这里直接转载一篇文章:
http://www.cnblogs.com/Kurodo/archive/2012/08/08/2628688.html
两个物体之间没有发生碰撞,这个轴线叫做Separating Axis(红色轴线)。
对于2D来说,红色线就是垂直与多边形边的轴。
因此,如果我们要检查两多边形是否碰撞,就去检查两多边形在每个所有可能的轴上的投影
是否重叠。
- /// 检测2个矩形是否发生碰撞
- /// </summary>
- /// <returns></returns>
- public static bool IsIntersect (Vector2[] A, Vector2[] B)
- {
- Vector2 AX, AY, BX, BY;
- AX = new Vector2();
- AY = new Vector2();
- BX = new Vector2();
- BY = new Vector2();
- AX.X = A[0].X - A[1].X;
- AX.Y = A[0].Y - A[1].Y;
- AY.X = A[0].X - A[3].X;
- AY.Y = A[0].Y - A[3].Y;
- BX.X = B[0].X - B[1].X;
- BX.Y = B[0].Y - B[1].Y;
- BY.X = B[0].X - B[3].X;
- BY.Y = B[0].Y - B[3].Y;
- //对于AX上:
- if (Tmp(AX, A, B)) return false;
- if (Tmp(AY, A, B)) return false;
- if (Tmp(BX, A, B)) return false;
- if (Tmp(BY, A, B)) return false;
- return true;
- }
- private static bool Tmp(Vector2 IS,Vector2[] A,Vector2[] B)
- {
- float[] v = new float[4];
- for (int i = 0; i < 4; i++)
- {
- float tmp = (IS.X * A[i].X + IS.Y * A[i].Y) / (IS.X * IS.X + IS.Y * IS.Y);
- v[i] = tmp * IS.X * IS.X + tmp * IS.Y * IS.Y;
- }
- float[] vv = new float[4];
- for (int i = 0; i < 4; i++)
- {
- float tmp = (IS.X * B[i].X + IS.Y * B[i].Y) / (IS.X * IS.X + IS.Y * IS.Y);
- vv[i] = tmp * IS.X * IS.X + tmp * IS.Y * IS.Y;
- }
- if (Math.Max(Math.Max(v[0], v[1]),Math.Max(v[2],v[3])) >Math.Min(Math.Min(vv[0],vv[1]),Math.Min(vv[2],vv[3])) && Math.Min(Math.Min(v[0],v[1]),Math.Min(v[2],v[3])) < Math.Max(Math.Max(vv[0],vv[1]),Math.Max(vv[2],vv[3]))) {
- return false;
- }//表示暂时不知道是否碰撞
- else return true;//表示知道未碰撞
- }