RaycastHit 光線をとばすということ
3Dゲーム内のオブジェクトになんらかのアクションをあたえたいとき、ぼくたちがそのオブジェクトにアクセスできる方法は、画面上から直線的に「光線」のようなものをとばして判定するしかありません。
タッチパネルに慣れ親しんでしまった僕たちにとって、画面内の3D空間に触れることはそれほど珍しい事ではありません。
でも、実際に実装してみようとすると、いくつかの概念の理解が必要となります。
たとえば、タッチする画面は平面であり奥行きがありません。
3D空間上の奥にあるオブジェクトに触れたいと思っても、現実の常識であれば画面がガラス板のようになり行く手をさえぎってしまいます。
これでは、いつまでたっても僕たちが3D空間内のオブジェクトにさわることはできません。
さらに、僕たちが3D空間内に与えることができる座標の情報は、二次元のものになってしまいます。
では、どのようにしたら二次元座標をだけを使って3D空間内にアクセスできるのでしょうか?
マウスの位置を取得する Input.mousePosition
画面上の二次元座標におけるマウスの位置は、Input.mousePosition で取得できます。
それを無理やり Vector3 という3次元座標にあてはめます。
しかし、そのままだとz軸の座標が指定されていないので、適当な奥行きを指定します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MousePosition : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
Vector3 v0 = Input.mousePosition; // マウスの位置(二次元座標)を三次元を扱う変数に無理やり代入
v0.z += 3.0f; // 適当な奥行きを指定
Vector3 v1 = Camera.main.ScreenToWorldPoint(v0); /*カメラ(ここではメインカメラ)から見た画面で取得した座標であることを明確にしている。*/
transform.position = v1;
}
}
特に分かりにくいのが『Camera.main.ScreenToWorldPoint』というメソッドだと思います。
詳しくは、Camera クラスの main プロパティにあるメソッドで、引数に指定したVector3の値をメインカメラから見た3次元空間の位置に変換できます。
3D空間内のオブジェクトにアクセスする
二次元の座標を3次元座標に変換する方法は、「Camera.main.ScreenToWorldPoint」を使用することで比較的簡単に実装できました。
しかし、これだけでは3D空間内のオブジェクトになんらかのアクションを起こすことはできません。
二次元の平面である画面をクリック、もしくはタッチして3D空間内のオブジェクトにアクセスするにはもう一つの概念が必要です。
それが「Ray」、「RayCastHit」クラスと「Physics.Raycast」メソッドです。
「Ray」は直訳すると「光線」です。
つまり、平面状の画面をクリック、またはタッチしたときに、画面の奥までまっすぐにレーザー光線を射出します。
そのとき、その線上にオブジェクトがあれば、接触した場所の座標が取得できて、そのオブジェクトにアクションを起こすことができます。
下記のスクリプトはこの概念を使って、3D空間内の「Cube」を移動するとともに、その「光線」が当たっている間、マウスの左ボタンを押し続けることで「Cube」が回転し続けるように組んであります。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotate : MonoBehaviour {
// 「Cube」の回転速度をインスペクター上から設定できるようにしている。
public float x = 1.0f;
public float y = 1.0f;
public float z = 1.0f;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
// 最初に設定した速度で回転し続ける仕様
Vector3 v = new Vector3(x, y, z);
transform.Rotate(v);
bool down = Input.GetMouseButton(0); // マウスの左ボタンがクリックされているか否かの真偽値を変数「down」に格納
if (down == true) // if (down) でもよい。
{
Vector3 v0 = Input.mousePosition;
v0.z += 3.0f;
Ray ray = Camera.main.ScreenPointToRay(v0);
RaycastHit hit;
bool f = Physics.Raycast(ray, out hit, 1000);
if (f == true)
{
if (hit.collider.gameObject.name == "Cube")
{
transform.Rotate(new Vector3(10.0f, 10.0f, 10.0f));
} else
{
Vector3 v1 = Camera.main.ScreenToWorldPoint(v0);
transform.position = v1;
}
} else
{
Vector3 v1 = Camera.main.ScreenToWorldPoint(v0);
transform.position = v1;
}
}
}
}
まず、画面上の二次元座標を、z軸の座標を設定したうえで「v0」に格納しています。
次に、その変数「v0」を「Ray ray = Camera.main.ScreenPointToRay(v0);」の引数として代入しています。
これで、画面をクリック、またはタッチした二次元座標からまっすぐに射出された「光線」が実装されます。
さらに、「RaycastHit hit; bool f = Physics.Raycast(ray, out hit, 1000);」において、射出された「光線」がオブジェクトに当たったかどうかの判定をします。
それが、「bool f」の真偽値に格納されます。
「Physics.Raycast(ray, out hit, 1000);」は、3つの引数が設定されていて何だかややこしいのですが、「Physics」(物理)クラスの「Raycast」というメソッドになります。
第一引数には、「Ray ray = Camera.main.ScreenPointToRay(v0);」で設定した「ray」が指定されています。
何度も書きますが「ray」は、画面をクリック、またはタッチされた二次元座標からまっすぐにのびた「光線」を示しています。
第二引数には、「光線」が当たったオブジェクトの情報がまとめてあるそうです。
実際には、「out hit 」と書かれることがほとんどです。
厳密には、前の行の「RaycastHit hit;
」によって定義されているので、必ず書きもれのないようにしなくてはなりません。
第三引数は、「光線」をとばす距離を設定しています。
当たったオブジェクトが「Cube」であるか?
「if (hit.collider.gameObject.name == “Cube”」。
このコードもちょっと複雑ですが、要は「光線」が当たった collider(コライダー)のなかにある gameObject(ゲームオブジェクト)の name (名前)が「Cube」であるかどうかの真偽を取得しています。
このスクリプトにおいては、「Cube」の回転速度をx、y、z軸において、それぞれ「10.0f」に上げています。
クリック、またはタッチしたとき、延長線上にあるオブジェクトが「Cube」でなければ、単に「Cube」が移動するだけになっています。
コメント