これまでにゲームを作ってきましたが、オブジェクトの数が増えると当たり判定に時間がかかるのでオブジェクトの数をあまり大きくすることはありませんでした。しかし二分探索法を使えば高速化は可能です。思った以上の差となりました。

HTML部分

まずHTML部分を示します。

style.css

グローバル変数と定数

グローバル変数と定数を示します。

Bulletクラスの定義

弾丸を操作できるようにするためにBulletクラスを定義します。コンストラクタの引数は弾丸のIDと初期座標、移動速度です。当たり判定は異なるIDの弾丸同士のときだけおこないます。

ページが読み込まれたときの処理

更新処理

弾丸を移動させ、当たり判定をしたあと描画処理をおこないます。また更新回数が24の倍数であれば新しい弾丸を生成して全方向に飛ばします。

弾丸の生成

弾丸を生成する処理を示します。10方向に初速を与えて放射状に飛ぶようにしています。

描画処理

描画処理を示します。canvas全体を黒で塗りつぶし弾丸を描画します。また弾丸の総数を表示させます。

当たり判定

本日のメインコンテンツの当たり判定です。

愚直な総当たり法

最初に愚直なやり方を示します。これは a < b < bullets.length を満たすすべての a と b について総当たりをしています。弾丸数が1000個だと499,500回、2000個だと1,999,000回の比較が必要です。当たり判定のありなしを切り替えると極端に動作速度が落ちることに気が付きます。

二分探索法を用いた高速化

高速化するにはどうすればよいでしょうか?

まず弾丸のX座標で配列をソートします。ソートすることでX座標が離れすぎているものを判定対象から外すことができます。弾丸Aと接しているのであれば、少なくともX座標が(弾丸AのX座標±弾丸の半径の範囲内)であるという条件を満たしているはずです。

そこで弾丸オブジェクトの配列のなかからそれを満たす区間を探します。そしてこの区間のなかだけを調べます。そうすることで無駄な処理をする必要がなくなるので高速化を実現することができるのです。

配列のなかから値がtarget以上である最小のindexを返す関数を示します。

配列を昇順でソートしているので添字の最小値よりも1小さいのであればその要素はtargetよりも明らかに小さいです。また添字の最大値よりも1大きいのであればその要素はtargetよりも明らかに大きいとみなします(そもそも配列の範囲外ではあるのだが・・・)。

そこで最初は変数 ng を -1、ok を arr.length で初期化しておきます。そしてこの中間の整数 mid を取ります。そして arr[mid] と target を比較します。この大小関係によってokとngの値を変更します。この処理を ok – ng == 1 になるまで繰り返せば配列内の値がtarget以上である部分とそうでない部分の境界線がわかります。このときのokの値が配列内の要素の値がtarget以上である最小のindexの値です。

改良された当たり判定の処理を示します。

bulletsをX座標でソートし、そのX座標を配列に変換したものを上記のlowerBound関数に渡します。この処理を target = bulletA.X – bulletRadius と target = bulletA.X + bulletRadius + 1 でおこなうことでbulletAとの当たり判定が必要な配列の区間がわかります。ラジオボタンで当たり判定のアルゴリズムを切り替えてみると両者の差は歴然といえます。