今回はオキュラスクエストの使い方を習得するための練習を行っていきましょう。
コントローラを表示させて、コントローラで弾丸を撃つ、物体をつかむ、レイを出す処理を作成していきます。
前回の記事:
コントローラの表示
前回作成したプロジェクトのままでは、VR用のカメラは配置されていますがコントローラは配置されていないため、HMD上でコントローラが見えない状態になっています。
そのため、両手2つのコントローラを表示させます。
ではまず、Oculus→VR→Prefabs→OVRControllerPrefabがあることを確認してください。こちらがコントローラのゲームオブジェクトです。
このコントローラオブジェクトをOVRCameraRig→TrackingSpace→LeftHandAnchorとRightHandAnchorにドラッグ&ドロップしてください。
【注意】コントローラは最初右手で設定されているため、LeftControllerAnchorにドラッグ&ドロップしたOVRControllerPrefabのOVRControllerHelper→ControllerをL Tracked Remoteに設定してください。
この状態でアプリを実行すると、コントローラが表示されるようになります。
コントローラから球を出す
ここからはコントローラのボタンを用いた様々な操作の作成方法について解説していきます。
まずは、コントローラから球を出す処理を作成します。そのため、コントローラの入力処理を行うための空のゲームオブジェクトを作成していきましょう。
空のゲームオブジェクトの名前はInputManagerに設定してください(ここは自由な名前でも問題ありません)。
次に、Assetsフォルダ内にScriptsフォルダを作成してください。
そして、Scriptsフォルダ内にInputManagerという名前のスクリプトを作成してください。
作成したInputManager.csをゲームオブジェクトInputManagerにドラッグ&ドロップしてください。
それでは、InputManager.csのコードを書いていきましょう。以下のコードを書いてみてください。
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 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class InputManager : MonoBehaviour { [SerializeField] GameObject rightController; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { if(OVRInput.GetDown(OVRInput.RawButton.A)) { //球を生成(「ヒエラルキーで右クリック→Create→3D Object→Sphere」と同じ作業です) GameObject go = Instantiate(GameObject.CreatePrimitive(PrimitiveType.Sphere)); //生成した球の位置を右手コントローラの位置に変更 go.transform.position = rightController.transform.position; //生成した球のサイズを半径0.1mに変更 go.transform.localScale *= 0.1f; } } } |
まず、OVRInputクラスはOpenVRと呼ばれるVR向けの処理をまとめたクラスです。OVRInput.GetDown関数は、引数のボタンが入力されたフレームかどうかを検知する関数です。引数にはOVRInput.RawButton.Aと書かれていますが、これはOculus TouchコントローラのAボタンのことを指します。そのため、このif分岐はAボタンが押されたら実行する処理であることが分かります。
次に、飛ばす球を作成していくのですが、今回はヒエラルキーから作成するのではなく関数から作成しています。それは、GameObject.CreatePrimitive関数です。これは、引数の形状のゲームオブジェクトを作成する関数で、キューブ、球、カプセル、平面などのCreate GameObjectから作成できるゲームオブジェクトを作成することができます。
GameObject.CreatePrimitive関数の返り値を変数goに代入させ、次の行で右手コントローラの位置に設定します。最後に、球の半径が初期値では1mであり大きすぎるため、go.transform.localScaleを変えて半径を0.1mに変更させています。
では、Unityに戻って、InputManagerのインスペクタのRight Controllerに右手コントローラのゲームオブジェクトであるOVRControllerPrefabをドラッグ&ドロップしてください。
この状態で実行してみましょう。右手コントローラの位置に球が生成される様子が確認できると思います。
しかし、まだ物理演算がないため物体が自由落下しません。
そのため、生成した球にRigidbodyコンポーネントを追加しましょう。通常ではインスペクタ―から追加するのですが、スクリプト上で生成したゲームオブジェクトの場合インスペクタ―から追加することができないため、スクリプト上で追加します。
コンポーネントの追加にはAddComponent関数を使用します。
Update関数に以下のコードを書いてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//玉を出す処理 if(OVRInput.GetDown(OVRInput.RawButton.A)) { //球を生成(「ヒエラルキーで右クリック→Create→3D Object→Sphere」と同じ作業です) GameObject go = Instantiate(GameObject.CreatePrimitive(PrimitiveType.Sphere)); //生成した球の位置を右手コントローラの位置に変更 go.transform.position = rightController.transform.position; //生成した球のサイズを半径0.1mに変更 go.transform.localScale *= 0.1f; //物理演算用のコンポーネントを追加 var rb = go.AddComponent<Rigidbody>(); } |
すると、次のように生成した球が自由落下していくようになります。
そして、最後に、コントローラの方向に初速度を追加する処理を加えます。今回は以下のコードで10m先へ一気に飛ばす初速度を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//玉を出す処理 if(OVRInput.GetDown(OVRInput.RawButton.A)) { //球を生成(「ヒエラルキーで右クリック→Create→3D Object→Sphere」と同じ作業です) GameObject go = Instantiate(GameObject.CreatePrimitive(PrimitiveType.Sphere)); //生成した球の位置を右手コントローラの位置に変更 go.transform.position = rightController.transform.position; //生成した球のサイズを半径0.1mに変更 go.transform.localScale *= 0.1f; //物理演算用のコンポーネントを追加 var rb = go.AddComponent<Rigidbody>(); rb.AddForce(rightController.transform.forward*10.0f,ForceMode.Impulse); } |
ちなみに、right.Controller.transform.forwardの向きは下の図のようなイメージです。
この状態で実行してみましょう。次のようにボタンから球が発射できるようになれば無事に球を出す処理の完成です。
コントローラで物体をつかむ
それでは次に、コントローラで物体をつかむ処理を作成していきましょう。
まずは、つかむ用のキューブを作成していきます。ヒエラルキーから右クリック→Create GameObject→3D Object→Cubeでキューブを作成して、Transformを下のように設定してください。
この状態で実行したとき、コントローラの届く位置にキューブが配置されていればOKです。
つかむ処理には衝突判定を使用するため、ゲームオブジェクトの識別用にCubeのTagを設定していきます。CubeのインスペクタからTag→Add Tagを選択してください。
Tags&LayersのTagsの+ボタンを押してCubeという名前でタグを作成してください。
作成し終えたら、Cubeのインスペクタに戻り、Tag→Cubeを設定してください。これでCubeのタグの設定が完了です。
では、InputManager.csのコードにつかむ処理を実装していきましょう。
以下のコードを書いてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//球を出す処理 // 省略 //つかむ処理 if(OVRInput.GetDown(OVRInput.RawButton.B)) { RaycastHit[] hits; hits = Physics.SphereCastAll(rightController.transform.position,0.01f,Vector3.forward); foreach(var hit in hits) { if(hit.collider.tag=="Cube") { hit.collider.transform.parent = rightController.transform; break; } } } |
このコードでは、Bボタンを押したときCubeが近くにあれば右コントローラの子オブジェクトに設定するといった処理を実行しています。Physics.SphereCastAll関数は、第一引数の位置を中心に第二引数の半径の球体に衝突するゲームオブジェクトを取得する関数です。第三引数はその球をどこに飛ばすかという設定になりますが、今回は必要な部分ではないので正面方向に設定しています。
そして、ヒットしたゲームオブジェクト群をforeach文でループさせています。この中で、タグがCubeのゲームオブジェクトであれば、右コントローラの子オブジェクトに設定するといった処理を加えています。
【注意】実際には、子オブジェクトのparentを右コントローラに設定する処理をコードに記述していることに注意してください。
コントローラの子オブジェクトに加えることにより、右手コントローラの位置や方向を変えたときCubeも同期して動くようになります。
この状態で実行してみましょう。すると下の図のようにつかむ処理ができると思います。
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 |
//物体をつかむ処理 if(OVRInput.GetDown(OVRInput.RawButton.B)) { RaycastHit[] hits; hits = Physics.SphereCastAll(rightController.transform.position,0.01f,Vector3.forward); foreach(var hit in hits) { if(hit.collider.tag=="Cube") { hit.collider.transform.parent = rightController.transform; break; } } } //物体を離す処理 if(OVRInput.GetUp(OVRInput.RawButton.B)) { for(int i=0;i<rightController.transform.childCount;i++) { var child = rightController.transform.GetChild(i); if(child.tag=="Cube") { child.parent = null; } } } |
離す処理は、Cubeタグのついた子オブジェクトを切り離すだけでできるため簡単に実装できます。こちらもつかむ処理と同じように、実際にはCubeのparentをnullにする処理となるため、子オブジェクトの親を指定しないといった処理を記述しています。
この状態で実行すると、ボタンを離すと物体を離すことができることが確認できます。
コントローラからレイを出して物体をつかむ
次に、空いている左手のコントローラからレイを出す処理を作成していきます。
まず、レイ用のゲームオブジェクトを作成します。空のゲームオブジェクトを左手コントローラの子オブジェクトとして作成してください。名前はRayObjectに変更してください。
そして、Add Componentから線を描画するためのLine Rendererを追加しておいてください。
それではコードを書いていきます。InputManager.csに以下の変数を追加してください。
1 2 3 4 5 |
[SerializeField] GameObject leftController; [SerializeField] LineRenderer rayObject; |
そして、これらを割り当てるため、インスペクタでleftControllerとrayObjectを以下のように割り当ててください。
設定し終えたら、コードの説明に戻ります。
まずは、レイを出す処理を作成していきます。以下のコードを書いてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//球を出す処理 // 省略 //物体をつかむ処理 // 省略 //物体を離す処理 // 省略 //レイを出す処理 rayObject.SetVertexCount(2); //頂点数を2個に設定(始点と終点) rayObject.SetPosition(0,leftController.transform.position); // 0番目の頂点を左手コントローラの位置に設定 rayObject.SetPosition(1,leftController.transform.position+leftController.transform.forward*100.0f); // 1番目の頂点を左手コントローラの位置から100m先に設定 rayObject.SetWidth(0.01f,0.01f); //線の太さを0.01mに設定 |
この処理では、レイの線の長さや太さを設定しています。
※ ここで、LineRenderer.SetVertexCount(int)と’LineRenderer.SetWidth(float, float)が古い書き方のため警告が表示されますが、現状は問題なく動作します。
この状態で実行すると、次のようになります。
それでは次に、レイにあたった物体をつかむ処理を作成していきます。下のコードを記述してください。
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 |
//レイを出す処理 rayObject.SetVertexCount(2); rayObject.SetPosition(0,leftController.transform.position); rayObject.SetPosition(1,leftController.transform.position+leftController.transform.forward*100.0f); rayObject.SetWidth(0.01f,0.01f); //レイに当たった物体をつかむ処理 if(OVRInput.GetDown(OVRInput.RawButton.Y)) { RaycastHit[] hits; hits = Physics.RaycastAll(leftController.transform.position,leftController.transform.forward,100.0f); foreach(var hit in hits) { if(hit.collider.tag=="Cube") { hit.collider.transform.parent = leftController.transform; break; } } } //物体を離す処理 if(OVRInput.GetUp(OVRInput.RawButton.Y)) { for(int i=0;i<leftController.transform.childCount;i++) { var child = leftController.transform.GetChild(i); if(child.tag=="Cube") { child.parent = null; } } } |
このコードは、前述した物体をつかむ処理をコピー&ペーストして少し改良したものとなります。Yボタンを押したとき、レイにあたったタグがCubeの物体を子オブジェクトに設定する処理となっています。Yボタンを離したときの処理はrightControllerからleftControllerに変えただけです。
この状態で実行してみましょう。すると、レイに当たった物体をつかんで移動させることができることが分かります。
最後に
今回紹介した3つの操作は、すべて独立して実装しているため、組み合わせて簡単なゲームを実装することができます。この記事を参考にしながら、ぜひVR開発を始めてみてください。
次回の記事:
コメント