【ゲーム制作】DirectXで円形とAABBの当たり判定を組み合わせる方法|初心者向けに解説

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

プレイヤーは円で判定したいけれど、壁や箱は四角形で管理している。そんなとき、円とAABBが当たっているかを、どう調べればよいのでしょうか。オブジェクトごとに形が違うゲームでは、よく出てくる組み合わせです。

この記事では、円とAABBの判定の考え方、Clampを使った最近接点の求め方、DirectXでの実装、ズレやすいポイントを解説します。

円形とAABBの当たり判定とは?

これは、円の中心と、矩形のいちばん近い点との距離を使って判定する方法です。

AABB同士なら「左右上下で離れているか」を見ればよかったですが、円と矩形ではそう単純にはいきません。そこで、矩形の中で円の中心に最も近い座標を求めて、その点までの距離が半径以内かどうかを調べます。

AABB自体の基本を先に整理したい方は、DirectXでAABBの当たり判定をする方法の記事を先に読むとかなり理解しやすいです。

なぜこの判定が必要?

実際のゲームでは、形を全部そろえられるとは限りません。

  • プレイヤーは円形で取りたい
  • 壁や床はAABBで管理したい
  • アイテムや障害物は四角で置いている

こういう時に、円とAABBの判定がないと処理を分けにくくなります。特に2Dアクションや見下ろし型ゲームでは、かなり出番が多いです。

判定の考え方

やることは次の3ステップです。

  • 円の中心座標を用意する
  • その中心に一番近い矩形内の点を求める
  • その点と円の中心の距離が半径以下ならヒット

この「矩形内の一番近い点」を求める時によく使うのが Clamp です。Clamp は、値を最小値〜最大値の範囲に収める処理です。

基本の実装方法

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

▼main.cpp

#include <iostream>

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

struct AABB {
    float x;
    float y;
    float width;
    float height;
};

float Clamp(float value, float min, float max) {
    if (value < min) return min;
    if (value > max) return max;
    return value;
}

bool IsHit(const Circle& circle, const AABB& box) {
    float closestX = Clamp(circle.x, box.x, box.x + box.width);
    float closestY = Clamp(circle.y, box.y, box.y + box.height);

    float dx = circle.x - closestX;
    float dy = circle.y - closestY;

    return dx * dx + dy * dy <= circle.radius * circle.radius;
}

int main() {
    Circle player = {170.0f, 130.0f, 25.0f};
    AABB wall = {100.0f, 100.0f, 50.0f, 60.0f};

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

    return 0;
}

実行結果

当たっています

このコードでは、矩形の中で円の中心に最も近い点を求めて、その点との距離を使って判定しています。なお、距離は平方根を使わず、2乗同士で比較しています。

実践例

たとえば、プレイヤーを円形、宝箱をAABBで持っているなら、こんなふうに使えます。

▼main.cpp

#include <iostream>

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

struct AABB {
    float x;
    float y;
    float width;
    float height;
};

float Clamp(float value, float min, float max) {
    if (value < min) return min;
    if (value > max) return max;
    return value;
}

bool IsHit(const Circle& circle, const AABB& box) {
    float closestX = Clamp(circle.x, box.x, box.x + box.width);
    float closestY = Clamp(circle.y, box.y, box.y + box.height);

    float dx = circle.x - closestX;
    float dy = circle.y - closestY;

    return dx * dx + dy * dy <= circle.radius * circle.radius;
}

int main() {
    Circle player = {220.0f, 140.0f, 20.0f};
    AABB itemBox = {200.0f, 120.0f, 40.0f, 40.0f};

    if (IsHit(player, itemBox)) {
        std::cout << "アイテムを取得しました\n";
    } else {
        std::cout << "まだ届いていません\n";
    }

    return 0;
}

実行結果

アイテムを取得しました

このように、丸いプレイヤー判定と四角いオブジェクト判定を自然に組み合わせられます。

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

私も自主制作の2Dアクションで、プレイヤーの判定を円、壁をAABBで管理していたことがあります。

最初は「円の中心とAABBの左上の距離」を見てしまっていて、壁の角に近づいた時だけおかしな判定になっていました。正面からぶつかると当たるのに、斜めから近づくとすり抜けたり、逆に少し離れているのに当たったりしてかなり混乱しました。

原因は、矩形の最も近い点を取らずに、基準点を雑に比較していたことでした。そこでClampを使って「矩形内の一番近い点」を求める形に直したところ、判定がかなり安定しました。

この時に実感したのは、円と四角は、中心同士ではなく“最も近い点”で考えるのが大事ということでした。

よくある失敗例と対処法

  • 矩形の左上座標と比較してしまう
    円の中心に最も近い矩形内の点を求める必要があります。左上だけで判定するとズレやすいです。
  • 円の座標が中心なのか左上なのか曖昧
    円形判定は基本的に中心座標が前提です。描画基準と判定基準をそろえましょう。
  • sqrtを使って毎回距離を出す
    判定だけなら平方根は不要です。2乗同士で比較した方が軽くてシンプルです。

AABB単体との違い

AABB同士の判定は、左右上下で離れているかを見るだけなのでかなりシンプルです。一方、円とAABBでは、矩形内の最近接点を求める処理が増えます。

そのぶん少し考え方は増えますが、プレイヤーや弾のような丸っぽい見た目に対しては、AABBだけで取るより自然な判定になります。

注意点

  • 円は中心座標、AABBは左上座標で管理することが多いので、基準点を混同しないことが大切です
  • 壁の押し戻し処理までやる場合は、当たり判定だけでなく、どの方向から当たったかも考える必要があります
  • まずは「当たっているかどうか」だけを安定させて、その後に押し戻しやすり抜け対策を足すのがおすすめです

要点の整理

  • 円形とAABBの当たり判定は、矩形内の最近接点を使って判定する
  • Clampを使うと最近接点を求めやすい
  • 距離比較は sqrtなしで2乗同士 でOK
  • 円と四角を組み合わせる時は、基準点の違いに注意する
  • AABB単体より少し複雑ですが、丸いオブジェクトにはかなり相性がいい

円とAABBの判定は、Clampで四角形上の最近接点を求めて、その点と円の中心との距離を、半径と比べます。距離の比較は2乗どうしで行えば、sqrtは要りません。円と四角では、基準点が違うので、そこだけは注意してください。

関連記事