最近在做一個有關投籃的小游戲,需要用到像素級碰撞檢測,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 }