AIゲーム制作ラボ
Unity×AI4分で読めます

当たり判定を速くする前に測る(総当たりを捨てて遅くなった話)

弾と敵の当たり判定を、総当たりからマス目(空間分割)に変えたら遅くなりました。切り出して測ると60倍速いのに、ゲームの中では負ける。理由と、結果を変えずに速さだけ選べるようにした形を、実測つきで残します。

Libra

公開 2026年9月19日

この記事の要点4 points

  1. 切り出して測ると、マス目は総当たりの16倍速い(300発 × 敵60体 × 200フレームで 100ms6ms)。
  2. ところがゲームの中では負けた30秒ぶんの模擬プレイで 1ms7ms)。 敵が消えるたびに作り直す手間が乗るため。
  3. 実際に出ているのは数体〜十数体。 その規模では総当たりで足りる
  4. 🔴 両方を残し、結果が同じであることをテストで固定してから、数で切り替えるようにした。
目次5
検証した環境最終確認 2026年9月19日
  • Windows 11 / RTX 2080 Ti
  • Unity 6.6(6000.6.0f1)
  • Claude Code(Opus 5 / 思考レベル 高)

書いてあるのは、この環境で実際に試した結果です。ツールの仕様や料金は変わるので、重要な判断の前には公式の情報も確認してください。

弾が最大300発、敵が最大60体。総当たりだと毎フレーム18,000回の距離くらべになります。これは減らしたい。

素直な手は「画面をマス目に区切って、弾の近くのマスにいる敵だけを見る」です。書いて、測って、遅くなりました

16倍

切り出すと速い

100ms → 6ms

7倍

ゲームの中では遅い

1ms → 7ms

1,200

切り替える境目

弾 × 敵

0件

結果の違い

5つの種で突き合わせ

まず、切り出して測った

マス目だけを取り出して、敵の数を変えながら測ります(弾300発・200フレームぶん)。

Unity のコンソール(エディターで実行)OK
敵数	弾数	総当たり(ms)	マス目(ms)
60	300	100	6
150	300	210	9
400	300	382	17
1000	300	420	28
3000	300	501	62

数字だけ見れば、マス目の勝ちです。敵60体でも16倍、3000体なら8倍。ここで満足して差し替えると、次の落とし穴に落ちます。

ゲームの中では負けた

同じ実装で、実際の1プレイ30秒ぶん・同じ種・同じ入力)を回して測ると、逆転しました。

マス目(7ms)

  • 敵が消えるたびにマス目を作り直す
  • 作り直しは「全部のマスを空にする」ところから
  • 敵が数体しかいなくても、その手間は変わらない

総当たり(1ms)

  • 実際に出ているのは数体〜十数体
  • 弾も20発前後。かけ算しても数百回
  • 下ごしらえが要らない

理由ははっきりしています。切り出した測定では、敵が消えません。実際のゲームでは、弾が当たるたびに敵が消え、並びが変わり、マス目は作り直しになります。

速くするための下ごしらえが、いちばん多い場面で毎回捨てられていた。

直し方:捨てずに、数で選ぶ

マス目が悪いわけではありません。数が増えれば逆転することは、切り出した測定で分かっています。そこで両方を残し、その場の数で選ぶようにしました。

bool useGrid = Mode == HitMode.Grid ||
               (Mode == HitMode.Auto && _shotCount * _enemyCount > GridThresholdPairs);

境目の 1,200 は実測から決めた数字です(この機械で、総当たり1回ぶんの手間とマス目の下ごしらえが釣り合うあたり)。

片方を消さない

速くするときに古いほうを消してしまうと、比べる相手がいなくなります。残しておけば、数が変わったとき・機械が変わったときに測り直せます。残すコストは、このゲームでは数十行でした。

結果が変わっていないことを、テストで固定する

速さのために実装を変えたときに怖いのは、答えが変わることです。当たり判定は「どの弾がどの敵に当たるか」なので、少し違うだけでスコアが変わります。

そこで、同じ種・同じ入力で1プレイぶん回し、結果を文字にして突き合わせています。

return $"点{battle.Score} 撃破{battle.Kills} 敵{battle.EnemyCount} 弾{battle.ShotCount} 残機{battle.Lives} 時間{battle.Time:0.000}";
[Test]
public void 総当たりとマス目で結果が同じ()
{
    for (int seed = 1; seed <= 5; seed++)
    {
        Assert.That(Play(HitMode.Grid, seed, 40f), Is.EqualTo(Play(HitMode.BruteForce, seed, 40f)), $"種 {seed} で結果が違う");
    }
}
01

マス目にすると、見つける順番が変わる

症状
同じ場面なのに、当たる敵が入れ替わって点がずれる
原因
マス目は「近くのマスにいるもの」をまとめて返すので、総当たりの「添字の小さい順」とは違う順で見つかる
対処
返ってきた候補のうちいちばん小さい添字を採る。これで総当たりと同じ答えになる
02

下ごしらえの回数を数えていない

症状
1回あたりは速いのに、全体では遅い
原因
対象が消えるたびに作り直していると、1フレームに何度も下ごしらえが走る
対処
作り直しが必要になった回数も測る。減らせないなら、その規模では使わない

まとめ

  • 切り出した測定は上限の目安。実際の場面では前提(消える・数が少ない)が違う
  • この規模(敵が数体〜十数体)では、総当たりで足りた
  • 速くした実装は捨てずに残し、数で切り替える
  • 実装を変えたら、結果が同じであることをテストで固定する

FAQ

よくある質問

当たり判定はマス目(空間分割)にすべきですか?

数が多いときだけです。切り出して測るとマス目は大きく速いのですが、実際のゲームでは「対象が消えるたびに作り直す」手間が乗ります。数十個しか出ない場面では、総当たりのほうが速いことがあります。

速くしたつもりが遅くなったのは、どうすれば分かりますか?

両方の実装を残して、同じ場面を同じ回数だけ回して測ります。片方だけ書いて差し替えると、比べる相手がいなくなります。

実装を差し替えるとき、結果が変わっていないかはどう確かめますか?

同じ種・同じ入力で1プレイぶん回し、点・撃破数・残機・時間を突き合わせます。文字にして比較すると、1か所でも違えばテストが落ちます。

どれくらいの数から切り替えますか?

このゲームでは「弾 × 敵」が 1,200 を超えたらマス目にしました。数字は実測から決めています(機械が変われば変わります)。

この記事は役に立ちましたか?

NEXT