Developer

【初心者Unity】Raycastの基本的な使い方(オブジェクトをクリックで取得)
2021.10.31
Lv1

【初心者Unity】Raycastの基本的な使い方(オブジェクトをクリックで取得)

1.はじめに

  • FPSの当たり判定を実装したい
  • 脱出ゲームでクリックしたアイテムを取得したい
  • RPGで敵に見つかったら追いかけられたい

一見すると共通点のなさそうなこれらの機能ですが、実はどれもRaycastという仕組みを使うことで実現できます。
Raycastの機能を簡潔に表すならば、ある始点と方向を与えたときにその先にあるオブジェクトを取得する、ということになるでしょうか。
非常に汎用性が高く、あらゆるジャンルのゲームにおいて頻出の仕組みとなっています。

本記事では、Raycastの仕組みについて解説するとともに、実装例まで紹介したいと思います。
しっかり理解して使いこなせるようにしましょう!

2.Rayとは

Raycastを理解するためには、まず、Rayについて理解する必要があります。
Rayは「光線」を表すクラスです。
コンストラクタの引数に始点と方向を与えることで生成できます。

■構文

Ray ray = new Ray(Vector3 origin, Vector3 direction);
引数名 意味
origin Rayの始点
direction Rayの伸びる方向

■例 原点からX軸方向に伸びるRayを生成

Vector3 origin = new Vector3(0, 0, 0); // 原点
Vector3 direction = new Vector3(1, 0, 0); // X軸方向を表すベクトル
Ray ray = new Ray(origin, direction); // Rayを生成

上記のスクリプトを任意のオブジェクトにアタッチすることでゲーム内にRayを生成することができます。

なお、Rayは無色透明な光の線ですので視認することができません。
開発段階においてはDebug.DrawRayで可視化すると便利です。

■構文

Debug.DrawRay(Vector3 start, Vector3 dir, Color color, float duration, bool depthTest);
引数名 意味
start Rayの始点
dir Rayの伸びる方向と長さ
color 表示する色
省略した場合は白色で表示
duration 表示する時間(秒)
省略した場合は1フレームのみ表示
depthTest Rayが他のオブジェクトと重なった時に隠すかどうか
省略した場合はtrue(隠す)

既存のRayを可視化する場合は、start・dirに可視化対象となるRayの始点・方向ベクトルを指定します。
それぞれ以下のプロパティから取得できます。

Ray ray = new Ray(new Vector3(0, 0, 0), new Vector3(1, 0, 0));
Vector3 origin = ray.origin; // 既存のRayの始点を取得
Vector3 direction = ray.direction; // 既存のRayの方向ベクトルを取得

■例 原点からX軸方向に伸びるRayを生成、長さ30・赤色で5秒間可視化

Vector3 origin = new Vector3(0, 0, 0); // 原点
Vector3 direction = new Vector3(1, 0, 0); // X軸方向を表すベクトル
Ray ray = new Ray(origin, direction); // Rayを生成
Debug.DrawRay(ray.origin, ray.direction * 30, Color.red, 5.0f); // 長さ30、赤色で5秒間可視化

3.Raycastとは

さて、本題のRaycastの説明です。
castは「投射する」というような意味の英単語です。
つまり、Raycastは「Rayを投射する」という意味の機能になりますが、Rayを投射することによって何ができるのでしょうか?
便利なことに、Raycastは次の2つの結果を同時に得ることができます。

  • Rayとコライダーが衝突しているかどうかのbool値
  • (Rayとコライダーが衝突している場合)衝突した相手オブジェクトを表すRaycastHit型の値

■構文
※Raycastには書き方が4パターンありますが、良く使用する1つを紹介しています。

Physics.Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);
引数名 意味
ray 投射する対象のRay
hitInfo 衝突した相手オブジェクトの情報
maxDistance Rayの長さ
省略した場合は無限長
layerMask 衝突検知の対象となるレイヤー
省略した場合はIgnore Raycastを除くすべてのレイヤー
queryTriggerInteraction is Triggerがonになっているcolliderも衝突対象にするかどうか
戻り値 意味
bool Rayと何らかのコライダーが衝突したらtrue
衝突しなかったらfalse

■例 原点からX軸方向に伸びるRayを生成、投射して衝突した相手オブジェクトの名前をコンソールに表示する

Vector3 origin = new Vector3(0, 0, 0); // 原点
Vector3 direction = new Vector3(1, 0, 0); // X軸方向を表すベクトル
Ray ray = new Ray(origin, direction); // Rayを生成;

RaycastHit hit;
if (Physics.Raycast(ray, out hit)) // もしRayを投射して何らかのコライダーに衝突したら
{
    string name = hit.collider.gameObject.name; // 衝突した相手オブジェクトの名前を取得
    Debug.Log(name); // コンソールに表示
}

少し複雑になってきましたね。
ポイントとなる箇所について、個別に解説したいと思います。

Raycastの実行

RaycastHit hit;
if (Physics.Raycast(ray, out hit)){...}

if文の条件式としてRaycastを使用しています。
第1引数には投射する対象のRay、第2引数には衝突した相手オブジェクトの情報が入ります。
ここで疑問に思われたかと思いますが、第2引数の書き方、少し変ですよね?
通常、引数には何らかの変数1つが入るはずですが、「out」「hit」の2単語が書かれています。
そもそも変数hitには「衝突した相手オブジェクトの情報」なんて入れた覚えがない…。

結論から話すと、outはパラメーター修飾子、hitは引数名をそれぞれ表しています。
outパラメーター修飾子は引数を参照渡しするための特殊なキーワードです。
参照渡しをすることによって、メソッド内で仮引数の値を書き換えた結果が呼び出し元の実引数にも反映されるようになります。

…と言っても良くわからないですね。
厳密な説明ではありませんが、イメージ図を作成してみました。

詳細は別の機会で解説しますが、変数hitに衝突した相手オブジェクトの情報が格納されるんだな、ということを押さえていただければ大丈夫です。
引数に値を入力するのではなく、引数に値が出力されるイメージです。

衝突したオブジェクトの取得

string name = hit.collider.gameObject.name; // 衝突した相手オブジェクトの名前を取得

変数hitには衝突した相手オブジェクトの情報が入っている、というお話はしました。
ここで注意したいのは、入っているのはあくまで「情報」であり「オブジェクトそのもの」ではない、ということです。
では、「オブジェクトそのもの」を取得したい場合はどうすれば良いのでしょうか。

hitはRaycastHit型の変数です。
RaycastHitクラスはプロパティとしてcollider(衝突した対象のコライダーコンポーネント)を持っています。
また、ColliderクラスはプロパティとしてgameObject(コライダーがアタッチされているオブジェクト)を持っています。
つまり、RaycastHit→Collider→GameObjectと辿っていくことで、衝突した対象オブジェクトを取得することができるのです。
これをスクリプトで表したものが上記となります。

4.実装例

それでは実際にRaycastを使用した例を見てみましょう。
今回実装するのは「クリックしたEnemyタグのオブジェクトを破壊する」機能です。

先にスクリプトの大枠を提示します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RayCastScript : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetMouseButtonDown(0)) // 左クリック
        {
            Ray ray = ①メインカメラからクリックした地点に伸びるRayを生成
            RaycastHit hit;
            if (②Rayを投射し、もし何らかのオブジェクトと衝突したら)
            {
                if(③衝突した相手のタグが"Enemy"だったら)
                {
                    ④相手オブジェクトを破壊
                }
            }
        }
    }
}

①メインカメラからクリックした地点に伸びるRayを生成
まずは投射するRayを作成しましょう。
今回作成したいのは、
始点 ➡ Main Camera
方向 ➡ Main Cameraからマウスをクリックした位置への方向
になりますが、方向は取得するのが難しそうです。

初めの方で、Rayの生成には始点と方向が必要だと説明しましたが、実はMain Cameraが始点の場合は、簡単にRayを生成する以下のメソッドが用意されています。

Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
語句 意味
Camera.main Main Cameraを表す特殊なプロパティ
ScreenPointToRay カメラを基準に引数のスクリーン座標の方向へ伸びるRayを生成する
Input.mousePosition マウスカーソルのスクリーン座標

if (Input.GetMouseButtonDown(0)){…}の中にInput.mousePositionを記述することで、左クリックをした瞬間のマウスカーソルの座標を取得することができますので、題意は満たせていそうです。

②Rayを投射し、もし何らかのオブジェクトと衝突したら
Raycastを使用します。
解説は前節をご参照ください。

if (Physics.Raycast(ray, out hit)) {...}

③衝突した相手のタグが”Enemy”だったら
タグの比較にはcompareTagメソッドが最適です。
ColliderクラスはcompareTagメソッドを用意しているため、以下のように記述するのが良いでしょう。

if(hit.collider.CompareTag("Enemy")){...}

④相手オブジェクトを破壊
オブジェクトの破壊にはDestroyメソッドを使用します。
Destroyメソッドの引数はGameObject型であるため、以下のように記述します。

Destroy(hit.collider.gameObject);

以下が完成版のコードです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RayCastScript : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetMouseButtonDown(0)) // 左クリック
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // Rayを生成
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit)) // Rayを投射
            {
                if(hit.collider.CompareTag("Enemy")) // タグを比較
                {
                    Destroy(hit.collider.gameObject); // オブジェクトを破壊
                }
            }
        }
    }
}

最後にシーン内に適当なオブジェクトを配置し、タグを”Enemy”にします(デフォルトでは存在しないので作成します)。

ゲームを再生しましょう。
クリックしたオブジェクトが破壊されるのが確認できたでしょうか。
※一番手前のオブジェクトはタグを”Untagged”にしています。なので破壊されません。

6.おわりに

Raycastの使い方について簡単に解説しました。
今回扱った実装例はとても単純なものでしたが、Raycastの動作を理解する上で良い教材になると思います。
Raycastは非常に応用が利く仕組みで、使いこなすことでゲームで表現できることの幅が広がります。
是非、本記事を参考に練習してみてください。

参考

冒頭に挙げた例の実装方針について軽く触れておきます。

・FPSの当たり判定を実装したい
こちらはHitscan方式の説明になります。
Hitscan方式とは実際に弾の発射は行わず、引き金を引いた時の射線上に敵が存在した場合に当たったと判定する方式です。
実際に弾丸のオブジェクトを飛ばして演算処理を行うProjectile方式と比較して、ライトな実装で採用されます。
Rayの始点を銃口、方向を銃の向きにすることで実現ができます。

・脱出ゲームでクリックしたアイテムを取得したい
本記事の例がそのまま使えるでしょう。
クリックしたオブジェクトを破壊すると同時にアイテム欄に加える等の処理が必要になるでしょうか。

・RPGで敵に見つかったら追いかけられる
Rayの始点を敵の目、方向を敵の目のtransform.forward等にすれば良いでしょう。
Rayとプレイヤーが衝突したらそれをトリガーにNavMeshが動的に作成され、敵がNavMeshAgentとしてプレイヤーを目標に走りだすようなイメージでしょうか。

 

連載目次リンク

「初心者のための」Unityゲーム制作 目次
 

Unity実践編 - 目次リンク

実践Unityゲームプログラミング 連載目次