当たり判定の仕組み:四角同士が重なるとはどういうことか
遊びをつくる執筆にClaudeを使用しています。

画面に青い四角と灰色の四角があり、青い方を矢印キーで動かす場面を考えてみます。2つの四角の端が触れた瞬間に色を変えたいとき、プログラムは何を見ればよいのでしょうか。画像の重なりを一画素ずつ調べる必要はありません。比べるのは、四角の左端・右端・上端・下端という4種類の座標だけです。 この記事の答えは、先に書いておきます。2つの四角が重なっているとは、横方向の区間と縦方向の区間が、どちらも重なっていることです。四角を2本の数直線に分けて考えると、2次元の図形の問題が「2本の線分が重なるか」という問題2つに変わります。以下では、その理由を数直線と図で確かめ、短いコードで動かし、つまずきやすい点を整理します。
この記事の目次
1. 四角を横の数直線に落とす
四角は、左上の座標 x, y と、幅 w、高さ h の4つの数で表せます。ブラウザの画面では、左上が原点で、yは下に向かって増えるのが普通です。
まず横方向だけを見ます。四角Aが x=2 から x=6 まで、四角Bが x=5 から x=9 までを占めているとします。これを数直線にすると次のようになります。
0 1 2 3 4 5 6 7 8 9
|---------------|
A:2 から 6
|---------------|
B:5 から 9AとBは 5 から 6 の範囲で重なっています。逆に、重ならない場合は2通りしかありません。
- Aの右端がBの左端以下の場合(Aが完全に左にある)
- Bの右端がAの左端以下の場合(Bが完全に左にある)
「重なる」は「この2通りのどちらでもない」と言い換えられます。そこで、次の2つの不等式が同時に成り立てば重なっていることになります。
- Aの左端 < Bの右端(a.x < b.x + b.w)
- Bの左端 < Aの右端(b.x < a.x + a.w)
上の例では 2 < 9 と 5 < 6 がどちらも成り立つので、横は重なっています。「どちらの端がどちらの端より内側か」を2回比べるだけで、横方向の判定は終わりです。
2. 縦と横の両方で重なったときだけ「当たり」
縦方向も同じ考え方で、上端と下端を比べます。ここで大事なのは、横が重なっていても、縦が重ならなければ当たっていない、という点です。
次の例で確かめます。
- 四角A:x が 2〜6、y が 1〜4
- 四角B:x が 5〜9、y が 3〜6
数字は「マスの境目の位置」を表し、マスの番号ではありません。A は x=2 の線から x=6 の線までの幅です。これをマス目に描くと次の図になります(A はAだけ、B はBだけ、X は両方に含まれるマスです)。
x: 0123456789
y=0 ..........
y=1 ..AAAA....
y=2 ..AAAA....
y=3 ..AAAXBBB.
y=4 .....BBBB.
y=5 .....BBBB.
y=6 ..........重なっているのは X の1マスだけです。横は 5〜6、縦は 3〜4 で、それぞれの数直線の重なりが交差した場所にあたります。重なった部分の大きさも、横の重なりの長さと縦の重なりの長さの積として求められます。
別の四角Cを考えます。Cは x が 5〜9、y が 5〜7 とします。Aと横の区間(2〜6 と 5〜9)は重なります。しかし縦は、Aが 1〜4、Cが 5〜7 で、間に隙間があります。図に描くと、Cは X のあるマスよりずっと下にあります。横だけ重なっても当たりにならない理由が、縦の数直線を並べると見えてきます。
まとめると、判定は次の4つの比較をすべて満たすかどうかです。
- a.x < b.x + b.w
- b.x < a.x + a.w
- a.y < b.y + b.h
- b.y < a.y + a.h
3. 動かして確かめる最小のコード
上の4つの比較を、そのまま関数にしたものを用意します。次のHTMLは1ファイルで動き、外部ライブラリもサーバーも使いません。保存して開き、矢印キーで青い四角を動かします。横と縦の判定結果が別々に表示されるので、数直線の考え方と画面を見比べられます。
<canvas id="c" width="320" height="200"></canvas>
<p id="t"></p>
<script>
const g = document.getElementById("c").getContext("2d");
const a = { x: 40, y: 40, w: 60, h: 40 };
const b = { x: 160, y: 80, w: 80, h: 60 };
const xHit = (p, q) => p.x < q.x + q.w && q.x < p.x + p.w;
const yHit = (p, q) => p.y < q.y + q.h && q.y < p.y + p.h;
function draw() {
g.clearRect(0, 0, 320, 200);
g.fillStyle = "#555";
g.fillRect(b.x, b.y, b.w, b.h);
g.fillStyle = xHit(a, b) && yHit(a, b) ? "#d33" : "#39c";
g.fillRect(a.x, a.y, a.w, a.h);
document.getElementById("t").textContent =
"x: " + xHit(a, b) + " / y: " + yHit(a, b) +
" / a.x=" + a.x + " a.y=" + a.y;
}
addEventListener("keydown", (e) => {
const d = { ArrowLeft: [-10, 0], ArrowRight: [10, 0],
ArrowUp: [0, -10], ArrowDown: [0, 10] }[e.key];
if (!d) return;
e.preventDefault();
a.x += d[0]; a.y += d[1];
draw();
});
draw();
</script>最初の位置では、青い四角の下端(y=80)と灰色の四角の上端(y=80)がちょうど同じ値です。そのため yHit は false になり、色も変わらないはずです。次の手順で、予想と画面が一致するかを確認してください。
- 下キーを1回押します(a.y が 50 になり、yHit が true になるはずです)。
- 右キーを6回押します(a.x が 100 になります。右端が 160 で、灰色の左端 160 と同じ値です)。
- 右キーをもう1回押します(a.x が 110 になります)。
条件式から考えると、手順2では xHit が false のままで、手順3で true になり、四角が赤くなるはずです。表示された値が予想と違えば、座標の持ち方か比較の向きを見直す手がかりになります。
4. 境目の扱いと、つまずきやすい点
数直線で考えると、判断が必要になる場所がはっきりします。
端がぴったり触れたら当たりか
手順2の状態では、青の右端と灰色の左端が同じ値です。この状態を「当たり」にするかは、設計の判断です。上のコードは < を使うので、触れただけでは当たりになりません。<= に変えると、触れた時点で当たりになります。どちらが正しいということはなく、「触れたら取れる」「重なったら取れる」のどちらをゲームの手触りにしたいかで決めます。
よくある誤り
- 座標の基準が違う:画像の中心を x, y として持っている場合、左端は x - w/2 です。左上基準と混ぜると、判定がずれます。
- yの向きを逆に考える:画面では下ほどyが大きくなります。「上端」が小さい値、「下端」が大きい値です。
- 見た目と判定の大きさが違う:絵の余白まで含めて判定すると、触れていないのに当たったように感じます。判定用の四角を絵より少し小さくして、感触を調整するのも一般的な工夫です。
- 速く動くとすり抜ける:1回の更新で動く距離が相手の幅より大きいと、更新前と更新後のどちらの位置でも重なっていないことがあります。これは数直線で考えると、移動の前後で区間が飛び越えた状態です。確かめるには、上のコードの移動量 10 を相手の幅(80)より大きくして、薄い四角を通過させてみてください。
5. 公式作品から読み取れること
アソバースの公式作品にも、「触れたかどうか」を判断する場面があります。確認済みの範囲で言えるのは、次の点です。
- 「紙ひこうき郵便」では、風船にぶつかると紙が傷み、3回で終了します。一方で、手紙を集め、最後の6秒で赤い郵便受けへ向かいます。つまり「避けたい接触」と「取りたい接触」が1つの画面にあります。
- 「小さな軌道」では、引力で曲がる軌道でゲートを目指します。
設計から読み取れるのは、どちらの作品でも「何が何に触れたか」を決める判定が、ゲームの得点や終了条件に直結しているということです。ただし、これらの作品の判定が四角なのか円なのか、判定の大きさが見た目と一致しているのかは、確認済みの情報にはありません。この部分は作品を開いて確かめてください。
確かめ方の例を挙げます。「紙ひこうき郵便」で、風船にかすったように見える場面を何度か作り、傷む回数が増えたかどうかを自分で数えます。見た目では離れているのに傷んだなら、判定が見た目より大きい可能性があります。逆に、重なって見えるのに傷まなければ、小さい可能性があります。これは結果を保証するものではなく、自分で調べるための見方です。
次の行動
上のコードで、< を <= に変えて、端が触れた状態の色がどうなるかを確かめてください。そのうえで、自分のゲームで「触れたら当たり」と「重なったら当たり」のどちらが合うかを決めます。動くHTMLができたら、1ファイルのHTMLとしてアソバースに投稿できます。ZIPで送る場合は、直下に index.html を置きます。公開にはメールアドレスの確認が必要です。

