BlenderとUnityで作ったものをVR(Meta Quest/Oculus Link)で動かします

VRで焚き火する #11 コントローラーからレーザーを出して薪をつかむ【Unity】

 
直接つかめたのは良いのですが、オブジェクトにかなり近づかないとつかめないので少し不便です。
今回は、コントローラーからレーザーを出して、遠くからつかんでみます。



開発環境

・Unity 2020.3.13f1(High Definition RP 10.5.0)
・グラボ:ASUS ROG-STRIX-RTX2060S-O8G-GAMING
・Meta Quest 2(Oculus Link利用)
・炎アセット:Ignis – Interactive Fire
 

(1) 右コントローラからレーザーを出す設定をする

今回の実装も、以下のサイトを参考にさせていただきました。
ありがとうございました!
 

レーザー用オブジェクトを追加する

まずは、右コントローラーの Hierarchy で右クリックして、「Create Empty」をクリックします。
私のUnityプロジェクトの場合、半透明の手オブジェクト(Hand)がありましたので、その中に作成しました。作成された空オブジェクトは、適当な名前に変更しておきます。
 
次に、レーザーを出すためのコンポーネント「Line Renderer」を追加しておきます。
線の太さや色は自由に変更できますので、今回は細めで半透明の緑の線にしてみました。
 

スクリプトに追加

先ほど追加したオブジェクトをスクリプトから呼びやすくするために、「OVRCameraRig にアタッチしているスクリプト」に以下の処理を追加します。私のUnityプロジェクトの場合、コントローラでの移動用に TransformMover.cs というスクリプトを作っていましたので、ここに追加しました。
 
[TransformMover.cs]
[SerializeField]
private Transform RightHandAnchor;      // 右コントローラ

[SerializeField]
private LineRenderer LaserPointerForGrab;     // 掴む用レーザーポインタ

[SerializeField]
private float MaxDistance = 100.0f;         // レーザーの最大距離

private Transform grabItem;       // 掴んだオブジェクト
 
2,5行目で、右コントローラとレーザーポインタを登録しておくための変数を定義してます。
8行目は、レーザーを出す最大距離。
10行目は、つかんだオブジェクトを一時的に保持する変数です。後から使います。
 

Inspectorでオブジェクトを割り当てる

最後に、右コントローラとレーザー用オブジェクトを先ほどスクリプトに追加した変数に登録します。
変数定義時に [SerializeField] を指定したことで Inspector上に項目が増えてますので、そこに追加します。
 
RightHandAnchorオブジェクトを 「Right Hand Anchor」、
RayObjectForGrabオブジェクトを「Laser Pointer For Grab」にですね。
 


(2) レーザーで物をつかむ

これで基本的な設定ができました。
ここからは、レーザーを出して物をつかむ処理を実装していきます。
 

右ハンドトリガーを押している時の処理

まずは、トリガーを押した時の実装です。
 
 
ここでは、オブジェクトとの衝突判定に使う見えない光線を「Ray」、目に見える光線を「レーザー」と記載してますよ。
 
[TransformMover.cs]
// 右ハンドトリガーを押している時
if (OVRInput.Get(OVRInput.Button.SecondaryHandTrigger, OVRInput.Controller.Touch))
{
    // コントローラからRayを出す
    Ray ray = new Ray(RightHandAnchor.position, RightHandAnchor.forward);

    // レーザーの起点
    LaserPointerForGrab.SetPosition(0, ray.origin);

    RaycastHit[] hits;
    hits = Physics.RaycastAll(RightHandAnchor.transform.position, RightHandAnchor.transform.forward, MaxDistance);

    // Rayが当たった
    if (hits.Length > 0)
    {
        foreach (var hit in hits)
        {
            // Rayが当たった所までレーザーを出す
            Vector3 hitPoint = (grabItem == null) ? hit.point : ray.origin;     // 掴んでいる時は長さ0
            LaserPointerForGrab.SetPosition(1, hitPoint);

            if (hit.collider.CompareTag("Grab") && grabItem == null)
            {
                // オブジェクトを掴む
                grabItem = hit.collider.transform;
                grabItem.parent = RightHandAnchor.transform;
                grabItem.GetComponent<Rigidbody>().useGravity = false;
                grabItem.GetComponent<Rigidbody>().isKinematic = true;
                break;
            }
        }
    }
    else
    {
        // 一定距離までレーザーを出す
        LaserPointerForGrab.SetPosition(1, ray.origin + ray.direction * MaxDistance);
    }
}
 
2行目で、右コントローラーの「ハンドトリガー」が押されたことを検知。
5行目で、コントローラから衝突検知用の Ray を出す。
8行目で、レーザーの起点を右コントローラに設定。
 
10,11行目で、コントローラから出した Ray が他のオブジェクトのコライダーに当たったかを取得。
RaycastAllメソッドの第3引数に MaxDistance(値は100.0f)を指定してますので、オブジェクトに当たらなかった場合でも Ray が出るのは一定距離までになります。そんな遠くの物は掴みませんので、100.0fくらいあれば十分でしょう。
 
14~37行目で、Ray が当たったオブジェクトの中身をチェックしています。同時に複数ヒットする可能性があるので、foreachでループします。
19行目の処理で、オブジェクトをつかんでいる時のレーザーの長さを 0(ray.origin=起点の位置)にしてます。つかんでる時のレーザーがジャマでしたので消してます。「つかんでいるか?」のチェックに使っているのは、25行目の grabItem 変数ですね。
20行目で、Ray がオブジェクトに当たった位置までレーザーを表示します。
 
22~30行目は、前回の記事とほぼ同じです。25行目の処理が増えてるだけですね。つかんだオブジェクトを grabItem 変数に代入しておくことで、ロジックが短くなるのと「オブジェクトをつかんでいるか?のフラグ代わり」として使えます。
 
36行目で、Ray がオブジェクトに当たらなかった場合、レーザーを一定距離まで表示します。
 

右ハンドトリガーを離した時の処理

次に、トリガーを離した時の実装です。
 
基本的には掴んだ時とは逆の処理になります。
 
[TransformMover.cs]
// 右ハンドトリガーを離した時
if (OVRInput.GetUp(OVRInput.Button.SecondaryHandTrigger, OVRInput.Controller.Touch))
{
    if (grabItem != null)
    {
        // オブジェクトを離す
        grabItem.parent = null;
        grabItem.GetComponent<Rigidbody>().useGravity = true;
        grabItem.GetComponent<Rigidbody>().isKinematic = false;
        grabItem = null;
    }

    // Rayを消す
    Ray ray = new Ray(RightHandAnchor.position, RightHandAnchor.forward);
    LaserPointerForGrab.SetPosition(0, ray.origin);
    LaserPointerForGrab.SetPosition(1, ray.origin);
}
 
7行目で、parent に null を代入することで、オブジェクトの親子関係を無くしています。
8,9行目で、重力と物理演算の影響をオンに戻してます。
10行目で、grabItem 変数に null を代入しておきます。
 
14~16行目で、レーザーを消してます。このタイミングでも消しておかないとレーザー表示時のゴミ?が残るようでした。
 

Unity開始時の処理

ちなみにですが、先ほどの「レーザーを消す処理」はUnityのPlay時に実行される Startメソッド 内にもあります。
 
[TransformMover.cs]
private void Start()
{
    LaserPointerForGrab.enabled = true;
    Ray ray = new Ray(RightHandAnchor.position, RightHandAnchor.forward);
    LaserPointerForGrab.SetPosition(0, ray.origin);
    LaserPointerForGrab.SetPosition(1, ray.origin);
}
 
この処理が無いと、シーン内に余分なレーザーが表示される現象が起きていました。設定内容によっては必要ないかも知れませんね。
 
これで完了です。
 

(3) 動画で確認してみる

VRモード(Oculus Link)で操作してみました。
 

レーザーでオブジェクトをつかんで離す

動画で確認してみます。
 
遠くにある薪オブジェクトをレーザーでつかんで離すことができました。
 


まとめ 

コントローラーから出したレーザーを使って、遠くにある薪オブジェクトをつかんで離してみました。
 
やはりVRモードの時はこちらの方が便利ですね。
 
次回は、コントローラのトリガーを押したタイミングで、薪オブジェクトを生成するように実装してみます。いくらでも薪が出せるので焚き火し放題です。
 
スポンサーリンク

コメント

  • トラックバックは利用できません。

  • コメント (0)

  1. この記事へのコメントはありません。

VRで焚き火する #10 薪オブジェクトを直接つかむ【Unity】

VRで焚き火する #12 薪オブジェクトを大量に生成する【Unity】


最近のコメント

だーしゅ
IT関係のお仕事してます。
3Dモデルの女の子は「ブログノ・スージー」。VRは楽しいですね。

[当ブログについて]