【Unity】FPS系ゲーム制作に使えるraycastの使い方(3D・2D)
「FPSなどのシューティングゲームで、照準はどのようにプログラムしてるんだろう」と思ったことはありませんか?
私自身Unityをしばらく触って動かしているうちに、上記のことが気になったことがあります。
その時、調べて出てきたのがraycastという機能でした。
そこで本記事では、シューティングゲーム以外にも使われている”raycast”という機能に焦点を絞って、
- raycastとはどういうものか
- raycastの使い方
- 簡易的なシチュエーションにおいてのraycastの実装
の順でご紹介いたします。
5分くらいで読めますので、ゲームを作る第一歩となるかと思いますので、ぜひご一読を!
raycastとは
raycastの”ray”とは、光線を意味する単語になります。
unityではある特定のオブジェクトから透明な光線を出し、光線がぶつかった別のオブジェクトの座標を取得する機能をraycastと呼んでいます。
座標に関しては詳細こちら→)【Unity】同次座標系をマスターしよう -「w」の正体まで徹底図解!
用途としては、FPSにおける照準やアイテム取得における注視などに使われることが多いです。
便利な機能ではありますが、発生させる光線の数を増やしすぎたり、必要以上に長くしすぎたりすると下記のようなデメリットも生じるので注意が必要です。
- ゲーム自体の動作が重くなる
- 多くのオブジェクトに対するテストが必要になる
raycastを使ってみよう
オブジェクトに設定するColliderに3D、2Dがあるように、raycastにも3D、2Dがあります。
Colliderに関する記事はこちら→【Unity】Colliderによる当たり判定を理解しよう!
関数に受け渡す値と返り値に若干の違いがありますのでそれぞれ解説します。
今回使用した環境の情報は下記になります。
- モデル:MacBook(Ratina,12inch,2017)
- OS:macOS Mojave 10.14.3
- CPU:Intel Core i7
- GPU:Intel HD Graphics 615
- Unityのバージョン:Unity 2019.1.3f1
3Dにおけるraycastの使い方
Rayの飛ばし方
raycastにおいて、Rayを飛ばすときの引数の取り方は複数用意されていますが、代表的なパターンとしては下記の2パターンがあります。
1つ目は、Rayの発射位置と方向を指定するパターン
1 2 3 |
//Physics.Raycast(発射位置、Rayの方向、衝突したオブジェクト情報、Rayの長さ) Physics.Raycast(origin, direction, out hit, Mathf.Infinity); |
2つ目は、直接Rayクラスを持った変数を設定するパターン
1 2 3 4 5 |
//メインカメラ上のマウスポインタのある位置からrayを飛ばす Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; Physics.Raycast(ray, out hit, Mathf.Infinity); |
Physics.Raycastに与えている引数に違いがみられるのがわかるかと思います。
マウスポインタのある位置のオブジェクト情報の取得
一見は百聞に如かずということで、raycastを使用してオブジェクト上にマウスポインタを置くと
オブジェクト情報を取得するものを実装してみます。
まずは、検知するオブジェクトを配置します。
Plane,Cube,Sphereを下記のように配置し、それぞれのColliderを設定します。
次にスクリプトファイルを作成し、下記のスクリプトを入力します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Raycast_Mouseover : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { //メインカメラ上のマウスポインタのある位置からrayを飛ばす Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit, Mathf.Infinity)) { //Rayが当たったオブジェクトの名前と位置情報をログに表示する Debug.Log(hit.collider.gameObject.name); Debug.Log(hit.collider.gameObject.transform.position); } } } |
空のオブジェクトを生成し、先ほどのスクリプトファイルをアタッチし、実際に動作させると
下記のようにマウスポインタが載っているオブジェクトの情報がコンソール画面に出力されます。
レイヤーと取得範囲の制限
先ほどのスクリプトでは、Rayによる検出から特定のオブジェクトを外すことができません。
そこで必要となってくるのが、レイヤーとレイヤーマスクになります。
インスペクターウィンドウにある下記の画像の部分でレイヤーは設定していきます。
単純な除外であればレイヤードロップダウンメニューからIgnore Raycastを選択すれば簡単に設定はできます。
しかし、Ignore Raycastは簡単に設定できる反面、色々な変更に対応できなくなります。
そこでレイヤーマスクを利用し、色々な変更に対応できる検出を設定していきます。
まず、画面上にある検出の対象とするCubeとSphereオブジェクトを、それぞれレイヤーを分けてみます。
インスペクターウィンドウのLayer→Add Layerを選択し、User Layer 8、9にそれぞれcube/sphereを定義します。
定義後、それぞれのオブジェクトのレイヤードロップダウンメニューから対応したものを選択しておきます。
先ほどのスクリプトをCubeとSphereのみを検出するように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Raycast_Mouseover_layer : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { //メインカメラ上のマウスポインタのある位置からrayを飛ばす Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; //ビット演算によりCubeとSphereのみをRayで検出するようにする int layerMask = 1 << 8 | 1 << 9; float maxDistance = 10; if (Physics.Raycast(ray, out hit, maxDistance, layerMask)) { //Rayが当たったオブジェクトの名前と位置情報をログに表示する Debug.Log(hit.collider.gameObject.name); Debug.Log(hit.collider.gameObject.transform.position); } } } |
実際に動作させてみると下記のようになります。
最初のスクリプトと違いPlaneオブジェクトを検出せずに、CubeとSphereオブジェクトのみを検出しているのがわかると思います。
重なった複数のオブジェクトの検出方法
次に重なった複数のオブジェクトはどうでしょうか。
下記のようにCubeオブジェクトを設置してみます。
先ほどのスクリプトでも重なったオブジェクトは検出をしないので別の方法を取る必要があります。
そこで利用するのが、Rayの当たった全てのオブジェクトを検出するPhysics.RaycastAllになります。
スクリプトは下記のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class RaycastAll_mouse : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); //Rayが衝突した全てのオブジェクトの名前をログに表示 foreach(RaycastHit hit in Physics.RaycastAll(ray)) { Debug.Log(hit.collider.gameObject.name); } } } |
実際に動作させてみると下記のようになります。
Physics.RaycastAllにより、Cubeオブジェクトに重なっているCube2の名前が検出されているのがわかります。
raycastを可視化してみる
デバッグ時にちゃんとRayが想定したように出ているか確認したいこともあるかと思います。
その時に使用するのがDrawRayになります。
CubeとSphereのみを検出するスクリプトに追記をします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Raycast_Mouseover_layer : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { //メインカメラ上のマウスポインタのある位置からRayを飛ばす Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; //ビット演算によりCubeとSphereのみをRayで検出するようにする int layerMask = 1 << 8 | 1 << 9; float maxDistance = 10; if (Physics.Raycast(ray, out hit, maxDistance, layerMask)) { //Rayが当たったオブジェクトの名前と位置情報をログに表示する Debug.Log(hit.collider.gameObject.name); //Debug.Log(hit.collider.gameObject.transform.position); } //Rayを緑色の線で可視化する Debug.DrawRay(ray.origin, ray.direction * maxDistance, Color.green, 5, false); } } |
実行してみると下記のようになります。
Sceneビューをみるとスクリプトで指定した通り緑の線が出ていることがわかります。
2Dにおけるraycast
マウスポインタのある位置のオブジェクト情報の取得
それでは2Dにおけるraycastもみていきましょう。
2DではPhysics2D.Raycastを使用します。
初期配置として今回は、アセットストアから「Simple Avatar icon」を使用します。
スクリプトは下記になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Raycast2D : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { //メインカメラ上のマウスポインタのある位置からRayを飛ばす Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); int layerMask = 1; float maxDistance = 10; //ヒット判定にPysics2D.Raycastを使用 RaycastHit2D hit = Physics2D.Raycast((Vector2)ray.origin, (Vector2)ray.direction, maxDistance, layerMask); if (hit.collider) { Debug.Log(hit.collider.gameObject.name); } } } |
実行結果は下記のようになります。
3Dのときと同様に検出が行われていることがわかります。
2Dと3Dとの違いは下記になります。
- Rayを開始位置と方向のベクトルの代わりに引数として設定できない
- 返り値が3Dではbool値だが、2DではRaycastHit2Dが返り値となっている
- Rayの開始位置のオブジェクトも検出の対象となる
raycastを可視化してみる
2DにおいてもRayの可視化は可能です。
3Dの時のようにカメラからRayを飛ばしても2Dでは見えないのでElf_2のアイコンからElf_1のアイコンに向かってRayを飛ばしてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Raycast2D_RayDraw : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { //オブジェクトから右側にRayを伸ばす Ray2D ray = new Ray2D(transform.position, transform.right); int layerMask = 1; int raydistance = 10; RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction, 10f, layerMask); if (hit.collider) { Debug.DrawRay(ray.origin, ray.direction * raydistance, Color.green); Debug.Log(hit.collider.gameObject.name); } } } |
実際に動作させてみると下記のようになります。
Elf_2から出ているRayからElf_1をずらすと検出されていないのがわかると思います。
raycastを使用したゲームの実装
今回はraycastを使用して簡易的な的当てゲームを実装してみます。
アセットとして「Military target」をアセットストアからインストールします。
まずは、Plane,SphereオブジェクトとMilitary targetを下記のように配置します。
新規レイヤーとしてtargetを8番目に作成し、targetのオブジェクトに適用します。
飛ばすボールとしてのSphereオブジェクトに、RigidbodyとSphere Colliderを設定します。
Rigidbodyについてはこちら→)【Unity】Rigidbodyを使用して重力・空気抵抗を発生させる方法
各種設定が済んだらSphereオブジェクトに対し、ボールの挙動を設定するスクリプトを適用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class bombshooter : MonoBehaviour { public void Shoot(Vector3 dir) { GetComponent<Rigidbody>().AddForce(dir); } private void OnCollisionEnter(Collision other) { //的に当たったらボールに加わる力を無効にする GetComponent<Rigidbody>().isKinematic = true; } // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } } |
このスクリプトでは、ボールに対し飛ばすための力を与えることと的に衝突したらそのまま的に張り付くという動作を記述しています。
スクリプトができたら適用したSphereオブジェクトをヒエラルキーウィンドウからプロジェクトウィンドウにドラッグ&ドロップしてPrefabにします。
Prefabの使い方はこちら→)【Unity】 Prefabを使ったオブジェクト生成・複製・削除等の方法
このPrefabを利用して同じ挙動をするボールを複製するスクリプトを記述していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class BombGenerator : MonoBehaviour { // Start is called before the first frame update void Start() { } public GameObject BombPrefab; // Update is called once per frame void Update() { if (Input.GetMouseButtonDown(0)) { //Prefabからオブジェクト生成 GameObject Bomb = Instantiate(BombPrefab) as GameObject; //Rayを使用してマウスでクリックした方向へボールを飛ばす Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; int layerMask = 1 << 8; Vector3 worldDir = ray.direction; Bomb.GetComponent<bombshooter>().Shoot(worldDir.normalized * 2000); //的に当たる方向になっているかチェック if(Physics.Raycast(ray,out hit,Mathf.Infinity,layerMask)) { Debug.Log(hit.collider.gameObject.name); } } } } |
上記が飛んでいくSphereオブジェクトを生成するスクリプトになります。
Rayを使用して画面上でクリックした方向へSphereオブジェクトが飛ぶように設定しています。
また、デバッグ用に的に当たっているかの情報を取得するためにraycastを利用しています。
スクリプトが書けたら空のゲームオブジェクトを作りアタッチします。
実際に動作をさせると下記のようになります。
ボールが飛んでいく方向に的があった場合、コンソールに情報が取得できているのがわかると思います。
まとめ
いかがでしたでしょうか。
今回はraycastについて解説していきました。
FPSや脱出ゲームなどを作成する時などには必要な要素になりますので、実際に色々試しながら実装をしてみてください。
この記事が、Unityを学んでいる人の参考になれば幸いです!
合わせて読みたい関連記事)
この記事はいかがでしたか?
もし「参考になった」「面白かった」という場合は、応援シェアお願いします!