リンドくん先生、シューティングゲームで敵に弾が当たったかどうかって、どうやって判定するんですか?
たなべRaycast(レイキャスト)という仕組みを使うのが一般的なんだ。
これは目に見えない「光線」を飛ばして、何かに当たったかを検出する技術なんだよ。
リンドくん目に見えない光線...?なんだか難しそうです。
たなべ心配しないで。考え方はとてもシンプルなんだ。
今日はRaycastの基本からシューティングゲームへの応用まで、ゲーム開発でよく使う技術をじっくり学んでいこう。
Unityでゲームを作り始めると、必ず必要になる技術の一つがオブジェクトの検出です。
プレイヤーが敵を攻撃したとき、弾が敵に当たったかどうか。キャラクターの前に壁があるかどうか。こうした判定を行うのがRaycast(レイキャスト)という機能です。
Raycastは、3Dゲーム開発において最も基本的で重要な技術の一つと言えます。シューティングゲームだけでなく、アドベンチャーゲームやアクションゲームなど、ほぼすべてのジャンルで活用されています。
この記事では、Unityを学び始めたばかりの方でも理解できるよう、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 ColliderやCapsule 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の理解が深まれば、より高度なゲーム開発にも挑戦できるようになります。まずは今回紹介したサンプルコードを実際に動かしてみて、少しずつ自分なりにカスタマイズしていくことをおすすめします。
ゲーム開発は試行錯誤の連続ですが、その分完成したときの達成感は格別です。あなたのゲーム開発が実り多きものになることを願っています。





