【学習】Collider と Rigidbody で部位別当たり判定 ― 矢から逃げる猫(アフター編)
これまでの学習では、
- 第1回(ビフォー編) では、円同士の距離(
d < r1 + r2)だけで当たり判定を書き、最小構成で遊べるようにしました。
しかし、円の近似しかできない・右腕に当たったか左腕かのような 部位が表現しにくい・対象が増えると 矢のスクリプトが太る といった限界もありました。
Unity 2D の物理(Collider2D / Rigidbody2D) と 子オブジェクト に当たり判定を分けると、形を Inspector で調整しつつ、イベント駆動で部位ごとに反応を書けます。
今回は第1回の前提を書き換え、OnTriggerEnter2D ベースの構成に作り替えます。
シリーズ
- 第1回: 座標計算による当たり判定 ― 最小構成(ビフォー編)
- 第2回(本記事): Collider / Rigidbody / 子オブジェクトの部位別当たり判定
今日のゴール
- 矢の落下と当たり判定を 物理エンジンとコライダ に寄せる(座標手計算の主役を降ろす)
- 猫の 右腕・左腕 にそれぞれ
Collider2Dを持たせ、部位ごとに衝突イベントを受け取る - コードを 毎フレームの距離判定 から、衝突時のコールバック中心に切り替える
座標計算版の限界(おさらい)
前回の当たり判定は、概ね次の 7 行でした。
Vector2 p1 = transform.position;
Vector2 p2 = this.player.transform.position;
Vector2 dir = p1 - p2;
float d = dir.magnitude;
float r1 = 0.5f;
float r2 = 1.0f;
if (d < r1 + r2) { /* 当たり */ }- 猫を 1 つの円でしか表現しにくい
- 右手 / 左手の区別がそのままではできない
- 敵・障害物が増えると
ArrowController側の列挙が増える - 落下を
transform.Translate前提にした場合、物理挙動は自前
これらの「置き場所の問題」と「形の問題」を、2D 物理+子オブジェクトで整理します。
2D 物理の用語(最低限)
| 用語 | 役割 |
|---|---|
| Collider2D | 当たり判定の形(Box, Circle, Polygon など)。形だけ。 |
| Rigidbody2D | このオブジェクトを物理シミュレーションに乗せる。重力・力・衝突応答の入口。 |
| Is Trigger | OFF … 衝突して押し合う/ON … 通り抜けつつ、当たったイベントを取る。 |
| OnCollisionEnter2D / OnTriggerEnter2D | 衝突が起きたとき Unity が呼ぶコールバック。Trigger が絡めば主に OnTriggerEnter2D。 |
中級者向けメモ
OnTriggerEnter2Dを使う一般的な条件のひとつとして、ペアのどちらかに Rigidbody2D がある必要があります。今回は矢に Rigidbody2D を付けて満たします。
全体像(ビフォーからの差分)
player_0 ← PlayerController.cs(変更なし)
├─ RightArm ★新規: BoxCollider2D + RightArmCollisionDetector.cs
└─ LeftArm ★新規: BoxCollider2D + LeftArmCollisionDetector.cs
arrowPrefab
├─ SpriteRenderer (既存)
├─ ArrowController.cs ★変更: 当たり判定と DecreaseHp・Destroy(衝突時)を削る
├─ BoxCollider2D ★新規: IsTrigger = true
└─ Rigidbody2D ★新規: Kinematic または Dynamic猫の本体ではなく、腕という子 GameObject に役割を分けるのが、この回の設計の要です。腕側は 非 Trigger、矢側 Trigger + Rigidbody2D という定番の組み合わせで、OnTriggerEnter2D を腕のスクリプトで受け取ります。
ステップ 1: 矢 Prefab に Collider2D と Rigidbody2D
arrowPrefab に次を Add Component します。
- BoxCollider2D
- Size: スプライトに合わせる(例:
0.65 × 1.03) - Is Trigger: ON
- Size: スプライトに合わせる(例:
- Rigidbody2D
- 後述のとおり
DynamicかKinematic
- 後述のとおり
Is Trigger を ON にする理由
矢は「当たったら消える」用途で、猫を物理的に押し退けたいわけではない、という前提なら、Trigger+OnTriggerEnter2D が扱いやすいです。
Rigidbody2D の Body Type
| Body Type | 重力 | 他から押される | 用途のイメージ |
|---|---|---|---|
| Dynamic | ある | される | 重力で落とす・物理で動かす |
| Kinematic | なし | 基本されない | スクリプトで主に動かしつつ、Collider イベントを取る |
| Static | なし | されない | 動かない地形など |
学習のしやすさのため、本稿の流れは Kinematic + ビフォー同様の transform.Translate 落下を推奨します(移行が最小)。Dynamic にして gravityScale だけで落とす方法も併記します。
ステップ 2: 右腕・左腕の子オブジェクト
player_0 の子として 空の GameObject を2つ作り、名前を RightArm / LeftArm にします。それぞれ BoxCollider2D を付け、Offset / Size を調整します(例)。
| オブジェクト | Offset (X, Y) | Size (W, H) | Is Trigger |
|---|---|---|---|
| RightArm | (3, 0.5) | (4, 1) | OFF |
| LeftArm | (-3, 0.5) | (4, 1) | OFF |
Offset は親 player_0 のローカル座標です。親が動けば子も一緒に動きます。

なぜ「腕」に分けるのか
1 つの GameObject に BoxCollider2D を複数足すことはできますが、どの Collider かを OnTriggerEnter2D の引数で見分けて分岐させる手間が出がちです。子に分けておけば、腕に付けたスクリプトの OnTriggerEnter2D が呼ばれた=その部位に当たった、と 1:1 に対応づけられます。これが「複数の Collider を扱う」本稿の実体です。
ステップ 3: 腕ごとの検出スクリプト(まずはログ)
RightArm / LeftArm にそれぞれ次を付け、再生で矢が当たると Console に出るか確認します。
// Assets/Scripts/RightArmCollisionDetector.cs
using UnityEngine;
public class RightArmCollisionDetector : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("右手に当たった");
}
}// Assets/Scripts/LeftArmCollisionDetector.cs
using UnityEngine;
public class LeftArmCollisionDetector : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("左手に当たった");
}
}OnTriggerEnter2D が呼ばれる条件(おさらい)
今回の「矢 vs 右腕」では次のとおりです。
- 2 つの Collider2D が重なった
- そのペアのどちらかに Rigidbody2D がある(矢が Kinematic の Rigidbody2D)
- そのペアのどちらかの Collider2D が
IsTrigger = true(矢が ON。腕は OFF)
よくある詰まり: 片方が Trigger なのに OnCollisionEnter2D と書く/OnTriggerEnter と 2D を落とす/Rigidbody2D がない など。
補足: 腕に Rigidbody2D を付けなくてよいのは、矢側で上記条件を満たしているからです(元稿の中級者向けメモと同趣旨です)。
ステップ 4: ArrowController から座標計算を抜く
ArrowController から 円判定・DecreaseHp・衝突時の Destroy を外し、落下と画面外だけ残します。
// Assets/Scripts/ArrowController.cs(アフター例)
using UnityEngine;
public class ArrowController : MonoBehaviour
{
void Update()
{
// フレームごとに等速で落下させる
transform.Translate(0, -0.1f, 0);
// 画面外に出たらオブジェクトを破棄する
if (transform.position.y < -5.0f)
{
Destroy(gameObject);
}
}
}playerやGameObject.Findは不要になれば削除します。- 当たり判定の責務は、矢から 腕側へ移ります(イベント駆動の分岐)。
Dynamic + 重力にした場合は、
Update内のTranslateをやめ、Inspector の Gravity Scale などに任せられます。
ステップ 5: HP 減少と矢の破棄を腕側から
GameDirector の DecreaseHp() はそのまま使い、呼び出し元を腕に移します。
// Assets/Scripts/RightArmCollisionDetector.cs(完成形の例)
using UnityEngine;
public class RightArmCollisionDetector : MonoBehaviour
{
GameDirector director;
void Start()
{
director = GameObject.Find("GameDirector").GetComponent<GameDirector>();
}
private void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("右手に当たった");
director.DecreaseHp();
Destroy(collision.gameObject);
}
}// Assets/Scripts/LeftArmCollisionDetector.cs(完成形の例)
using UnityEngine;
public class LeftArmCollisionDetector : MonoBehaviour
{
GameDirector director;
void Start()
{
director = GameObject.Find("GameDirector").GetComponent<GameDirector>();
}
private void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("左手に当たった");
director.DecreaseHp();
Destroy(collision.gameObject);
}
}collision… ぶつかってきた相手の Collider2D。collision.gameObjectでその GameObject(矢)をDestroyできます。GameDirectorの取得はStartで一度(毎フレームFindしないのが目安です)。
中級者向け: 共通化の例
public class ArmCollisionDetector : MonoBehaviour
{
[SerializeField] string armLabel = "腕";
GameDirector director;
void Start()
{
director = GameObject.Find("GameDirector").GetComponent<GameDirector>();
}
private void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log($"{armLabel}に当たった");
director.DecreaseHp();
Destroy(collision.gameObject);
}
}SerializeField でラベルやダメージ倍率を Inspector から与えると、同じスクリプトを複数部位に貼る展開に向きます。
ビフォー / アフター比較
| 観点 | ビフォー(座標) | アフター(Collider 等) |
|---|---|---|
| 形 | 円固定の近似 | Box 等、Inspector で形を詰められる |
| 部位 | 1 式では区別しにくい | 子オブジェクト+スクリプトの分離 |
| 主なコード | 毎フレームの距離判定 | 衝突イベント中心 |
| 拡張 | 列挙が Arrow 側に寄りがち |
部位を増やす=子と Collider を足すのが素直 |
| 物理 | 自前 | Rigidbody2D に委ねられる余地 |
トラブルシューティング
- Console に何も出ない … 矢の Rigidbody2D、Trigger 設定、コールバック名(2D 付き)を確認する。
- 貫通する/反発が強い …
IsTriggerの意図(通過+イベントか、衝突応答か)を整理する。 - 矢の着弾位置がズレる … 生成位置のワールド座標と、カメラ・背景のオフセットの関係を確認する。
- 子の Collider 位置がおかしい … 親の Scale が 1 以外なら、子のオフセット感覚が変わります。
重要ポイント
- 座標と距離の1行(
d < r1 + r2)が、Unity 上ではCollider2D + Rigidbody2D + イベントの組に置き換わる、と捉えると移行の意味がはっきりします。 - 子オブジェクトは、当たり判定の単位=責務の単位に揃えやすい、という点で有効です。
- Rigidbody2D / Trigger / 2D コールバックの三つセットは、同様の 2D ミニゲーム全般の土台になります。
(参考)Animator でモーションの終端を揃える
Collider だけの話の先では、攻撃アニメーションと当たりが有効なフレームを揃えたい場面が出てきます。Animator では、Attack などのステートから Exit へ抜ける遷移に Has Exit Time を付け、Exit Time(例: 0.75 = 再生のおおよそ 75% 付近)で いつ次の遷移に入れるか を制御できます。攻撃の手が振り切れるまで ステートを抜かせない といった モーションの終端合わせに使います。遷移先が Exit の場合、プレビューに「遷移先のステートがない」旨の表示が出ることはあり得ます(設定上の例としては問題ありません)。

発展アイデア
- 部位ごとに 減少 HP を変える(右腕 5%・左腕 15% など)
ArmCollisionDetector一つに寄せ、Inspector だけ差し替え- Layer と Layer Collision Matrix で、矢と反応する相手を限定する
- 矢の Rigidbody2D を Dynamic にし、gravityScale や回転を試す
第1回からここまでで、「座標の一行」から「コンポーネントの組み合わせ」への橋を一度通したことになります。同じシーン上で、デバッグ表示(Gizmo)で Collider を可視化しながら触ると理解が深まります。