【ゲーム制作】DirectXで円形の当たり判定をする方法|初心者向けにわかりやすく解説

サムネイル画像 ゲーム制作

今回は、DirectXでの「円形の当たり判定をする方法」について解説していきます。

「弾と敵の当たり判定を円で取りたい」
「AABBより自然な判定にしたい」
「距離を使うって聞いたけど、どう計算すればいいの?」

こんな疑問はありませんか?

ゲーム制作では、見た目が丸いオブジェクトに四角形の判定を使うと、どうしても違和感が出やすいです。特に弾・アイテム・プレイヤー周辺の判定では、円形の当たり判定の方が直感に近いことがよくあります。

この記事を読み終えると、あなたは円形当たり判定の考え方・実装方法・AABBとの使い分けを理解できると思いますので、ぜひ最後まで読んでいただけると嬉しいです。

円形の当たり判定とは?

円形の当たり判定とは、2つの円の中心同士の距離を使って、ぶつかっているかを判定する方法です。

考え方はとてもシンプルで、中心同士の距離が、2つの半径の合計以下なら当たっているというものです。

たとえば、半径20の弾と半径30の敵があるなら、中心同士の距離が50以下ならヒットです。

なぜ円形判定が必要?

円形判定が役立つのは、見た目が丸いオブジェクトに相性がいいからです。

  • コインやアイテム
  • 球体っぽい敵
  • プレイヤーの接触判定

逆に、箱や壁のような四角いものにはAABBの方が向いています。矩形の当たり判定から整理したい方は、DirectXでAABBの当たり判定をする方法の記事も先に読むと理解しやすいです。

円形当たり判定の基本式

基本式は次の考え方です。

(中心同士の距離) <= (半径A + 半径B)

ただし、実装では毎回 sqrt を使って距離を求めるより、2乗のまま比較した方が軽くて便利です。

つまり、次のように判定します。

dx * dx + dy * dy <= (r1 + r2) * (r1 + r2)

これなら平方根を使わずに済むので、ゲームループの中でも扱いやすいです。

基本の実装方法

まずは一番シンプルな実装を見てみましょう。

▼main.cpp

#include <iostream>

struct Circle {
    float x;
    float y;
    float radius;
};

bool IsHit(const Circle& a, const Circle& b) {
    float dx = a.x - b.x;
    float dy = a.y - b.y;
    float distanceSq = dx * dx + dy * dy;
    float radiusSum = a.radius + b.radius;

    return distanceSq <= radiusSum * radiusSum;
}

int main() {
    Circle player = {100.0f, 100.0f, 20.0f};
    Circle enemy  = {130.0f, 110.0f, 20.0f};

    if (IsHit(player, enemy)) {
        std::cout << "当たっています\n";
    } else {
        std::cout << "当たっていません\n";
    }

    return 0;
}

実行結果

当たっています

このコードでは、中心座標 xy、そして半径 radius を持つ円同士で判定しています。

実践例

実際のゲームでは、弾と敵、プレイヤーとアイテムなどでよく使います。

▼main.cpp

#include <iostream>

struct Circle {
    float x;
    float y;
    float radius;
};

bool IsHit(const Circle& a, const Circle& b) {
    float dx = a.x - b.x;
    float dy = a.y - b.y;
    float distanceSq = dx * dx + dy * dy;
    float radiusSum = a.radius + b.radius;

    return distanceSq <= radiusSum * radiusSum;
}

int main() {
    Circle bullet = {320.0f, 180.0f, 8.0f};
    Circle enemy  = {330.0f, 185.0f, 24.0f};

    if (IsHit(bullet, enemy)) {
        std::cout << "弾が敵にヒットしました\n";
    } else {
        std::cout << "まだ当たっていません\n";
    }

    return 0;
}

実行結果

弾が敵にヒットしました

このように、見た目が丸に近いオブジェクトなら、AABBより自然な感触の判定になりやすいです。

AABBとの違いと使い分け

円形判定とAABBは、どちらが上というより使う場面が違うというイメージです。

判定方法向いているもの特徴
AABB壁、床、箱、四角い敵軽くて扱いやすい
円形判定弾、球体の敵、アイテム丸い見た目に自然

四角いオブジェクトを円で判定するとスカスカになりやすく、逆に丸いオブジェクトをAABBで判定すると角で引っかかって見えることがあります。

【重要】私が実際に円形判定で困った体験談

私も自主制作のシューティングゲームで、弾と敵の当たり判定を最初はAABBで取っていました。

ただ、敵が丸っぽい見た目だったので、見た目では当たっていないのに角だけAABBに触れてヒット扱いになることがあり、プレイ感がかなり悪かったです。

そこで、弾と敵だけ円形判定に変えたところ、違和感がかなり減りました。逆に、壁や地形はAABBのままにした方が扱いやすかったので、全部を円にするのではなく、オブジェクトごとに判定方法を使い分けるのが大事だと実感しました。

また、一度だけ半径を直径のつもりで入れてしまい、判定が異常に大きくなったこともあります。以降は変数名を radius に統一して、意味をはっきりさせるようにしています。

円形判定使用時のよくある失敗例と対処法

  • 半径と直径を混同する
    変数名を radius に統一して、値の意味を曖昧にしないようにしましょう。
  • sqrtを毎回使ってしまう
    判定だけなら平方根は不要です。2乗同士で比較した方が軽いです。
  • 中心座標ではなく左上座標で計算してしまう
    円形判定は中心座標が前提です。描画基準と判定基準を揃えることが大切です。

クラスや構造体でまとめると使いやすい

オブジェクトが増えてきたら、円形判定も構造体やクラスにまとめておくと管理しやすいです。

▼Circle.h

#pragma once

struct Circle {
    float x;
    float y;
    float radius;
};

▼Collision.h

#pragma once
#include "Circle.h"

bool IsHit(const Circle& a, const Circle& b);

▼Collision.cpp

#include "Collision.h"

bool IsHit(const Circle& a, const Circle& b) {
    float dx = a.x - b.x;
    float dy = a.y - b.y;
    float distanceSq = dx * dx + dy * dy;
    float radiusSum = a.radius + b.radius;

    return distanceSq <= radiusSum * radiusSum;
}

このようにしておくと、弾、敵、アイテムなど、いろいろな場所から同じ判定関数を再利用できます。

クラスや構造体で整理する考え方は、C++のクラスの記事もあわせて読むと理解しやすいです。

注意点

  • 円形判定は丸いオブジェクトに向いていますが、壁や地形には向かないことがあります
  • 見た目どおりに厳密に取りたい場合は、円だけでなくAABBや他の判定と組み合わせることもあります
  • まずは軽くて分かりやすい判定から始めるのがおすすめです

まとめ

  • 円形の当たり判定は、中心同士の距離で判定する
  • 実装では sqrt を使わず、2乗同士で比較すると軽い
  • 弾やアイテムなど、丸い見た目のオブジェクトに向いている
  • AABBと円形判定は、オブジェクトごとに使い分けるのが実践的
  • 中心座標・半径の意味を揃えることがズレ防止のポイント

円形当たり判定は、AABBと並んでかなり使いやすい基本技術です。

まずはシンプルな式でしっかり動かして、必要に応じて他の判定方法と組み合わせていくのがおすすめです。

ここまで読んでくださり、ありがとうございました。

関連記事