分享一位国外大牛写的不规则物体像素级碰撞检测算法及详解
最近在做一个有关投篮的小游戏,需要用到像素级碰撞检测,as3自带的hitTestObject显然无法满足需要。网上搜寻了一下,在9ria挖坟挖到两篇好文章:
第一篇文章介绍了一位国外大牛写的不规则物体像素级碰撞检测算法,原理是用bitmap绘制两对象不透明区域,利用混合模式计算出两对象的相交区域。
第二篇文章则在该算法的基础上进行了效率的优化,原理是判断出两对象发生hitTestObject碰撞后,将碰撞矩形区域缩小至20*20进行判断,缩小后的检测结果可能与实际结果不同,因此需要改变缩小倍率进行多次检测。但效率也已比原算法要优化,特别在针对大尺寸的像素检测时,效率可提高50%~500%,20*20的小尺寸对象则效率持平。
在实际测试中,优化过的算法,一如网友反映的那样,当检测物体不旋转时检测正常;当检测物体是旋转的情况,检测结果会有异常,其效果类似于自带的hitTestObject,无法做到像素级检测。根据自己游戏的需要,检测的物体大小并不大,在80*80以内,且一定要能在物体旋转情况下检测正常,所以使用了原算法。现附上算法的详细注释,更好理解算法原理:
1 package 2 { 3 import flash.display.BitmapData; 4 import flash.display.BlendMode; 5 import flash.display.DisplayObject; 6 import flash.display.Sprite; 7 8 import flash.geom.ColorTransform; 9 import flash.geom.Matrix; 10 import flash.geom.Point; 11 import flash.geom.Rectangle; 12 13 /** 14 * 高效的不规则物体碰撞检测类 15 */ 16 public class HitTest 17 { 18 public function HitTest() 19 { 20 } 21 22 /** 判断两物体是否发生碰撞(可调节精度) */ 23 public static function complexHitTestObject( target1:DisplayObject, target2:DisplayObject, accuracy:Number = 1 ):Boolean 24 { 25 return complexIntersectionRectangle( target1, target2, accuracy ).width != 0; 26 } 27 28 /** 获取碰撞相交矩形区域 */ 29 public static function intersectionRectangle( target1:DisplayObject, target2:DisplayObject ):Rectangle 30 { 31 // 如果有任一对象没加入显示列表,或者两对象hitTestObject的结果为false,则代表两对象没有发生碰撞 32 if( !target1.root || !target2.root || !target1.hitTestObject( target2 ) ) return new Rectangle(); 33 34 // 分别得到两对象的显示矩形区域 35 var bounds1:Rectangle = target1.getBounds( target1.root ); 36 var bounds2:Rectangle = target2.getBounds( target2.root ); 37 38 // 得出两对象相交部分的矩形区域 39 var intersection:Rectangle = new Rectangle(); 40 intersection.x = Math.max( bounds1.x, bounds2.x ); 41 intersection.y = Math.max( bounds1.y, bounds2.y ); 42 intersection.width = Math.min( ( bounds1.x + bounds1.width ) - intersection.x, ( bounds2.x + bounds2.width ) - intersection.x ); 43 intersection.height = Math.min( ( bounds1.y + bounds1.height ) - intersection.y, ( bounds2.y + bounds2.height ) - intersection.y ); 44 45 return intersection; 46 } 47 48 /** 获取碰撞相交矩形区域(可调节精度) */ 49 public static function complexIntersectionRectangle( target1:DisplayObject, target2:DisplayObject, accuracy:Number = 1 ):Rectangle 50 { 51 //不允许设置accuracy小于0,会抛出错误 52 if( accuracy <= 0 ) throw new Error( "ArgumentError: Error #5001: Invalid value for accurracy", 5001 ); 53 54 //如果两对象hitTestObject的结果为false,则代表两对象没有发生碰撞 55 if( !target1.hitTestObject( target2 ) ) return new Rectangle(); 56 57 var hitRectangle:Rectangle = intersectionRectangle( target1, target2 ); 58 // 判断重叠区域的长宽任一是否超过碰撞临界值,没超过则视为两对象没有发生碰撞。临界值默认为1,可根据accuracy调节精度 59 if( hitRectangle.width * accuracy <1 || hitRectangle.height * accuracy <1 ) return new Rectangle(); 60 61 62 //---------------------------------- 核心算法--------------------------------------- 63 //创建一个用于draw的临时BitmapData对象 64 var bitmapData:BitmapData = new BitmapData( hitRectangle.width * accuracy, hitRectangle.height * accuracy, false, 0x000000 ); 65 66 //把target1的不透明处绘制为指定颜色 67 bitmapData.draw( target1, HitTest.getDrawMatrix( target1, hitRectangle, accuracy ), new ColorTransform( 1, 1, 1, 1, 255, -255, -255, 255 ) ); 68 //把target2的不透明处绘制为指定颜色,并将混合模式设置为DIFFERENCE模式 69 bitmapData.draw( target2, HitTest.getDrawMatrix( target2, hitRectangle, accuracy ), new ColorTransform( 1, 1, 1, 1, 255, 255, 255, 255 ), BlendMode.DIFFERENCE ); 70 71 //target1与target2的不透明处如果发生相交,那么相交部分区域的32位颜色信息必为0xFF00FFFF,即得出两对象的像素碰撞区域 72 var intersection:Rectangle = bitmapData.getColorBoundsRect( 0xFFFFFFFF,0xFF00FFFF ); 73 74 bitmapData.dispose(); 75 //---------------------------------- --------------------------------------- 76 77 // Alter width and positions to compensate for accurracy 78 //前面是乘以accuracy缩放两对象后,再通过叠加模式计算出相交区域的,因此在此要再除以一次accuracy,恢复原本相交区域大小 79 if( accuracy != 1 ) 80 { 81 intersection.x /= accuracy; 82 intersection.y /= accuracy; 83 intersection.width /= accuracy; 84 intersection.height /= accuracy; 85 } 86 87 intersection.x += hitRectangle.x; 88 intersection.y += hitRectangle.y; 89 90 return intersection; 91 } 92 93 94 protected static function getDrawMatrix( target:DisplayObject, hitRectangle:Rectangle, accurracy:Number ):Matrix 95 { 96 var localToGlobal:Point; 97 var matrix:Matrix; 98 99 var rootConcatenatedMatrix:Matrix = target.root.transform.concatenatedMatrix; 100 101 localToGlobal = target.localToGlobal( new Point( ) ); 102 matrix = target.transform.concatenatedMatrix; 103 matrix.tx = localToGlobal.x - hitRectangle.x; 104 matrix.ty = localToGlobal.y - hitRectangle.y; 105 106 matrix.a = matrix.a / rootConcatenatedMatrix.a; 107 matrix.d = matrix.d / rootConcatenatedMatrix.d; 108 if( accurracy != 1 ) matrix.scale( accurracy, accurracy ); 109 110 return matrix; 111 } 112 113 } 114 }