Box2Dの衝突判定と、衝突のグループ・カテゴリ分けをする方法のメモです。
衝突判定
Box2Dでは、衝突が起こった物体同士のリストが作成させるので、そのリストを取得して、衝突判定をします。
衝突の起こった物体同士は、b2Worldクラスのm_contactList(ContactListクラス)にb2Contactオブジェクトとして入れられます。
このm_contactListを、Box2Dの演算時に、順次取り出して、衝突時の処理をします。
衝突のグループ・カテゴリ分け
Box2Dで作った物体は、デフォルトでは全ての物体と衝突するようになっています。
これを、ある物体とは衝突するけどある物体とは衝突しないような、グループ分けをするには、b2ShapeDefクラスのfilter(b2FilterDataクラス)の値を変更します。
衝突のグループ・カテゴリ分けの方法
下記の二種類があって、両方を併用することもできます。
- groupIndex を使用する
- categoryBit と maskBit を使用する
1.groupIndexを使用する
groupIndexを0以外の値を指定して、グループを作る。値が正負かによって、処理が異なる。
- 正の同じ値のグループ内の物体は、maskBit・categoryBitの値が何であれ、必ず衝突する。
- 負の同じ値のグループ内の物体は、maskBit・categoryBitの値が何であれ、必ず衝突しない。
別の値のグループとは、maskBit・categoryBitの値によって、衝突判定が決まる。
2.categoryBit と maskBit を使用する
16進数4桁(2進数16桁)の categoryBit を指定。(0x0001)
maskBitも同じく16進数4桁をつける
categoryBitとmaskBitを論理積(AND)で演算して、0にならない物体同士は衝突。
- Aグループ:
- categoryBit=0x0001(0000 0000 0000 0001)
- Bグループ:
- categoryBit=0x0002(0000 0000 0000 0010)
- Cグループ:
- categoryBit=0x0004(0000 0000 0000 0100)
各グループ内の物体同士を衝突させる
- A
- maskBit=0x0003(0000 0000 0000 0011)
- B
- maskBit=0x0003(0000 0000 0000 0011)
- C
- maskBit=0x0003(0000 0000 0000 0100)
各グループ内の物体同士と、AとBグループの物体同士を衝突させる
- AとB
- maskBit=0x0006(0000 0000 0000 0011)
- C
- maskBit=0x0006(0000 0000 0000 0100)
maskBit・categoryBitのみで衝突のグループ分けしたい場合は、groupIndexをデフォルトの0のままにしておく
Body作成後に、m_filterの値を変えた場合は、b2WorldクラスのRefilterメソッドを使って、フィルターを再設定してやる必要があるようです。
変えなくてもフィルター設定反映されたのですが、念のため。
サンプルコード
※Box2Dのまとめたクラス(Box2DBase)を作って、基本クラスで使ってます。ソースは物理エンジンライブラリ Box2DFlashAS3(基本)にあります。
マウスジョイントの辺りは、Box2Dの付属コード、TestBedのTest.asのサンプルからほぼコピペです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
public class StudyB2Contact extends B2Base { public function StudyB2Contact() { makeB2Walls(); makeGroup(); this.addEventListener(Event.ENTER_FRAME, update, false, 0, true); //マウスジョイント用 this.addEventListener(MouseEvent.MOUSE_DOWN, this.onMouseDown, false, 0, true); this.addEventListener(MouseEvent.MOUSE_UP, this.onMouseUp, false, 0, true); } //Box2D 上下左右の壁作成 private function makeB2Walls():void { //デフォルトの衝突フィルターの値 //categorybit = 0x0001 //maskBit = 0xFFFF //groupIndex = 0 this.makeB2BodyBox(620, 10, 300, 5, 0, 0, 0.5, 0.5); //上 this.makeB2BodyBox(620, 10, 300, 495, 0, 0, 0.5, 0.5); //下 this.makeB2BodyBox(10, 520, 5, 250, 0, 0, 0.5, 0.5); //左 this.makeB2BodyBox(10, 520, 595, 250, 0, 0, 0.5, 0.5); //右 } //衝突のグループ分けした物体作成 private function makeGroup():void { //表示用Sprite var disp_box = new Sprite(); this.addChild(disp_box); var disp_item = new Sprite(); this.addChild(disp_item); var i:int; var s_item_num:int = 3; var item_size:int = 40; var box_size:int = 100; var category_bit:uint; var mask_bit:uint; //赤と赤、緑と緑、赤と緑は衝突 //壁とも衝突 //赤色 category_bit = 0x0002; // 0000 0000 0000 0010 mask_bit = 0x0007; // 0000 0000 0000 0111 var box_red:Sprite = new box_red_mc as Sprite; this.setBox(box_size, box_size, 120, 150, box_red, disp_box, category_bit, mask_bit); for (i = 0; i < s_item_num; i++) { var kinoko_red:Sprite = new kinoko_red_mc as Sprite; this.setCircle(item_size, Math.random() * 500, 20, kinoko_red, disp_item, category_bit, mask_bit); } //緑色 category_bit = 0x0004; // 0000 0000 0000 0100 mask_bit = 0x0007; // 0000 0000 0000 0111 var box_green:Sprite = new box_green_mc as Sprite; this.setBox(box_size, box_size, 240, 350, box_green, disp_box, category_bit, mask_bit); for (i = 0; i < s_item_num; i++) { var kinoko_green:Sprite = new kinoko_green_mc as Sprite; this.setCircle(item_size, Math.random() * 500, 20, kinoko_green, disp_item, category_bit, mask_bit); } //黄と黄は衝突 //壁とも衝突 //黄色 category_bit = 0x0011; // 0000 0000 0001 0001 mask_bit = 0x0011; // 0000 0000 0001 0001 var box_yellow:Sprite = new box_yellow_mc as Sprite; this.setBox(box_size, box_size, 480, 350, box_yellow, disp_box, category_bit, mask_bit); for (i = 0; i < s_item_num; i++) { var kinoko_yellow:Sprite = new kinoko_yellow_mc as Sprite; this.setCircle(item_size, Math.random() * 500, 20, kinoko_yellow, disp_item, category_bit, mask_bit); } //青は壁以外衝突しない(青と青も衝突しない) //青色 category_bit = 0x0008; // 0000 0000 0000 1000 mask_bit = 0x0001; // 0000 0000 0000 0001 var box_blue:Sprite = new box_blue_mc as Sprite; this.setBox(box_size, box_size, 360, 150, box_blue, disp_box, category_bit, mask_bit); for (i = 0; i < s_item_num; i++) { var kinoko_blue:Sprite = new kinoko_blue_mc as Sprite; this.setCircle(item_size, Math.random() * 500, 20, kinoko_blue, disp_item, category_bit, mask_bit); } } //四角い物体作成 private function setBox(w:Number, h:Number, x:Number, y:Number, disp_block:Sprite, disp_blocks:Sprite, category_bits:uint, mask_bit:uint):void { disp_block.width = w; disp_block.height = h; var block:b2Body = this.makeB2BodyBox(w, h, x, y, 0, 0, 0.5, 0.5, disp_block, disp_blocks); block.m_shapeList.m_filter.categoryBits = category_bits; block.m_shapeList.m_filter.maskBits = mask_bit; //フィルターの更新 this.m_b2_world.Refilter(block.m_shapeList); } //丸い物体作成 private function setCircle(size:Number, x:Number, y:Number, disp_block:Sprite, disp_blocks:Sprite, category_bits:uint, mask_bit:uint):void { disp_block.width = size; disp_block.height = size; var block:b2Body = this.makeB2BodyCircle(size, x, y, 1, 0.5, 0.5, disp_block, disp_blocks); block.m_shapeList.m_filter.categoryBits = category_bits; block.m_shapeList.m_filter.maskBits = mask_bit; //フィルターの更新 this.m_b2_world.Refilter(block.m_shapeList); } //演算 private function update(e:Event):void { m_b2_world.Step(m_b2_timeStep, m_b2_iterations); for (var bb:b2Body = m_b2_world.m_bodyList; bb; bb = bb.m_next){ if (bb.m_userData is Sprite){ bb.m_userData.x = bb.GetPosition().x * m_b2_physcale; bb.m_userData.y = bb.GetPosition().y * m_b2_physcale; bb.m_userData.rotation = bb.GetAngle() * (180 / Math.PI); } } //衝突判定 for (var cbb:b2Contact = m_b2_world.m_contactList; cbb; cbb = cbb.m_next) { var userdata1:MovieClip = cbb.m_shape1.m_body.m_userData as MovieClip; if (userdata1 is Sprite) { userdata1.gotoAndPlay("contact"); } var userdata2:MovieClip = cbb.m_shape2.m_body.m_userData as MovieClip; if (userdata2 is Sprite) { userdata2.gotoAndPlay("contact"); } } //マウスジョイント this.updateMouseWorld(); this.mouseDrag(); } //マウスジョイント設定 --------------------------------------------------------------------- //Box2D付属のGeneralのInputをほぼこコピペ // world mouse position private var m_mouseJoint:b2MouseJoint; //マウスジョイント private var m_mouseXWorldPhys:Number; //メートル単位のマウス座標 private var m_mouseYWorldPhys:Number; private var m_mouse_dowm:Boolean = false; //マウスボタン押されてるか private function onMouseDown(e:Event):void { this.m_mouse_dowm = true; } private function onMouseUp(e:Event):void { this.m_mouse_dowm = false; } //マウス座標取得 public function updateMouseWorld():void{ //メートル単位のマウス座標に変換 m_mouseXWorldPhys = this.mouseX / this.m_b2_physcale; m_mouseYWorldPhys = this.mouseY / this.m_b2_physcale; } //マウスドラッグ時 public function mouseDrag():void{ //マウスボタン押した(ジョイントまだ) if (this.m_mouse_dowm && !this.m_mouseJoint){ var body:b2Body = GetBodyAtMouse(); //マウスの下の物体取得 if (body) { var md:b2MouseJointDef = new b2MouseJointDef(); md.body1 = this.m_b2_world.GetGroundBody(); md.body2 = body; md.target.Set(this.m_mouseXWorldPhys, this.m_mouseYWorldPhys); md.maxForce = 300000.0 * body.GetMass(); md.timeStep = this.m_b2_timeStep; m_mouseJoint = this.m_b2_world.CreateJoint(md) as b2MouseJoint; body.WakeUp(); } } //マウスボタン離した if (!this.m_mouse_dowm){ if (this.m_mouseJoint) { this.m_b2_world.DestroyJoint(m_mouseJoint); m_mouseJoint = null; } } //マウス押されてる状態(ジョイントされてる) if (this.m_mouse_dowm && this.m_mouseJoint) { var p2:b2Vec2 = new b2Vec2(this.m_mouseXWorldPhys, this.m_mouseYWorldPhys); m_mouseJoint.SetTarget(p2); } } //マウスの下の物体取得 private var mousePVec:b2Vec2 = new b2Vec2(); public function GetBodyAtMouse(includeStatic:Boolean=false):b2Body{ // Make a small box. mousePVec.Set(this.m_mouseXWorldPhys, this.m_mouseYWorldPhys); var aabb:b2AABB = new b2AABB(); aabb.lowerBound.Set(this.m_mouseXWorldPhys - 0.001, this.m_mouseYWorldPhys - 0.001); aabb.upperBound.Set(this.m_mouseXWorldPhys + 0.001, this.m_mouseYWorldPhys + 0.001); // Query the world for overlapping shapes. var k_maxCount:int = 10; var shapes:Array = new Array(); var count:int = this.m_b2_world.Query(aabb, shapes, k_maxCount); var body:b2Body = null; for (var i:int = 0; i < count; ++i) { if (shapes[i].GetBody().IsStatic() == false || includeStatic) { var tShape:b2Shape = shapes[i] as b2Shape; var inside:Boolean = tShape.TestPoint(tShape.GetBody().GetXForm(), mousePVec); if (inside) { body = tShape.GetBody(); break; } } } return body; } } |