LessonEX Raycast
EX-1 Raycastとは
UnityにはRaycastという機能があり、Ray(光線)を発射してそれがヒットしたオブジェクトの情報を取得することができます。2Dランゲーム編や3D脱出ゲーム編でも使用しています。
まずは適当な3Dプロジェクトを開いて、レイの基点となるオブジェクトとその他のオブジェクトをいくつか配置しましょう。

新しいスクリプトRayTestを作成して、レイの基点とするオブジェクトにアタッチしましょう。
RayTestスクリプトを開いて、以下のように入力してください。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RayTest : MonoBehaviour
{
void Start()
{
RaycastHit hit;
// コライダーと衝突したらtrueを返す
if (Physics.Raycast(transform.position, Vector3.right, out hit))
{
// 衝突したオブジェクトを削除する
Destroy(hit.collider.gameObject);
}
}
}
レイがヒットしたオブジェクトの情報は、RaycastHit型の変数に格納されます。
Physics.Raycast関数はレイを発射して衝突したオブジェクトの情報を取得します。
第一引数:レイの基点となる座標(発射地点)
第二引数:レイの方向
第三引数:情報の格納先(RaycastHit型)
第三引数にはoutという少々見慣れない修飾子がついています。outがないと情報の格納先であるhitが初期化されていないためにエラーが発生してしまいます。その時にout修飾子をつけることで「この関数内で初期化するから無視して大丈夫だよ」と教えている訳です(よくわからない場合はとりあえずoutをつけなきゃいけないんだな~くらいの認識でOKです)

第三引数に格納されるのは最初にレイがヒットしたオブジェクトの情報のみです。なのでこの状態で実行するとオブジェクトは1つしか消えません。

第四引数ではレイの長さ(MaxDistance)を設定できます。指定しなかった場合、レイの長さは無限大になります。
試しにレイの長さを極端に短くして、レイが他のオブジェクトにヒットしなくなることを確認してみてください。

第四引数に小さい値を入れるとレイが短くなり、オブジェクトが消えなくなるはずです。
レイが衝突しては困るオブジェクトもあると思います。そういったオブジェクトはLayerをIgnore Raycastに設定しましょう。このレイヤーに設定したオブジェクトはレイが衝突しなくなります。
(第四引数を削除して、レイの長さを無限大に戻しておいてください)

第四引数に小さい値を入れるとレイが短くなり、オブジェクトが消えなくなるはずです。
レイが衝突しては困るオブジェクトもあると思います。そういったオブジェクトはLayerをIgnore Raycastに設定しましょう。このレイヤーに設定したオブジェクトはレイが衝突しなくなります。
(第四引数を削除してレイの長さを無限大に戻しておいてください)

レイがどのように発射されているか確認するために、シーン内にレイを表示するようにしてみましょう。
RayTestスクリプトに赤い部分のコードを追加してください。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RayTest : MonoBehaviour
{
void Start()
{
RaycastHit hit;
// コライダーと衝突したらtrueを返す
if (Physics.Raycast(transform.position, Vector3.right,out hit))
{
// 衝突したオブジェクトを削除する
Destroy(hit.collider.gameObject);
}
// レイを描画
Debug.DrawRay(transform.position, Vector3.right * 100.0f, Color.red, 10.0f);
}
}
Debug.DrawRay関数はレイを描画するためのデバッグ用関数です。Physics.Raycast関数とは引数の構成が違うので気をつけてください。
第一引数:レイの基点となる座標
第二引数:レイが伸びるベクトル(レイの方向と長さ)
第三引数:レイの色
第四引数:レイが表示されている時間(今回は10,0fを指定しているので10秒経つと消える)
この状態で実行すると、シーン内にレイが描画されるようになります。

Physics.Raycast関数では最初にレイがヒットしたオブジェクトの情報しか取得できませんが、RaycastAll関数を使うとレイがヒットした全てのオブジェクトの情報を取得できます。
今までのRaycast処理はコメントアウトして、赤い部分のコードを追加してください。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RayTest : MonoBehaviour
{
void Start()
{
//RaycastHit hit;
//// コライダーと衝突したらtrueを返す
//if (Physics.Raycast(transform.position, Vector3.right,out hit))
//{
// // 衝突したオブジェクトを削除する
// Destroy(hit.collider.gameObject);
//}
//// レイを描画
//Debug.DrawRay(transform.position, Vector3.right * 100.0f, Color.red, 10.0f);
// レイが衝突した全てのオブジェクトを削除
RaycastHit[] hits;
hits = Physics.RaycastAll(transform.position, Vector3.right);
foreach(RaycastHit hit in hits)
{
Destroy(hit.collider.gameObject);
}
}
}
RayCastAll関数で返ってくる配列は必ずしも距離が近い順という訳ではないので注意してください。
配列のループはforeach文ではなくfor文でもOKです。

この状態で実行すると、レイがヒットした全てのオブジェクトが消えるようになります。

今まではRay(光線)の名の通り1本の線を発射して判定を取っていましたが、線だけでなく球体や箱型の判定を取ることもできます。今回は球体を発射するSphereCast関数を使ってみましょう。
RayTestスクリプトに以下のように入力してください。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RayTest : MonoBehaviour
{
void Start()
{
// 球の線形判定
RaycastHit hit;
if (Physics.SphereCast(transform.position, 1.0f, transform.right, out hit, 5.0f))
{
// 衝突したオブジェクトを削除する
Destroy(hit.collider.gameObject);
}
}
void OnDrawGizmos()
{
// 判定を描画
Gizmos.DrawWireSphere(transform.position + Vector3.right * 5.0f, 1.0f);
}
}
OnDrawGizmos関数内にギズモ(シーン内でのみ見える)を描画するコードを書いています。わかりやすくするために表示するだけなので、実際に制作で使う場合は書かなくても構いません。
基本の使い方はRaycastと変わりません。ただし第二引数は球体の半径になります。
この状態で実行してみると、球体が衝突したオブジェクトが削除されます。

レイと違って球体なので、真横から少しずれた位置にオブジェクトがあっても判定されるようになります。

箱を発射するBoxCast関数や、カプセルを発射するCapsuleCast関数も同じように使えます。行いたい処理によって使い分けましょう。
EX-2 Rayを用いて自動生成
Raycastの使用例を一つ紹介します。Raycastを使うことで起伏のある地面であっても接地した状態でオブジェクトを生成することができます。
今まで使っていたオブジェクトを削除して、地面となるCubeを作成してください。カメラも地面全体が見えるように調整してください。

Cubeを組み合わせて起伏のある地面を作ってみてください。

エネミー役のオブジェクトを作成してプレハブ化してください。Cubeでも構いませんし、適当なモデルを使用してもOKです。

空オブジェクトGameSystemを作成してください。このオブジェクトに敵を生成してもらいます。

新しいスクリプトEnemyMakerを作成して、GameSystemにアタッチしておいてください。
EnemyMakerスクリプトを開いて、以下のように入力してください。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyMaker : MonoBehaviour
{
// 生成するオブジェクト
public GameObject Enemy;
// 生成する数
public int EnemyNum = 10;
// 生成する範囲(半径)
public float Radius = 5.0f;
// レイの基点となる高さ
public float RayOriginY = 10.0f;
void Start()
{
// 敵を生成
for(int i = 0; i < EnemyNum; i++)
{
// レイの基点を決める
Vector3 rayOrigin = Random.insideUnitSphere * Radius;
rayOrigin.y = RayOriginY;
// レイを発射して衝突した場所を求める
RaycastHit hit;
if (Physics.Raycast(rayOrigin, -Vector3.up, out hit))
{
// レイの衝突点にエネミーを生成
Instantiate(Enemy, hit.point, Quaternion.identity);
}
else
{
// 地面と衝突しなかったらやり直す
i--;
}
}
}
}
【プログラムの解説】
・Random.insideUnitSphereは半径1の球体の中のランダムな1点の座標を返します。
今回は返ってきた座標のY座標だけを高くしています。そしてそれを基点として真下にレイを飛ばし、その衝突点をエネミーの生成ポイントとしています。

インスペクター内のEnemyにエネミー役のプレハブをドラッグ&ドロップしておいてください。

実行すると地面に接地した状態でエネミーが自動生成されます。パラメータを調整してエネミーの生成数や範囲を変更してみてください。

今回は単純にレイが衝突した場所に生成していますが、SphereCastなどを使うことで「エネミーを作ろうとしている場所の近くに既にエネミーがいたら生成をスキップする」といった処理もできます。レイが衝突した地面の情報を取得して「地面の種類によって生成する敵を変更する」処理を行うのも良いでしょう。
他にもRaycastを活用することで様々なゲームを作ることができます。
例えばエネミーの前方にレイを飛ばして「壁に衝突したら方向転換する」処理や「目の前に足場がなかったら追跡をやめる」処理を実装して賢い敵を作ることも可能です。

他にも「壁に隠れているオブジェクトは検知しない」といった視野の処理を実装することもできます。敵からプレイヤーに向けてレイを飛ばして、障害物にぶつかったら発見処理をしない、という流れで実装できます。

Raycastを使うことで実装できる処理の幅が広がるため、ぜひ活用してみてください。