フリーキーズ | 独学プログラミング

Unity入門 Raycastでオブジェクト検出とシューティングの基礎を実装しよう!初心者向け基礎解説

リンドくん

リンドくん

先生、シューティングゲームで敵に弾が当たったかどうかって、どうやって判定するんですか?

たなべ

たなべ

Raycast(レイキャスト)という仕組みを使うのが一般的なんだ。
これは目に見えない「光線」を飛ばして、何かに当たったかを検出する技術なんだよ。

リンドくん

リンドくん

目に見えない光線...?なんだか難しそうです。

たなべ

たなべ

心配しないで。考え方はとてもシンプルなんだ。
今日はRaycastの基本からシューティングゲームへの応用まで、ゲーム開発でよく使う技術をじっくり学んでいこう。

Unityでゲームを作り始めると、必ず必要になる技術の一つがオブジェクトの検出です。
プレイヤーが敵を攻撃したとき、弾が敵に当たったかどうか。キャラクターの前に壁があるかどうか。こうした判定を行うのがRaycast(レイキャスト)という機能です。

Raycastは、3Dゲーム開発において最も基本的で重要な技術の一つと言えます。シューティングゲームだけでなく、アドベンチャーゲームやアクションゲームなど、ほぼすべてのジャンルで活用されています。

この記事では、Unityを学び始めたばかりの方でも理解できるよう、Raycastの基本概念からシューティングゲームの基本実装まで、段階的に解説していきます。

プログラミング学習でお悩みの方へ

HackATAは、エンジニアを目指す方のためのプログラミング学習コーチングサービスです。 経験豊富な現役エンジニアがあなたの学習をサポートします。

✓ 質問し放題

✓ β版公開中(2025年内の特別割引)

HackATAの詳細を見る

Raycastとは何か?基本概念を理解しよう

リンドくん

リンドくん

そもそもRaycastって、具体的にどういう仕組みなんですか?

たなべ

たなべ

イメージとしてはレーザーポインターを想像してみて。
レーザーを壁に向けて照射すると、壁に赤い点が見えるよね。Raycastもそれと同じで、ある地点から特定の方向に目に見えない線を飛ばして、何かに当たったかを検出するんだ。

Raycastの概念

Raycastの概念

Raycastの基本的な仕組み

Raycastは日本語で「光線投射」と訳されます。その名の通り、仮想的な光線(Ray)を特定の方向に飛ばし、その光線が何かのオブジェクトに衝突したかを検出する技術です。

この仕組みには以下のような特徴があります。

  • 始点と方向を指定できる - どこから、どの方向に光線を飛ばすかを自由に設定できます
  • 距離を制限できる - 無限に飛ばすのではなく、特定の距離までに制限することも可能です
  • 衝突したオブジェクトの情報を取得できる - 何に当たったのか、どの位置で当たったのかなどの詳細情報が得られます
  • レイヤーで対象を絞り込める - 特定のレイヤーのオブジェクトだけを検出対象にできます

なぜRaycastが重要なのか

ゲーム開発において、Raycastは以下のような場面で必須の技術となります。

  • シューティングゲームの弾の当たり判定 - 銃で撃った弾が敵に当たったかを瞬時に判定
  • 視線の検出 - キャラクターが特定のオブジェクトを見ているかどうかの判定
  • 地面との接触判定 - キャラクターが地面に立っているかどうかの確認
  • クリック判定 - マウスでクリックした位置にあるオブジェクトの特定

特にシューティングゲームでは、Raycastを使わない実装は考えられないほど重要な技術です。物理的な弾のオブジェクトを飛ばす方法もありますが、高速な弾の場合はRaycastを使う方が正確で効率的なのです。

Raycastの種類

Unityには主に2つのRaycast方式があります。

  • Physics.Raycast - 3Dゲーム用の基本的なRaycast
  • Physics2D.Raycast - 2Dゲーム用のRaycast

この記事では、3Dゲームで使用するPhysics.Raycastを中心に解説していきます。

UnityでRaycastを使ってみよう

リンドくん

リンドくん

実際にどうやってコードを書けばいいんですか?

たなべ

たなべ

基本的な使い方は意外とシンプルなんだよ。
まずは最もシンプルなRaycastの例から見ていこうか。

最もシンプルなRaycast

まずは、オブジェクトの前方に光線を飛ばして、何かに当たったかを検出する基本的なコードを見てみましょう。

using UnityEngine;

public class SimpleRaycast : MonoBehaviour
{
    void Update()
    {
        // このオブジェクトの位置から前方に向かってRayを飛ばす
        Ray ray = new Ray(transform.position, transform.forward);
        
        // Raycastを実行(最大距離10単位) ※Unityで作るゲーム内単位
        if (Physics.Raycast(ray, 10f))
        {
            Debug.Log("何かに当たりました!");
        }
    }
}

このコードでは、以下のことを行っています。

  • Rayオブジェクトを作成 - 始点(transform.position)と方向(transform.forward)を指定
  • Physics.Raycastで光線を飛ばす - 10単位先まで検出
  • 何かに当たったらtrueが返される - 当たった場合にメッセージを表示

当たったオブジェクトの情報を取得する

次は、当たったオブジェクトの詳細情報を取得してみましょう。

using UnityEngine;

public class RaycastWithInfo : MonoBehaviour
{
    void Update()
    {
        Ray ray = new Ray(transform.position, transform.forward);
        RaycastHit hitInfo;
        
        // Raycastを実行し、当たった情報をout hitInfoに格納
        if (Physics.Raycast(ray, out hitInfo, 10f))
        {
            // 当たったオブジェクトの名前を表示
            Debug.Log("当たったオブジェクト: " + hitInfo.collider.gameObject.name);
            
            // 当たった位置を表示
            Debug.Log("当たった位置: " + hitInfo.point);
            
            // 当たったオブジェクトまでの距離を表示
            Debug.Log("距離: " + hitInfo.distance + "メートル");
        }
    }
}

RaycastHitという構造体を使うことで、以下のような情報が取得できます。

  • collider - 当たったオブジェクトのColliderコンポーネント
  • point - 当たった正確な座標
  • distance - 始点から当たった地点までの距離
  • normal - 当たった面の法線ベクトル

Rayを可視化してデバッグする

開発中は、Rayが実際にどこに飛んでいるのかを視覚的に確認したいものです。そんなときはDebug.DrawRayを使いましょう。

using UnityEngine;

public class VisualizedRaycast : MonoBehaviour
{
    void Update()
    {
        Vector3 startPosition = transform.position;
        Vector3 direction = transform.forward;
        float maxDistance = 10f;
        
        // Scene viewでRayを赤い線で表示
        Debug.DrawRay(startPosition, direction * maxDistance, Color.red);
        
        // Raycastを実行
        RaycastHit hitInfo;
        if (Physics.Raycast(startPosition, direction, out hitInfo, maxDistance))
        {
            Debug.Log("当たった!");
        }
    }
}

Debug.DrawRayを使うと、UnityエディタのScene viewでRayが赤い線として表示されます。これにより、Rayが意図した方向に飛んでいるかを確認できるので、デバッグがとても楽になります。

シューティングゲームの基礎

リンドくん

リンドくん

基本はわかりました!でも実際のシューティングゲームではどう使うんですか?

たなべ

たなべ

よし、それじゃあ実際に銃を撃つシステムを作ってみよう。
マウスをクリックしたら弾を発射して、敵に当たったらダメージを与える、という本格的なシステムだよ。

基本的なシューティングシステム

まずは、シンプルなシューティングシステムを作ってみましょう。

using UnityEngine;

public class SimpleShooter : MonoBehaviour
{
    [SerializeField] private float shootRange = 100f;  // 射程距離
    [SerializeField] private float shootDamage = 10f;  // ダメージ量
    
    void Update()
    {
        // マウス左クリックで射撃
        if (Input.GetMouseButtonDown(0))
        {
            Shoot();
        }
    }
    
    void Shoot()
    {
        // カメラの中心から前方に向かってRayを飛ばす
        Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
        RaycastHit hitInfo;
        
        // Raycastを実行
        if (Physics.Raycast(ray, out hitInfo, shootRange))
        {
            Debug.Log("命中: " + hitInfo.collider.gameObject.name);
            
            // 当たったオブジェクトにダメージを与える
            Enemy enemy = hitInfo.collider.GetComponent<Enemy>();
            if (enemy != null)
            {
                enemy.TakeDamage(shootDamage);
            }
        }
    }
}

このコードのポイントは以下の通りです。

  • Camera.main.ViewportPointToRay - 画面中央(照準の位置)からRayを飛ばす
  • 敵オブジェクトの検出 - GetComponent<Enemy>()で敵スクリプトを取得
  • ダメージの適用 - 敵が見つかった場合のみダメージを与える

敵のダメージシステム

上記のシューティングシステムと連携する、敵のダメージ処理スクリプトも作成しましょう。

using UnityEngine;

public class Enemy : MonoBehaviour
{
    [SerializeField] private float maxHealth = 100f;  // 最大HP
    private float currentHealth;
    
    void Start()
    {
        // 初期HPを設定
        currentHealth = maxHealth;
    }
    
    public void TakeDamage(float damage)
    {
        // ダメージを受ける
        currentHealth -= damage;
        Debug.Log(gameObject.name + " の残りHP: " + currentHealth);
        
        // HPが0以下になったら破壊
        if (currentHealth <= 0)
        {
            Die();
        }
    }
    
    void Die()
    {
        Debug.Log(gameObject.name + " が倒された!");
        Destroy(gameObject);
    }
}

より本格的なシューティングシステム

次は、射撃エフェクトや着弾エフェクトも含めた、より本格的なシステムを見てみましょう。

using UnityEngine;

public class AdvancedShooter : MonoBehaviour
{
    [Header("射撃設定")]
    [SerializeField] private float shootRange = 100f;
    [SerializeField] private float shootDamage = 10f;
    [SerializeField] private float fireRate = 0.1f;  // 連射間隔(秒)
    
    [Header("エフェクト")]
    [SerializeField] private GameObject muzzleFlashEffect;  // 発砲エフェクト
    [SerializeField] private GameObject impactEffect;  // 着弾エフェクト
    [SerializeField] private Transform firePoint;  // 発砲位置
    
    private float nextFireTime = 0f;
    
    void Update()
    {
        // 連射制限付きの射撃
        if (Input.GetMouseButton(0) && Time.time >= nextFireTime)
        {
            nextFireTime = Time.time + fireRate;
            Shoot();
        }
    }
    
    void Shoot()
    {
        // 発砲エフェクトを表示
        if (muzzleFlashEffect != null)
        {
            Instantiate(muzzleFlashEffect, firePoint.position, firePoint.rotation);
        }
        
        // Raycastで射撃判定
        Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
        RaycastHit hitInfo;
        
        if (Physics.Raycast(ray, out hitInfo, shootRange))
        {
            Debug.Log("命中: " + hitInfo.collider.gameObject.name);
            
            // 着弾エフェクトを表示
            if (impactEffect != null)
            {
                GameObject impact = Instantiate(impactEffect, hitInfo.point, 
                                               Quaternion.LookRotation(hitInfo.normal));
                Destroy(impact, 2f);  // 2秒後に削除
            }
            
            // ダメージ処理
            Enemy enemy = hitInfo.collider.GetComponent<Enemy>();
            if (enemy != null)
            {
                enemy.TakeDamage(shootDamage);
            }
        }
        
        // Rayを可視化(デバッグ用)
        Debug.DrawRay(ray.origin, ray.direction * shootRange, Color.yellow, 0.5f);
    }
}

この実装では以下の機能が追加されています。

  • 連射制限 - fireRateで連射速度を制御
  • 発砲エフェクト - 銃口から火花が出るエフェクト
  • 着弾エフェクト - 弾が当たった場所に表示されるエフェクト
  • 法線ベクトルの活用 - 着弾エフェクトが壁の向きに合わせて表示される

レイヤーとタグを使った高度な検出

リンドくん

リンドくん

敵以外のオブジェクトにも弾が当たっちゃうんですけど、敵だけを狙いたいときはどうするんですか?

たなべ

たなべ

それにはレイヤーマスクという仕組みを使うんだ。
特定のレイヤーのオブジェクトだけを検出対象にできるから、とても便利なんだよ。

レイヤーマスクを使った検出

特定のレイヤーのオブジェクトだけを検出したい場合は、レイヤーマスクを使います。

using UnityEngine;

public class LayerBasedShooter : MonoBehaviour
{
    [SerializeField] private float shootRange = 100f;
    [SerializeField] private LayerMask targetLayers;  // 検出対象のレイヤー
    
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Shoot();
        }
    }
    
    void Shoot()
    {
        Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
        RaycastHit hitInfo;
        
        // 特定のレイヤーのみを対象にRaycastを実行
        if (Physics.Raycast(ray, out hitInfo, shootRange, targetLayers))
        {
            Debug.Log("ターゲットに命中: " + hitInfo.collider.gameObject.name);
            
            Enemy enemy = hitInfo.collider.GetComponent<Enemy>();
            if (enemy != null)
            {
                enemy.TakeDamage(10f);
            }
        }
    }
}

UnityエディタでtargetLayersに「Enemy」レイヤーだけを設定すれば、敵オブジェクトだけに反応するようになります。

タグを使った判定

レイヤーと並んでよく使われるのがタグによる判定です。

using UnityEngine;

public class TagBasedShooter : MonoBehaviour
{
    [SerializeField] private float shootRange = 100f;
    
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Shoot();
        }
    }
    
    void Shoot()
    {
        Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
        RaycastHit hitInfo;
        
        if (Physics.Raycast(ray, out hitInfo, shootRange))
        {
            // タグで判定
            if (hitInfo.collider.CompareTag("Enemy"))
            {
                Debug.Log("敵に命中!");
                
                Enemy enemy = hitInfo.collider.GetComponent<Enemy>();
                if (enemy != null)
                {
                    enemy.TakeDamage(10f);
                }
            }
            else if (hitInfo.collider.CompareTag("Wall"))
            {
                Debug.Log("壁に命中");
            }
        }
    }
}

タグを使うことで、当たったオブジェクトの種類に応じて異なる処理を実行できます。

レイヤーとタグの使い分け

それぞれの使い分けは以下のような感じです。

  • レイヤー - 物理演算やRaycastの対象を事前に絞り込みたい場合に使用
  • タグ - 当たった後に種類を判定したい場合に使用

一般的に、パフォーマンスを重視する場合はレイヤーマスクを使うことが推奨されます。なぜなら、不要なオブジェクトは最初から検出対象外にできるからです。

よくある問題と解決方法

リンドくん

リンドくん

実際に作ってみたんですけど、うまく当たり判定が取れないことがあるんです...

たなべ

たなべ

Raycastでよくつまずくポイントがいくつかあるんだよね。
代表的な問題とその解決方法を見ていこう。

問題1. Colliderがない

症状: Raycastが何にも当たらない

原因: 対象のオブジェクトにColliderコンポーネントがついていない

解決法

// オブジェクトにColliderがあるか確認する方法
void CheckCollider()
{
    Collider col = GetComponent<Collider>();
    if (col == null)
    {
        Debug.LogError("Colliderがありません!");
    }
}

敵オブジェクトには必ずBox ColliderCapsule Colliderなどを追加してください。

問題2. Rayの始点が間違っている

症状: 意図しない場所からRayが飛んでいる

解決法

void CorrectRayOrigin()
{
    // カメラの位置から飛ばす場合
    Ray ray = new Ray(Camera.main.transform.position, Camera.main.transform.forward);
    
    // 銃口の位置から飛ばす場合
    // Ray ray = new Ray(gunMuzzle.position, gunMuzzle.forward);
    
    // 画面中央(照準位置)から飛ばす場合
    // Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
    
    Debug.DrawRay(ray.origin, ray.direction * 10f, Color.red, 1f);
}

Debug.DrawRayで必ず可視化して、Rayが正しい位置から飛んでいるか確認しましょう。

問題3. レイヤーの設定ミス

症状: 特定のオブジェクトだけ検出されない

解決法

  • Unityエディタで対象オブジェクトのレイヤーが正しく設定されているか確認
  • レイヤーマスクに該当レイヤーが含まれているか確認
  • Physics.Raycastのレイヤーマスク引数が正しく渡されているか確認

問題4. Rayの距離が短すぎる

症状: 遠くの敵に当たらない

解決法

// 距離を十分に長く設定
[SerializeField] private float shootRange = 100f;  // 短すぎる場合は増やす

// または無限に飛ばす(Mathf.Infinity)
if (Physics.Raycast(ray, out hitInfo, Mathf.Infinity))
{
    // 処理
}

まとめ

リンドくん

リンドくん

Raycastって最初は難しそうでしたけど、理解できました!

たなべ

たなべ

よく頑張ったね!Raycastはゲーム開発で最も使う技術の一つだから、今日学んだことはこれからずっと役に立つよ。
まずは簡単なシューティングゲームから作り始めてみよう。

この記事では、UnityのRaycastを使ったオブジェクト検出とシューティングゲームの実装について解説してきました。

重要なポイントをおさらいしましょう。

  • Raycastは目に見えない光線を飛ばしてオブジェクトを検出する技術です
  • RayオブジェクトとPhysics.Raycastを組み合わせて使います
  • RaycastHitで当たったオブジェクトの詳細情報を取得できます
  • レイヤーマスクを使えば特定のオブジェクトだけを検出できます
  • Debug.DrawRayで可視化してデバッグすると開発がスムーズになります

シューティングゲームだけでなく、アドベンチャーゲームやパズルゲームなど、ほぼすべてのゲームジャンルで使える基本技術です。今回学んだ内容を活かして、ぜひ自分だけのゲームを作ってみてください。

Raycastの理解が深まれば、より高度なゲーム開発にも挑戦できるようになります。まずは今回紹介したサンプルコードを実際に動かしてみて、少しずつ自分なりにカスタマイズしていくことをおすすめします。

ゲーム開発は試行錯誤の連続ですが、その分完成したときの達成感は格別です。あなたのゲーム開発が実り多きものになることを願っています。

この記事をシェア

関連するコンテンツ