【Unity】UnityのPhysics.Raycast徹底解説:初心者からマスターする射線判定の基本と応用

作成: 2025-12-10最終更新: 2026-07-13

「今見ているものは何か」「クリックした先に何があるか」「地面に立っているか」——空間の問いに答える万能ツールがPhysics.Raycastです。基本の書き方から、クリック選択・接地判定の定番レシピ、つまずきやすいレイヤーマスクまで徹底解説します。

「今、プレイヤーが見ているものは何か?」「マウスでクリックした先に何があるか?」「キャラクターは地面に立っているか?」——ゲーム開発では、こうした「空間への問いかけ」が頻繁に必要になります。

これに答えてくれるのが、Unityの物理エンジンが提供する Physics.Raycast です。ゲーム空間のある地点から、ある方向に向かって 目に見えない光線(Ray) を飛ばし、その光線が Collider(コライダー) を持つオブジェクトに当たったかどうか、当たったなら「何に・どこで・どれくらいの距離で」当たったかを教えてくれます。

この記事では、Physics.Raycastの基本的な使い方から、クリック選択・接地判定という2大定番レシピ、そして初心者が最も躓きやすいレイヤーマスクの活用法までを、実践的なC#コードを交えて解説します。

Raycastのイメージ。ロボットの目から光線が伸び、遠くのターゲットオブジェクトに命中してマーカーが光っている

この記事でわかること

  • Ray(始点+方向)とRaycastHit(衝突情報)の基本概念
  • もっともシンプルなRaycastの書き方とDebug.DrawRayでの可視化
  • 定番レシピ2つ: マウスクリックでオブジェクト選択接地判定
  • つまずきの元凶「レイヤーマスク」の指定方法と反転イディオム
  • 敵・壁・空振りを1本のRayで撃ち分ける射撃の完成例

Sponsored

Raycastの基本概念:光線と衝突情報

Physics.Raycastは、文字通り「光線(Ray)」を投射して、その光線がColliderを持つオブジェクトと交差するかどうかを調べます。登場人物は2つだけです。

Rayの概念図。始点から方向へ伸びる光線が最初のColliderに当たり、RaycastHitとして衝突した相手・衝突点・距離の情報が返ってくる

Ray(光線)とは

Rayは、「始点(Origin)」「方向(Direction)」 の2つの要素で定義されます。「カメラの位置から、カメラの前方へ」「キャラクターの足元から、真下へ」のように、どこから・どちらへを決めれば1本のRayになります。

RaycastHit(衝突情報)とは

Rayがオブジェクトに衝突した場合、その詳細がRaycastHitという構造体に格納されます。主に使うのは次の情報です。

  • hit.collider: 衝突した相手のCollider(そこからgameObjectや他のコンポーネントへアクセス)
  • hit.point: 衝突したワールド座標(着弾エフェクトを出す位置など)
  • hit.normal: 衝突した面の法線ベクトル(弾痕を面に沿って向けるなど)
  • hit.distance: 始点から衝突点までの距離
Sponsored

最もシンプルなRaycastの実装

最も基本的な使い方は、カメラの中心から前方にRayを飛ばし、何かに当たったかどうかを判定するものです。FPSの「照準が合っているものを調べる」がまさにこれです。

using UnityEngine;

public class SimpleRaycaster : MonoBehaviour
{
    void Update()
    {
        // 始点: メインカメラの位置
        Vector3 origin = Camera.main.transform.position;
        // 方向: メインカメラの前方
        Vector3 direction = Camera.main.transform.forward;
        // 最大距離: 100メートル
        float maxDistance = 100f;

        // Raycastを実行し、何かに当たったかどうかを判定
        if (Physics.Raycast(origin, direction, out RaycastHit hit, maxDistance))
        {
            Debug.Log("オブジェクトに衝突しました: " + hit.collider.gameObject.name);
        }

        // シーンビューにRayの軌跡を描画(デバッグ用)
        Debug.DrawRay(origin, direction * maxDistance, Color.red);
    }
}

Physics.Raycastはブール値を返します。trueは衝突があったことを意味し、その際にout hitで指定した変数に衝突情報が書き込まれます。

tips: 「Raycastが当たらない」バグの調査は、まずDebug.DrawRayRayを見える化するのが最短ルートです。向きが逆だった・長さが足りなかった・そもそも別の場所から飛んでいた——のような原因が一目で分かります。エディタでの可視化全般は Gizmos入門 を参照してください。

定番レシピ1:マウスクリックでオブジェクトを選択する

Raycastの実用で最も出番が多いのが「クリックしたものを取得する」です。ポイントは、マウスのスクリーン座標から3D空間へのRayをScreenPointToRayで作ることです。画面は2D・世界は3Dなので、「画面上のこの点の奥に伸びる線」に変換してもらうイメージです。

ScreenPointToRayの図。画面上のマウスカーソルの位置から、カメラを通して3D空間の奥へRayが伸び、クリックした先の宝箱に命中している
using UnityEngine;

public class ClickSelector : MonoBehaviour
{
    void Update()
    {
        // 左クリックの瞬間だけ判定
        if (Input.GetMouseButtonDown(0))
        {
            // マウスのスクリーン座標から、3D空間へ伸びるRayを作る
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            if (Physics.Raycast(ray, out RaycastHit hit, 100f))
            {
                Debug.Log("クリックしたオブジェクト: " + hit.collider.gameObject.name);
                // hit.point にエフェクトを出す、選択状態にする、などの処理
            }
        }
    }
}

RTSのユニット選択、パズルのピースをつまむ、3D画面のボタンを押す——応用範囲は無限大です。マウス入力の基本は 入力処理の記事 で整理しています。

定番レシピ2:接地判定

「ジャンプは地面にいるときだけ」を実現する接地判定も、Raycastの定番です。キャラクターの足元から真下に短いRayを飛ばし、地面レイヤーに当たるかどうかで判定します。

接地判定の図。キャラクターの足元から真下に短いRayが伸びて地面に当たっている状態と、ジャンプ中でRayが地面に届かず接地していない状態の比較
using UnityEngine;

public class GroundChecker : MonoBehaviour
{
    [SerializeField] private LayerMask groundLayer;  // 地面のレイヤーだけを対象に
    [SerializeField] private float checkDistance = 0.2f;

    public bool IsGrounded()
    {
        // 足元の少し上から、真下に短いRayを飛ばす
        Vector3 origin = transform.position + Vector3.up * 0.1f;
        return Physics.Raycast(origin, Vector3.down, checkDistance + 0.1f, groundLayer);
    }

    void Update()
    {
        if (IsGrounded() && Input.GetKeyDown(KeyCode.Space))
        {
            Debug.Log("ジャンプ!");
        }
    }
}

ここでgroundLayerを指定しているのがポイントです。指定しないと自分自身のColliderにRayが当たって「常に接地している」ことになる——これが次のセクションの主役、レイヤーマスクの話です。

Sponsored

初心者が躓きやすいポイント:レイヤーマスクの活用

Raycastを使用する際、初心者が最も混乱しやすいのが 「レイヤーマスク(LayerMask)」 の扱いです。

問題点:意図しないオブジェクトへの衝突

デフォルトのPhysics.Raycastは、シーン内のすべてのColliderを対象とします。プレイヤー自身、透明なトリガー、エフェクト——対象にしたくないものにRayが当たると、「銃弾が自分に当たる」「接地判定が常にtrue」のような意図しない動作を引き起こします。

解決策:LayerMaskで対象を絞り込む

Physics.Raycastには、オプションの引数としてlayerMaskを指定できます。Rayに「このレイヤーのものにだけ当たってね」というフィルタを付けるイメージです。

レイヤーマスクの図。1本のRayの通り道に自分自身・エフェクト・敵が並んでいるが、レイヤーマスクのフィルタによって自分とエフェクトは素通りし、敵レイヤーのオブジェクトにだけ当たる
using UnityEngine;

public class LayerMaskRaycaster : MonoBehaviour
{
    // Inspectorから設定できるようにSerializeFieldで定義
    [SerializeField]
    private LayerMask targetLayer;

    void Update()
    {
        Vector3 origin = Camera.main.transform.position;
        Vector3 direction = Camera.main.transform.forward;
        float maxDistance = 100f;

        // 最後の引数にLayerMaskを指定
        if (Physics.Raycast(origin, direction, out RaycastHit hit, maxDistance, targetLayer))
        {
            Debug.Log("ターゲットレイヤーのオブジェクトに衝突: " + hit.collider.gameObject.name);
        }
    }
}

LayerMask型の変数を[SerializeField]にしておけば、対象レイヤーをInspectorのドロップダウンで選ぶだけで済みます。コードにレイヤー名を書き込まずに済むため、まずはこの形を基本にしましょう(レイヤーの仕組み自体は レイヤーとタグの記事 で解説しています)。

ゲームでの実用例:ジャンル別のレイヤー設計

実際のゲームでは、レイヤーマスクは次のように働きます。自分のジャンルに置き換えてみてください。

レイヤーマスクのジャンル別実用例の図。FPSの射撃では弾のRayがEnemyとWallレイヤーにだけ当たり味方や回復アイテムは素通り、クリック移動RPGではマウスからのRayがGroundレイヤーにだけ当たって敵やNPCの上をクリックしても地面の移動先が取れる
  • FPS・TPSの射撃: 弾のRayはEnemyWallだけに当てます。味方・回復アイテム・演出用のパーティクルが射線上にいても素通りさせないと、「見えない何かに弾が吸われる」ゲームになります
  • クリック移動(RPG・RTS): 移動先の指定はGroundレイヤーだけに当てます。敵やNPCの頭上をクリックしても、Rayはキャラクターを素通りしてその足元の地面に当たるため、正しい移動先が取れます
  • 敵AIの視線判定(ステルスゲーム): 敵からプレイヤーへRayを飛ばし、Wallレイヤーを含めて判定します。Rayが先に壁に当たれば「障害物で見えていない」、プレイヤーに届けば「発見」——隠密の成立条件そのものです

どの例も共通するのは、「Rayに当たってほしいもの」はゲームのルールが決めるということです。レイヤー設計は当たり判定のルール設計そのものだと考えると、迷いにくくなります。

特定のレイヤーを無視する方法

逆に、「特定のレイヤー(例:プレイヤー自身や透明な壁など)以外のすべて」を対象としたい場合もあります。この場合、ビット演算子を使ってレイヤーマスクを反転させます。

例えば、「Ignore Raycast」レイヤーを無視したい場合は、以下のコードを使用します。

using UnityEngine;

public class IgnoreLayerRaycaster : MonoBehaviour
{
    void Update()
    {
        // 無視したいレイヤーの番号を取得
        int ignoreLayer = LayerMask.NameToLayer("Ignore Raycast");

        // 「そのレイヤーのビットだけを立てて、全体を反転」=それ以外のすべて
        int layerMask = ~(1 << ignoreLayer);

        Vector3 origin = transform.position;
        Vector3 direction = transform.forward;
        float maxDistance = 10f;

        // 反転させたLayerMaskを適用
        if (Physics.Raycast(origin, direction, out RaycastHit hit, maxDistance, layerMask))
        {
            Debug.DrawRay(origin, direction * hit.distance, Color.green);
            Debug.Log("無視レイヤー以外のオブジェクトに衝突: " + hit.collider.gameObject.name);
        }
        else
        {
            Debug.DrawRay(origin, direction * maxDistance, Color.white);
        }
    }
}

この~(1 << ignoreLayer)という記述は、UnityのLayerMaskを扱う上で非常に頻繁に使用されるイディオム(慣用的な書き方)ですので、ぜひ覚えておきましょう。

Sponsored

実践:1本のRayで「敵・壁・空振り」を撃ち分ける

仕上げに、ここまでの部品——RaycastHitの中身、レイヤーマスク、可視化——を1本の射撃にまとめます。FPS・TPSの銃はもちろん、ソウルライクの遠距離攻撃でも、SFのレーザーポインタでも、中身はこの形です。目標は「敵に当たればHPが減って火花、壁なら弾痕、何もなければ何も起きない」を1本のRayで分岐させることです。

1本のRayが当たった相手で結果が変わる図。敵に命中すればHP−10と火花、壁に命中すれば面に沿った弾痕、空振りなら何も起きない3パターンの比較
using UnityEngine;

public class HitscanShooter : MonoBehaviour
{
    [SerializeField] private LayerMask hitLayers;        // EnemyとWallだけを入れる
    [SerializeField] private GameObject enemyHitEffect;  // 火花・ヒットマークなど
    [SerializeField] private GameObject wallHitEffect;   // 弾痕・土煙など
    [SerializeField] private float range = 50f;
    [SerializeField] private int damage = 10;

    void Update()
    {
        if (Input.GetMouseButtonDown(0)) Shoot();
    }

    void Shoot()
    {
        // 画面中央(照準)からまっすぐ前へ1本
        Ray ray = new Ray(Camera.main.transform.position, Camera.main.transform.forward);

        // 空振り: 何にも当たらなければ、何もしないで終わり
        if (!Physics.Raycast(ray, out RaycastHit hit, range, hitLayers,
                QueryTriggerInteraction.Ignore))
        {
            return;
        }

        if (hit.collider.TryGetComponent(out EnemyHealth enemy))
        {
            // 敵: HPを減らして、敵用エフェクト
            enemy.TakeDamage(damage);
            Instantiate(enemyHitEffect, hit.point, Quaternion.LookRotation(hit.normal));
        }
        else
        {
            // 壁: 弾痕を「面の向き」に沿わせて置く
            Instantiate(wallHitEffect, hit.point, Quaternion.LookRotation(hit.normal));
        }
    }
}

見どころは3つあります。

  • 分岐はTryGetComponent1回: 当たった相手がEnemyHealthを持っていれば敵、持っていなければ壁。「敵か壁か」をタグ文字列の比較でなくコンポーネントの有無で判定するので、敵の種類が増えても書き換え不要です
  • hit.normalの回収: 冒頭で紹介した法線ベクトルがここで活躍します。Quaternion.LookRotation(hit.normal)で、弾痕が斜めの壁でも面にぴたりと沿って貼り付きます
  • QueryTriggerInteraction.Ignore: 回復エリアのような透明トリガーを弾が素通りします。これを外すと「見えない何かに弾が吸われる」不思議バグの完成です

わざと壊して、直す

この射撃は、Raycastの定番事故をまとめて体験できる教材でもあります。

  1. hitLayersからEnemyを外す: 弾が敵を貫通して、背後の壁にだけ弾痕が付きます。「当たらない」バグの最有力容疑者は、いつでもレイヤーマスクです
  2. 始点をカメラでなく銃口にする: 銃口はプレイヤーのColliderのすぐそば(または内部)なので、マスクの設定次第で自分に当たります。FPSで「照準はカメラ中心・演出は銃口から」と2本に分けるのはこれが理由です
  3. rangeを5fに縮める: 遠くの敵に当たらなくなります。Debug.DrawRay(ray.origin, ray.direction * range, Color.red, 0.5f)を1行足してRayを目で見れば、「短すぎた」が一瞬で分かります

Playして、敵を撃てばHPが減って火花が散り、壁なら弾痕が面に沿って付き、空を撃てば何も起きない——1本のRayで3つの結果が正しく分かれたら完成です。この分岐の型は、視線判定にもアイテム調べにもそのまま流用できます。

おまけ:先に知っておくと良いこと

Raycastを使いこなせるようになったら、次はこの兄弟たちが視野に入ってきます。

  • 全部欲しいならRaycastAll: Physics.Raycast最初の1つにしか当たりません。貫通する弾や「壁越しに敵が何体いるか」には、射線上のすべての衝突を配列で返すPhysics.RaycastAllを使います。
  • 太さが欲しいならSphereCast: Rayは太さゼロの線なので、細い隙間をすり抜けます。「球を転がしながら進める」Physics.SphereCastにすると、キャラクターの幅を考慮した接地判定や当たり判定になります。
  • トリガーに当てたくないとき: 引数のQueryTriggerInteraction.Ignoreを指定すると、Is TriggerのColliderを無視できます(デフォルトはプロジェクト設定の「Queries Hit Triggers」に従います)。
  • 2DゲームではPhysics2D.Raycast: 2DのColliderには2D専用APIを使います。戻り値がRaycastHit2Dを直接返すなど形も違うため、Rigidbody2DとCollider2Dの記事 とあわせて確認してください。

まとめ

Physics.Raycastは、Unityでのインタラクション実装の基盤となる技術です。

  1. Raycastは「光線」による当たり判定:始点と方向を持つRayを飛ばし、Colliderとの衝突を判定する。
  2. 衝突情報はRaycastHitに格納される:相手(collider)・位置(point)・法線(normal)・距離(distance)が取れる。
  3. クリック選択はScreenPointToRay:マウスのスクリーン座標を3D空間のRayに変換する定番パターン。
  4. 接地判定は「足元から真下に短いRay」+地面レイヤー指定
  5. LayerMaskで対象を絞り込む:指定しないと自分自身にも当たる。無視したい場合は~(1 << layer)のビット反転イディオム。
  6. Debug.DrawRayで可視化:「当たらない」バグは、まずRayを目で見るのが最短ルート。
  7. 分岐はTryGetComponent:当たった相手のコンポーネントの有無で処理を分け、hit.pointhit.normalでエフェクトを置く。

これらの知識とコード例を活用することで、オブジェクト操作や視線判定の精度は飛躍的に向上するはずです。