2017-11-08

【HTC VIVE】 VR開発チュートリアル ~ものをつかんで投げ、テレポートできるようにする~

日本にHTC VIVEのチュートリアルが足りていないと感じたため、海外のものを翻訳しています。今回は、HTC Vive Tutorial for Unity(By Eric Van de Kerckhove @BlackDragonBE)を翻訳してみたいと思います。ものをつかんで投げたり、テレポートできるようになります。

(Since Japan has few tutorials on VR development, I translated tutorials abroad, "HTC Vive Tutorial for Unity" written by Eric Van de Kerckhove @BlackDragonBE)


完成するとこんな感じになります!






このチュートリアルについて


HTC Viveは、HTCとValve Corporationが開発したバーチャルリアリティヘッドセットです。画面上のアバターではなく、仮想世界に足を踏み入れて体験することができます。 あなたがユニティデベロッパーであれば、HTC Viveを使ったバーチャルリアリティゲームを自分のゲームにするのは簡単です。

このHTC Viveチュートリアルでは、あなた自身のUnityゲームにHTC Viveを統合する方法を学びます。具体的には、次の方法を学習します。


  • SteamVRをダウンロードして設定する
  • コントローラの入力を処理する
  • VRの物理オブジェクトと対話する
  • レーザーポインターを作る
  • エリア周辺でテレポートする

このHTC Viveチュートリアルの終わりには、さらなる実験の準備ができている素敵な小さなサンドボックスがあります。始めましょう!



(筆者追記:また、途中でC#のスクリプトが表示されますが、この記事の最後に完成したスクリプトを貼っておきます。



事前準備


このチュートリアルを熟読する前に、次のものがあることを確認してください。

  • VR対応のWindows PC。
  • Unity 5.5~
  • HTC Vive
  • Steamのインストール
  • SteamVRのインストール
  • Unityの基礎知識
  • スクリプトの基本的な理解
  • Visual Studioのインストール

はじめに


  • HTC Viveの電源がオンで接続されていることを確認してください!
  • Unity上で「Tutorial」というプロジェクトを作成します
  • 以下のリンクから、Starter Projectをダウンロードしてください。Desktopフォルダなど、適当な場所に保存してください。

  • Unity内でダウンロードした、以下のフォルダを開きます。(どうやって?)
    • C:\Users\User\Desktop\Introduction To HTC Vive - Starter\Assets



  • エクスプローラーでC:\workspace\Unity\Tutorial2\Assetフォルダ内に、Assets内のすべてのファイルをコピーします(作者追記)

各フォルダは、特定のアセットのホームベースとして機能します。

  1. Assets:青い弾み球を含むシーンに使用される材料
  2. Models:すべてのモデル
  3. Physics Materials:弾力のあるボール物理物質
  4. Prefabs:オブジェクトプリファブが緩い
  5. Scene:ゲームシーンはここにあります
  6. Script:すべてのスクリプト
  7. Texture:シーン内のすべてのオブジェクトによって共有される単一のテクスチャ

  • Scene > Gameを押して、シーンビューを見て、再生ボタンを押して "ゲーム"を試してください

現時点では、シーンにはまだVR Rigがないので、部屋の中を見れません。 ViveをUnityに接続するには、SteamVRをプロジェクトに追加する必要があります。


SteamVRを設定する前に、

  • 「Hierarchy」で「Level」を展開し、「Floor」を選択します。
  • そのレイヤーを「CanTeleport」を作成し、設定します。

SteamVRをセットアップする


SteamVR SDKはValveが作成した公式ライブラリで、Viveの開発を容易にします。現在、Asset Storeでは無料で、Oculus RiftとHTC Viveをサポートしています。

  • 上部のバーで[Window]> [Asset Store]を選択してAsset Storeを開きます。


  • ストアがロードされたら、最上部の検索フィールドに「SteamVR」と入力し、Enterキーを押します。
  • 少し下にスクロールして、選択したアセットを表示します。 SteamVR Pluginをクリックしてストアページを開きます


  • ダウンロードボタンをクリックしてください。
  • 完了したら、パッケージインポートダイアログが表示されます。パッケージをインポートするには、右下の[Import]をクリックします。


インポートの最後に、次のメッセージが表示されることがあります。




  • [バックアップを作成しました]というボタンをクリックします。数秒後にこのウィンドウが表示されます




これはSteamVRプラグインの一部です。パフォーマンスと互換性を最大限に高めるためにどのエディタ設定を改善できるかを示します。

新しくプロジェクトを開いてSteamVRをインポートすると、かなりの数のエントリが表示されます。スタータープロジェクトはすでに最適化されているため、ここでは解像度ダイアログを無効にすることをお勧めします。

  • [すべて受け入れる]ボタンをクリックすると、すべての推奨変更が実行されます。
  • Asset Storeを閉じ、シーンビューに戻ります。プロジェクトウィンドウにSteamVRという名前の新しいフォルダが作成されます。(どこに?)

  • フォルダを開き、内部のフォルダを確認します。 PrefabsフォルダのVR GameObjectsをシーンに追加します。(どこ?)
  • [CameraRig]と[SteamVR]の両方を選択し、Hierarchyウィンドウにドラッグします:


[SteamVR]はいくつかのことを処理します。プレイヤーがシステムメニューを開いて物理更新レートをレンダリングシステムのレートと同期させると、自動的にゲームが一時停止されます。また、ルームスケールのVRムーブメントのスムージングを処理します。


SteamVR Inspectorパネルでプロパティを確認します。



[CameraRig]は、Viveのヘッドセットとコントローラーを制御するので、もっと面白いです。
  •  [CameraRig]を選択し、InspectorパネルでPositionを(X:0、Y:0、Z:-1.1)に設定して、テーブルのすぐ後ろにあるリグ全体をスライドさせます。


  • Main Cameraを階層から削除します
[CameraRig]とその埋め込みカメラに干渉します。
  • コントローラーの電源を入れ、シーンを開始します。両方のコントローラーを持ち、少し振ってください。シーンビューで仮想コントローラが振る舞っているのが実際にわかります。




SteamVRプラグインがコントローラを検出すると、これらの仮想バージョンが作成されます。コントローラは[CameraRig]の2つのコントローラの子にマップされます:




  • 今度はシーンを実行しながら、HierarchyのCamera(eye)を選択して、トップストラップでヘッドマウントディスプレイを慎重にピックアップしてください。移動して少し回転させて、シーンビューを見ます





カメラはヘッドマウントディスプレイにリンクされ、すべての動きを正確に追跡します。

  • Camera(eye)が選択された状態で、Steam VR_Update Posesコンポーネントを追加します。(これ、選べなかった。Scene実行中ならできたけど・・・)

これはUnity 5.6でコントローラが追跡されないバグを修正します。
ヘッドマウントディスプレイを頭に置き、コントローラーをつかんで見て少し歩いて部屋の雰囲気を感じてください。オブジェクトとのやりとりをすると、あなたは失望するでしょう - 何も起こりません。動きの追跡以外の機能を追加するには、スクリプトを実行する必要があります。


コントローラーからのインプットを扱う



あなたの手のコントローラーの1つを持って、それを適切な外観にしてください。各コントローラには以下の入力があります。




タッチパッドはボタンとアナログ "ジョイスティック"として機能します。コントローラーには、移動と回転の速度と回転速度もあります。これは、物理オブジェクトとやりとりするときに特に便利です。


さっそくコードを書いてみましょう!

  •  Scriptsフォルダに新しいC#スクリプトを作成し、ViveControllerInputTestという名前を付けます。
  • ダブルクリックし、好きなコードエディタで開きます。(Visual Studio?)
  • Start()メソッドを削除し、Update()メソッドの上に次のように追加します。
ViveControllerInputTest.cs

// 1
private SteamVR_TrackedObject trackedObj;
// 2
private SteamVR_Controller.Device Controller
{
    get { return SteamVR_Controller.Input((int)trackedObj.index); }
}


ここで、書いたコードの意味ですが、
  1. 追跡中のオブジェクトを参照します。この場合、コントローラです。
  2. コントローラに簡単にアクセスできるようにするDeviceプロパティ。トラッキングされたオブジェクトのインデックスを使用してコントローラの入力を返します。

ヘッドマウントディスプレイとコントローラーの両方が追跡されたオブジェクトです。現実世界での動きと回転は、HTC Viveベースステーションによって追跡され、仮想世界に送信されます。


  • Update()のすぐ上に次のメソッドを追加します。

ViveControllerInputTest.cs

void Awake()
{
    trackedObj = GetComponent<SteamVR_TrackedObject>();
}

このスクリプトがロードされると、trackedObjはコントローラに接続されているSteamVR_TrackedObjectコンポーネントへの参照を取得します。



これでコントローラへのアクセスが可能になり、入力を簡単に読み取ることができます。 

  • Update()メソッドの内部に以下を追加します。
ViveControllerInputTest.cs

// 1
if (Controller.GetAxis() != Vector2.zero)
{
    Debug.Log(gameObject.name + Controller.GetAxis());
}

// 2
if (Controller.GetHairTriggerDown())
{
    Debug.Log(gameObject.name + " Trigger Press");
}

// 3
if (Controller.GetHairTriggerUp())
{
    Debug.Log(gameObject.name + " Trigger Release");
}

// 4
if (Controller.GetPressDown(SteamVR_Controller.ButtonMask.Grip))
{
    Debug.Log(gameObject.name + " Grip Press");
}

// 5
if (Controller.GetPressUp(SteamVR_Controller.ButtonMask.Grip))
{
    Debug.Log(gameObject.name + " Grip Release");
}

上記のコードは、あなたがVRにいる間にプレイヤーの入力にアクセスするほとんどの方法をカバーしています。コンソールにGameObjectの名前を書き込むことで、左右のコントローラを簡単に区別することができます。ここでは、セクションごとの内訳を示します。

  1. タッチパッド上にあるときの指の位置を取得してコンソールに書き込みます。
  2. ヘアトリガーを絞ると、この行がコンソールに書き込まれます。ヘアトリガには、押されているかどうかをチェックする特別なメソッドがあります:
    1. GetHairTrigger()
    2. GetHairTriggerDown()
    3. GetHairTriggerUp()
  3. ヘアトリガーを解放すると、このif文がコンソールに書き込まれます。
  4. グリップボタンを押すと、このセクションはコンソールに書き込みます。 GetPressDown()メソッドを使用するのは、ボタンが押されたかどうかを確認する標準的なメソッドです。
  5. グリップボタンの1つを離すと、そのアクションがコンソールに書き込まれます。 GetPressUp()メソッドを使用するのは、ボタンがリリースされたかどうかを確認する標準的な方法です。

ここまでのコードをまとめえると、こうなります。


ViveControllerInputTest.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ViveControllerInputTest : MonoBehaviour {

    private SteamVR_TrackedObject trackedObj;
    private SteamVR_Controller.Device Controller
    {
        get { return SteamVR_Controller.Input((int)trackedObj.index); }
    }

    void Awake()
    {
        trackedObj = GetComponent<SteamVR_TrackedObject>();
    }

    void Update () {

        if (Controller.GetAxis() != Vector2.zero)
        {
            Debug.Log(gameObject.name + Controller.GetAxis());
        }

        if (Controller.GetHairTriggerDown())
        {
            Debug.Log(gameObject.name + " Trigger Press");
        }

        if (Controller.GetHairTriggerUp())
        {
            Debug.Log(gameObject.name + " Trigger Release");
        }

        if (Controller.GetPressDown(SteamVR_Controller.ButtonMask.Grip))
        {
            Debug.Log(gameObject.name + " Grip Press");
        }

        if (Controller.GetPressUp(SteamVR_Controller.ButtonMask.Grip))
        {
            Debug.Log(gameObject.name + " Grip Release");
        }
    }
}


これで、スクリプトのテスト準備が整いました。

  • ViveControllerInputTest.csを保存し、Unityエディタに戻ります。(どれ?)
  • Hierarchyの両方のコントローラを選択し、InspectorにドラッグしてViveControllerInputTestコンポーネントを追加します。



  • ゲームをもう一度実行し、両方のコントローラーを手に持って、画面の下部にあるコンソールラインを見ます



ボタンを押し、トリガーを絞ってタッチパッド上を移動します。各アクションが登録されているため、コンソールがビジー状態です。


これがが基本入力設定です。指先で仮想世界を操ることができます。


コントローラを使用してPhysicsオブジェクトを扱う


VRを使用すると、オブジェクトをピックアップして調べ、後でクリーンアップすることなく周囲に投げ込むなど、「現実の世界」では不可能な多くの機会がユーザに与えられます。HTC Viveを使用すると、いくつかのトリガーコライダーを採用し、少しのスクリプトを作成することで、気楽な仮想体験を作成できます。


  • Hierarchyの両方のコントローラを選択し、Rigidbodyコンポーネントを追加します。 (Add Component>Physics>Rigidbody)
  • 「Kinematic」チェックボックスをオンにして、「Use Gravity」をオフにします


  • 両方のコントローラにBox Colliderを追加し(Add Component>Physics> Box Collider)Is Triggerをチェックします。
デフォルトのコライダーは巨大なので、サイズを変更して再配置する必要があります。

  • センターを(X:0、Y:-0.04、Z:0.02)に変更します
  • Sizeを(X:0.14、Y:0.07、Z:0.05)に設定します。この場合、ユニットの100分の1であっても、衝突者がどこで終わるかが影響を受けるため、この種の正確な値が必要です。

  • もう一度ゲームを実行してください。
  • Hierarchy内のコントローラを選択し、実際のコントローラを選択します。
  • シーンビューを見て、保持しているコントローラにフォーカスを合わせます(Fキーを押します)。コライダーは、コントローラーの上部を右に移動します。これは、オブジェクトをピックアップするために使用する部分です。

スクリプトがなければ、このコライダーは役に立たないキューブです。

  • Scriptsフォルダに新しいC#スクリプトを作成し、ControllerGrabObjectという名前を付けて開きます。
  • Start()メソッドを削除し、この使い慣れたコードをその場所に追加します。

ControllerGrabObject.cs

private SteamVR_TrackedObject trackedObj;

private SteamVR_Controller.Device Controller
{
    get { return SteamVR_Controller.Input((int)trackedObj.index); }
}

void Awake()
{
    trackedObj = GetComponent<SteamVR_TrackedObject>();
}
}


これは、入力テストスクリプトで使用したのとまったく同じコードです。コントローラーを取得し、後で使用するための参照を保管します。

  • 以下の変数をtrackedObjの直下に追加します。

ControllerGrabObject.cs

// 1
private GameObject collidingObject; 
// 2
private GameObject objectInHand;


各変数には目的があります:
  1. トリガーが現在衝突しているGameObjectを格納するので、オブジェクトをつかむことができます。
  2. プレーヤーが現在つかんでいるGameObjectへの参照として機能します。

  • これをAwake()メソッドの下に追加します:

ControllerGrabObject.cs

private void SetCollidingObject(Collider col)
{
    // 1
    if (collidingObject || !col.GetComponent<Rigidbody>())
    {
        return;
    }
    // 2
    collidingObject = col.gameObject;
}



このメソッドは、コライダーをパラメーターとして受け取り、そのGameObjectを取得して解放するためのcollidingObjectとして使用します。さらに、

  1. プレイヤーがすでに何かを保持している場合やオブジェクトに剛体がない場合、GameObjectを潜在的なグラブターゲットにしません。
  2. オブジェクトを潜在的なグラブターゲットとして割り当てます。

  • これらのトリガーメソッドを追加します:
ControllerGrabObject.cs

// 1
public void OnTriggerEnter(Collider other)
{
    SetCollidingObject(other);
}

// 2
public void OnTriggerStay(Collider other)
{
    SetCollidingObject(other);
}

// 3
public void OnTriggerExit(Collider other)
{
    if (!collidingObject)
    {
        return;
    }

    collidingObject = null;
}


これらのメソッドは、トリガーコライダーが別のコライダーに入ったり出たりするときに何が起こるべきかを処理します。

  1. トリガー・コライダーが別のトリガー・コライダーに入ると、これは他のコライダーを潜在的なグラブ・ターゲットとして設定します。
  2. セクション1(// 1)と似ていますが、オブジェクトがコントローラをしばらく保持しているときにターゲットが確実に設定されるため、異なります。これがなければ、衝突は失敗するかバグになる可能性があります。
  3. コライダーがオブジェクトを終了し、未グラブのターゲットを放棄すると、このコードはそのターゲットをnullに設定して削除します。
  • 次に、オブジェクトを取得するためのコードを追加します。
ControllerGrabObject.cs

private void GrabObject()
{
    // 1
    objectInHand = collidingObject;
    collidingObject = null;
    // 2
    var joint = AddFixedJoint();
    joint.connectedBody = objectInHand.GetComponent<Rigidbody>();
}

// 3
private FixedJoint AddFixedJoint()
{
    FixedJoint fx = gameObject.AddComponent<FixedJoint>();
    fx.breakForce = 20000;
    fx.breakTorque = 20000;
    return fx;
}

ここでは、あなたは:

  1. プレイヤーの手の中にGameObjectを移動し、collidingObject変数からGameObjectを削除します。
  2. 以下のAddFixedJoint()メソッドを使用して、コントローラをオブジェクトに接続する新しいジョイントを追加します。
  3. 新しい固定ジョイントを作ってコントローラに追加し、簡単に破損しないようにセットアップします。最後に、あなたはそれを返します
つかむことができるものをリリースする必要があります。この次のブロックは、オブジェクトの解放を処理します。

筆者追記:
  • Fixed Jointは、複数の物理オブジェクトをどちらかに依存させるメソッドのようです。)


  • 次のコードを追加します。
ControllerGrabObject.cs

private void ReleaseObject()
{
    // 1
    if (GetComponent<FixedJoint>())
    {
        // 2
        GetComponent<FixedJoint>().connectedBody = null;
        Destroy(GetComponent<FixedJoint>());
        // 3
        objectInHand.GetComponent<Rigidbody>().velocity = Controller.velocity;
        objectInHand.GetComponent<Rigidbody>().angularVelocity = Controller.angularVelocity;
    }
    // 4
    objectInHand = null;
}


このコードは、グラブされたオブジェクトの固定されたジョイントを取り除き、プレイヤーがそれを投げるときに速度(velocity)と回転(angularVelocity)を制御します。コントローラーの速度が重要です。それを使用せずに、あなたのスローがどんなに完璧であっても、破棄されたオブジェクトはまっすぐに落ちるでしょう。

  1. コントローラーにfixed jointが取り付けられていることを確認します
  2. jointに保持されているobjectへの接続を削除し、jointを破棄します。
  3. プレイヤーがobjectを解放したときに、実際の球のようにするため、コントローラの速度(velocity)と回転(angular velocity)を追加します
  4. 以前に接続されたobjectへの参照を削除します。

  • 最後に、コントローラの入力を処理するUpdate()の内部に以下のコードを追加します。

ControllerGrabObject.cs

// 1
if (Controller.GetHairTriggerDown())
{
    if (collidingObject)
    {
        GrabObject();
    }
}

// 2
if (Controller.GetHairTriggerUp())
{
    if (objectInHand)
    {
        ReleaseObject();
    }
}


  1. プレイヤーがトリガーを絞って潜在的なグラブターゲットがあるとき、これはそれをつかみます
  2. プレイヤーがトリガーを解放し、コントローラーにオブジェクトが接続されている場合は、これが解放されます。

これまでのコードは、全体としてこのようになります。


ControllerGrabObjects.rb

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ControllerGrabObject : MonoBehaviour {

    private SteamVR_TrackedObject trackedObj;

    private GameObject collidingObject;

    private GameObject objectInHand;

    private SteamVR_Controller.Device Controller
    {
        get { return SteamVR_Controller.Input((int)trackedObj.index); }
    }

    void Awake()
    {
        trackedObj = GetComponent<SteamVR_TrackedObject>();
    }

    private void SetCollidingObject(Collider col)
    {
        if (collidingObject || !col.GetComponent<Rigidbody>())
        {
            return;
        }
        collidingObject = col.gameObject;
    }

    public void OnTriggerEnter(Collider other)
    {
        SetCollidingObject(other);
    }

    public void OnTriggerStay(Collider other)
    {
        SetCollidingObject(other);
    }

    public void OnTriggerExit(Collider other)
    {
        if (!collidingObject)
        {
            return;
        }

        collidingObject = null;
    }

    private void GrabObject()
    {
        objectInHand = collidingObject;
        collidingObject = null;
        var joint = AddFixedJoint();
        joint.connectedBody = objectInHand.GetComponent<Rigidbody>();
    }

    private FixedJoint AddFixedJoint()
    {
        FixedJoint fx = gameObject.AddComponent<FixedJoint>();
        fx.breakForce = 20000;
        fx.breakTorque = 20000;
        return fx;
    }

    private void ReleaseObject()
    {
        if (GetComponent<FixedJoint>())
        {
            GetComponent<FixedJoint>().connectedBody = null;
            Destroy(GetComponent<FixedJoint>());

            objectInHand.GetComponent<Rigidbody>().velocity = Controller.velocity;
            objectInHand.GetComponent<Rigidbody>().angularVelocity = Controller.angularVelocity;
        }

        objectInHand = null;
    }

    void Update()
    {
        if (Controller.GetHairTriggerDown())
        {
            if (collidingObject)
            {
                GrabObject();
            }
        }

        if (Controller.GetHairTriggerUp())
        {
            if (objectInHand)
            {
                ReleaseObject();
            }
        }
    }
}


  • スクリプトを保存してエディタに戻ります。
  • Hierarchyで両方のコントローラを選択し、新しいスクリプトをインスペクタにドラッグしてコンポーネントにします。



実際に遊んでみましょう!コントローラーを準備し、ゲームを開始してヘッドセットを装着します。ヘアトリガーを使っていくつかのキューブとボールをピックアップし、投げてみましょう。あなたは少しでも練習をしていても気をつけることができます。



あなたはそれを自分に渡さなければなりません - あなたは今はとても素晴らしいです。しかし、私はあなたのVR体験をよりクールにすることができると思います!



レーザーポインター(laser pointer)の作り方



レーザーポインターはあらゆる理由でVR世界で便利です。バーチャルバルーンをポップアップさせ、銃をより良く照準することもできるでしょう。


作り方はとても簡単です。キューブと別のスクリプトが必要なだけです。

    • 最初に、階層(Create> 3D Object> Cube)に新しいキューブを作成します。Laserという名前を付けてください。


  • Positionを(X:0、Y:5、Z:0)に設定します
  • スケールを(X:0.005、Y:0.005、Z:0)に変更します
  • Box Colliderコンポーネントを削除します。
それに焦点を当てると、残りのレベルの上に浮かんでいるはずです。(どういう意味?)



レーザは影を投げかけてはならず、常に同じ色ですので、消灯したマテリアルを使用して望みの効果を得ることができます。

  • Materialsフォルダで右クリックし、create > materialを選択しLaserという名前新でマテリアルを作成します。
  • ShaderをUnlit / Colorに変更します
  • Main Colorを純粋な赤に設定します:


  • 新しいmaterialをSceneビューのレーザーにドラッグして割り当てます。または、HierarchyのLaserにドラッグすることもできます。


  • 最後に、LaserをPrefabsフォルダにドラッグし、元のファイルをHierarchyから削除します。


  • ScriptsフォルダにLaserPointerという名前の新しいC#スクリプトを作成して開きます。この使い慣れたヘルパーコードを追加してください:
LaserPointer.cs

private SteamVR_TrackedObject trackedObj;

private SteamVR_Controller.Device Controller
{
    get { return SteamVR_Controller.Input((int)trackedObj.index); }
}

void Awake()
{
    trackedObj = GetComponent<SteamVR_TrackedObject>();
}

  • 以下の変数をtrackedObjの下に追加します。

LaserPointer.cs

// 1
public GameObject laserPrefab;
// 2
private GameObject laser;
// 3
private Transform laserTransform;
// 4
private Vector3 hitPoint;


  1. これは、レーザーのプレハブへの参照です。
  2. laserは、レーザのインス​​タンスへの参照を記憶します
  3. transformコンポーネントは、使いやすさのために格納されます。
  4. これは、レーザーが当たる位置です。

  • 以下のメソッドを追加して、レーザーを表示します:
LaserPointer.cs

private void ShowLaser(RaycastHit hit)
{
    // 1
    laser.SetActive(true);
    // 2
    laserTransform.position = Vector3.Lerp(trackedObj.transform.position, hitPoint, .5f);
    // 3
    laserTransform.LookAt(hitPoint); 
    // 4
    laserTransform.localScale = new Vector3(laserTransform.localScale.x, laserTransform.localScale.y,
        hit.distance);
}


このメソッドは、ヒットの位置と移動距離を含むため、RaycastHitをパラメータとして使用します。

各メソッドを一つ一つ見ていきます:
  1. レーザーを表示する。
  2. コントローラーとレイキャストが当たるポイントの間にレーザーを配置します。 Lerpを使用するのは、2つのポジションとそのパーセンテージを渡すことができるからです。 0.5f(50%)を渡すと、正確な中間点が返されます。
  3. レイキャストが当たる位置にレーザーを向ける。
  4. レーザーを2つの位置の間に完全に収まるようにスケールします。

筆者追記
Raycastとは、ある地点から透明な線を特定の方向に引いて、そのRay上のどこかでコライダがあるか検知をするものです。例えば、ゲームであれば銃から弾を発射して敵に攻撃する場合などに用います。

  • プレーヤーの入力を利用するには、Update()メソッドの中で以下を追加します:

LaserPointer.cs

// 1
if (Controller.GetPress(SteamVR_Controller.ButtonMask.Touchpad))
{
    RaycastHit hit;

    // 2
    if (Physics.Raycast(trackedObj.transform.position, transform.forward, out hit, 100))
    {
        hitPoint = hit.point;
        ShowLaser(hit);
    }
}
else // 3
{
    laser.SetActive(false);
}


  1. タッチパッドが押されている場合...
  2. コントローラから光線を射撃する。何かに当たった場合は、ヒットしたポイントを保存してレーザーを表示します。
  3. プレイヤーがタッチパッドを離したときにレーザーを隠す。

  • 空のStart()メソッドの内部に以下のコードを追加します。
LaserPointer.cs

// 1
laser = Instantiate(laserPrefab);
// 2
laserTransform = laser.transform;


  1. 新しいレーザーを生成し、そのレーザーへの参照を保存します。
  2. レーザーの変換コンポーネントを保管します。

全体のスクリプトは以下のようになります。

LaserPointer.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LaserPointer : MonoBehaviour {

    private SteamVR_TrackedObject trackedObj;

    public GameObject laserPrefab;

    private GameObject laser;

    private Transform laserTransform;

    private Vector3 hitPoint;

    private SteamVR_Controller.Device Controller
    {
        get { return SteamVR_Controller.Input((int)trackedObj.index); }
    }

    void Start()
    {
        laser = Instantiate(laserPrefab);
        laserTransform = laser.transform;
    }

    void Awake()
    {
        trackedObj = GetComponent<SteamVR_TrackedObject>();
    }

    private void ShowLaser(RaycastHit hit)
    {
        laser.SetActive(true);
        laserTransform.position = Vector3.Lerp(trackedObj.transform.position, hitPoint, .5f);
        laserTransform.LookAt(hitPoint);
        laserTransform.localScale = new Vector3(laserTransform.localScale.x, laserTransform.localScale.y,
            hit.distance);
    }

    void Update () {

        if (Controller.GetPress(SteamVR_Controller.ButtonMask.Touchpad))
        {
            RaycastHit hit;

            if (Physics.Raycast(trackedObj.transform.position, transform.forward, out hit, 100))
            {
                hitPoint = hit.point;
                ShowLaser(hit);
            }
        }
        else
        {
            laser.SetActive(false);
        }

    }
}


  • このスクリプトを保存してエディタに戻ります。
  • Hierarchyで両方のコントローラを選択し、インスペクタにレーザスクリプトをドラッグしてコンポーネントにします。


  • PrefabsフォルダからInspectorのLaserスロットにLaserプレハブをドラッグします:


あなたのプロジェクトを保存し、ゲームをもう一度実行してください。コントローラーを持ち上げてヘッドセットを取り付け、タッチパッドを持ってみてください。今すぐレーザーが表示されます:



次に進む前に、

  • 入力テストコンポーネントを右クリックし、[remove Component]を選択して、コントローラから入力テストコンポーネントを削除します。

あなたがそれらを削除しているのは、すべてのフレームの文字列を書き込み、それらをコンソールに記録するからです。パフォーマンスにとってはそれほど大したことではなく、1ミリ秒ごとにVRでカウントされます。入力をテストするのに便利でしたが、実際のゲームプレイには使用しないでください。


次のステップでは、このレーザーを使って部屋を移動します。


部屋を移動する


VRで移動することは、プレーヤーを前方に押すほど簡単ではありません。そうすることは、吐き気を誘発するための確実な方法です。回避するより実現可能な方法はテレポートです。


プレイヤーの知覚感覚は、徐々に変化する位置感覚よりも急激な位置変化を受け入れることになります。 VR設定の微妙な変化は、突然あなたを新しい場所で見つけ出すよりも、あなたのバランス感覚と速度感を上回ります。


最終的にどこに表示されるかを表示するには、Prefabsフォルダにあるマーカーまたはreticle を使用します。reticle ルはシンプルで、消灯した円形ディスクです。



レチクルを使用するには、LaserPointerスクリプトを追加するため、コードエディタで開き、これらの変数をクラスの先頭に追加します。

LaserPointer.cs

// 1
public Transform cameraRigTransform; 
// 2
public GameObject teleportReticlePrefab;
// 3
private GameObject reticle;
// 4
private Transform teleportReticleTransform; 
// 5
public Transform headTransform; 
// 6
public Vector3 teleportReticleOffset; 
// 7
public LayerMask teleportMask; 
// 8
private bool shouldTeleport; 



各変数はそれぞれの役割を果たします。

  1. [CameraRig]のtransformです。
  2. teleport reticle prefabへの参照を格納します。
  3. reticleのインスタンスへの参照します
  4. 使いやすさのためにteleport reticle transformへの参照を格納します。
  5. プレーヤーの頭(カメラ)への参照を格納します。
  6. reticleは床からオフセットされているので、他のサーフェスとの「Z-fighting」はありません。
  7. テレポートが許可されているエリアをフィルタリングするレイヤーマスクです。
  8. 有効なテレポート位置が見つかった場合はtrueに設定されます。

  • Update()メソッドで、次の行を置き換えます。

LaserPointer.cs

if (Physics.Raycast(trackedObj.transform.position, transform.forward, out hit, 100))

レイヤーマスクを考慮に入れた次のものを使用します。

LaserPointer.cs

if (Physics.Raycast(trackedObj.transform.position, transform.forward, out hit, 100, teleportMask))


これは、あなたがテレポートすることができるGameObjectsにのみレーザーが当たることを保証します。

  • また、Update()メソッドでは、このコードをShowLaser()の呼び出しの下に追加します。

LaserPointer.cs

// 1
reticle.SetActive(true);
// 2
teleportReticleTransform.position = hitPoint + teleportReticleOffset;
// 3
shouldTeleport = true;


このコードについて説明します。
  1. テレポートレチクルを表示する。
  2. Z-戦闘を避けるために、オフセットを加えてレイキャストがヒットした場所にレチクルを移動します。
  3. スクリプトがテレポートするための有効な位置を見つけたことを示すには、shouldTeleportをtrueに設定します。


Update()メソッドの中で、laser.SetActive(false)を見つけてください。その下に次の行を追加します。

LaserPointer.cs

reticle.SetActive(false);


これは有効なターゲットがない場合にレチクルを隠します。


  • テレポーティングの動作を処理する次のメソッドを追加します。
LaserPointer.cs

private void Teleport()
{
    // 1
    shouldTeleport = false;
    // 2
    reticle.SetActive(false);
    // 3
    Vector3 difference = cameraRigTransform.position - headTransform.position;
    // 4
    difference.y = 0;
    // 5
    cameraRigTransform.position = hitPoint + difference;
}


そのコードを一つずつ見てみましょう:

  1. テレポーテーションが進行中のときは、shouldTeleportフラグをfalseに設定します。
  2. レチクルを隠します。
  3. Camera Rigの中心とプレーヤーの頭の位置の差を計算します。
  4. 計算ではプレーヤーの頭の垂直位置は考慮されないため、上記の差のy位置を0にリセットします。
  5. カメラリグをヒットポイントの位置に移動し、計算された差を加算します。違いがなければ、プレイヤーは誤った場所に移動します。以下の例を参照してください。



ご覧のように、この違いは、カメラリグを正確に配置し、プレーヤーが着陸する場所に正確に配置する上で重要な役割を果たします。

  • Update()の最後にこれを追加します。if-else文:
LaserPointer.cs

if (Controller.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad) && shouldTeleport)
{
    Teleport();
}



これは、タッチパッドが解放され、有効なテレポート位置がある場合、プレーヤーをテレポートします。

  • 最後に、このコードをStart()メソッドに追加します。

LaserPointer.cs

// 1
reticle = Instantiate(teleportReticlePrefab);
// 2
teleportReticleTransform = reticle.transform;


  1. 新しいレチクルを作成し、レチクルにそのレファレンスを保存します。
  2. レチクルの変換コンポーネントを格納します。

全体としてはこんな感じになります。


LaserPointer.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LaserPointer : MonoBehaviour {

    private SteamVR_TrackedObject trackedObj;

    public GameObject laserPrefab;

    private GameObject laser;

    private Transform laserTransform;

    private Vector3 hitPoint;

    private SteamVR_Controller.Device Controller
    {
        get { return SteamVR_Controller.Input((int)trackedObj.index); }
    }

    public Transform cameraRigTransform;

    public GameObject teleportReticlePrefab;

    private GameObject reticle;

    private Transform teleportReticleTransform;

    public Transform headTransform;

    public Vector3 teleportReticleOffset;

    public LayerMask teleportMask;

    private bool shouldTeleport;

    void Start()
    {
        laser = Instantiate(laserPrefab);
        laserTransform = laser.transform;
        reticle = Instantiate(teleportReticlePrefab);
        teleportReticleTransform = reticle.transform;
    }

    void Awake()
    {
        trackedObj = GetComponent<SteamVR_TrackedObject>();
    }

    private void ShowLaser(RaycastHit hit)
    {
        laser.SetActive(true);
        laserTransform.position = Vector3.Lerp(trackedObj.transform.position, hitPoint, .5f);
        laserTransform.LookAt(hitPoint);
        laserTransform.localScale = new Vector3(laserTransform.localScale.x, laserTransform.localScale.y,
            hit.distance);
    }

    private void Teleport()
    {
        shouldTeleport = false;
        reticle.SetActive(false);
        Vector3 difference = cameraRigTransform.position - headTransform.position;
        difference.y = 0;
        cameraRigTransform.position = hitPoint + difference;
    }

    void Update () {

        RaycastHit hit;


        if (Physics.Raycast(trackedObj.transform.position, transform.forward, out hit, 100, teleportMask))
        {

            if (Physics.Raycast(trackedObj.transform.position, transform.forward, out hit, 100))
            {
                hitPoint = hit.point;
                ShowLaser(hit);
                reticle.SetActive(true);
                teleportReticleTransform.position = hitPoint + teleportReticleOffset;
                shouldTeleport = true;
            }
        }
        else
        {
            laser.SetActive(false);
            reticle.SetActive(false);
        }

         if (Controller.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad) && shouldTeleport)
        {
            Teleport();
        }

    }
}


これで終わりです!スクリプトを保存してUnityに戻ります。

  • Hierarchyの両方のコントローラを選択し、新しいフィールドを書き留めます。


  • [CameraRig]をCamera Rig Transformスロットにドラッグ
  • PrefabsフォルダからTeleport Reticle PrehabスロットにTeleportReticleをドラッグ
  • Camera(ヘッド)をHead Transformスロットにドラッグします。

今ゲームをプレイし、部屋の周りをテレポートするために床にレーザーを使用します。






注目の投稿

 PythonのTweepyを利用して、Twitter APIを利用している。 その中で、ハマったポイントをメモしておく。 まず、Searchに関して。 Twitter検索は、クライアントアプリ側では、全期間の検索が可能になっている。 一方で、APIを利用する際は、過去1週間しか...