ブロック崩し衝突判定の高速化の為に
ゲーム製作には欠かせないオブジェクトとオブジェクトの衝突判定。Flashでのブロック崩しの場合では通常 次のようなボールとブロックとの衝突判定を行うと思います。
//ブロック数(50個)分ループ for( var i=0 ; i < 50 ; i++ ){ //ボール(ballMC)とブロック(blockMC0~49)との衝突判定 if( ballMC.hitTest( this[ "blockMC" + i ] ) == true ){ //衝突時の処理実行 ~ } }
最近のPCは性能がよく多少難ありの処理でも問題なく軽快に動作します。上記のようなブロック数分ループさせ衝突判定を行う処理を記述するのはとても簡単で、開発時間短縮にもなりますので 通常はこれでまったく問題ないと思います。
しかしブロック数が50や100ならいいですが、1000や2000になったことを考えた場合、またはスペックの低いマシンでの動作を考えた場合、他には ボールが一つではなく50個100個になった場合等 上記衝突判定方法だと ものすごく処理が重たくなってしまいます。
最近FlashLite用ゲーム製作にてブロック崩しを作成したのですが、50個もあろうブロックを全て1フレーム毎に衝突判定していたら、とてもではありませんがゲームになりませんでした。今の携帯電話のFlashLiteですと fpsを12に設定していたとしても、fps3~5くらいの遅さにまでに落ち込み そのガクガク感たるや玉子王子も青ざめるほどの速度です。
そこで この問題を解決すべく、ボールとブロックとの衝突判定高速化の方法を考えました。この方法はブロックの数に関係なく、ボール1つに対するブロック衝突判定は4回で済みます。
hitTestで全検索を行う方法よりも多少複雑で面倒になりますが、処理速度アップを行いたい方はお試し下さい。
(1)前知識 : ボールのブロック衝突時の反転処理
今回のボールのブロック衝突時の反転処理は次のような方法をとります。1フレーム内での処理です。
1.ボールX軸移動 2.衝突判定 : 衝突していたら X軸移動方向反転 3.ボールY軸移動 4.衝突反転 : 衝突していたら Y軸移動方向反転
単純に、X方向に移動していてブロックにぶつかったらX軸移動方向反転、Y方向に移動していてブロックにぶつかったらY軸移動方向反転、という考え方です。ブロックのこの部分にぶつかったからあちらの方向へ反転、といったことは考えません。
(2)ブロック配置時の命名規則
ブロック用ムービークリップ(以下MC)を配置数分 複製する際、複製された各ブロック用MCの名前自体に そのブロックがどの場所に位置しているかわかるような名前をつけます。例えば50個のブロックを配置する場合、一番最初に配置する左上側のブロックMC名を「blockMC0_0」その右隣のブロックMC名を「blockMC0_1」とします。「blockMC0~blockMC14」といった連番にしないところがポイントです。
(3)配置したブロックへのアクセス方法
横方向x番目、縦方向y番目へのブロックMCへは、以下のようにevalや連想配列からアクセスすることが可能になります。
eval( "blockMC" + x + "_" + y )
this[ "blockMC" + x + "_" + y ]
FlashLiteではこうなります。(文字列結合はaddを使用)
eval( "blockMC" add x add "_" add y )
その他 直接ブロック用MCに名前を設定せずに、Objectのプロパティに次々と設定していく方法がお勧めです。この場合は、blockMCの名前は何でもかまいません。
var blockList:Object = new Object();
blockList[ "block" + x + "_" + y ] = 複製したブロック用MCパス;
私が作成したPaintBLOCK崩しでは 各ブロック一つ一つを取り扱うためのBlockクラスを作成し、各BlockクラスオブジェクトをObjectのプロパティに設定しました。
var blockList:Object = new Object();
blockList[ "block" + x + "_" + y ] = Blockクラスオブジェクト;
(4)ボールとブロックの衝突判定
ボールが現在ブロック郡のどの場所に位置しているかを計算し、現在位置している場所にブロックMCが存在すればボールとブロックが衝突したと考え、ブロックMCを消去してボール反転を行う、という方法を採ります。
まず、ボールを四角形と考え、四隅の座標を求めます。
ボール(ballMC)の四隅の座標はMovieClip.getBoundsで取得できます。
var point:Object = ballMC.getBounds( _root ); //四隅 : ( x座標, y座標 ) // //左上 : ( point.xMin, point.yMin ) //左下 : ( point.xMin, point.yMax ) //右上 : ( point.xMax, point.yMin ) //右下 : ( point.xMax, point.yMax )
得られたボール各隅のx,y座標から、その座標がブロック郡のどの場所に位置しているか求めます。
var blockX:Number = Math.floor( ( x座標 - blockMC0_0._x )/ブロック横幅 ); var blockY:Number = Math.floor( ( y座標 - blockMC0_0._y )/ブロック縦幅 );
これにより、該当blockMCの検出が可能になります。
var mc:MovieClip = this[ "blockMC" + blockX + "_" + blockY ];
検出されたblockMCが存在する場合、ボールとブロックがぶつかったことを示しますので、blockMCを消去しボールを反転させましょう。
//ブロックが存在する場合 if( mc != undefined ){ //ブロック消去 mc.removeMovieclip(); //ボール反転処理 ~ }
まとめて、(1)のボールの処理の衝突判定の流れは以下のようになります。
1.X軸移動 2.ブロック衝突判定 右方向に進んだ場合 : 右上・右下隅を調査 左方向に進んだ場合 : 左上・左下隅を調査 3.Y軸移動 4.ブロック衝突判定 下方向に進んだ場合 : 左下・右下隅を調査 上方向に進んだ場合 : 左上・右上隅を調査
例としまして ボールがX軸右方向に進んだ場合、右上・右下隅の衝突判定を行い 左上・左下隅を調べる必要はありませんので、二回のブロックとの衝突判定が行われます。同じくY軸をどちらかに進んだ時の二回の衝突判定を加え、合計四回のブロック衝突判定で済むと言うわけです。Oh my dango!
(5)注意点
ボールの移動速度がブロックの高さや幅を越えてしまう場合、衝突判定時ボールがブロックをすりぬけてしまう恐れがあるので 対処方法を考える必要が出てきます。なので、ボールの移動スピードがブロックの高さや幅を超えないよう調整してください。
以上で解説終了です。
Flashではeval関数や連想配列が使用できるところがメリットですね。
今回の考え方は、ブロック崩しのみならず 他のゲーム製作へも有効に適用できるのではないかと思います。(画面全体がパネルで区切られているものと考える)
パネルで区切られていなくとも特定座標に位置するオブジェクトを取得する、という考え方は色々応用が利きそうです。