2017-11-05

【HTC VIVE】VR開発チュートリアル ~町の中で空を飛んでビームを撃つ~

HTC VIVEでVRアプリを作成してみた


こんにちは、DAIです。


2016年がVR元年と呼ばれ、やっと普及し始めつつあるVRですが、私も10月にHTC VIVEを衝動買いしてしまいました。もともとVRゲームを作って、「残業させようとする上司を銃撃するゲーム」を作ることを前提に購入したわけで、この3連休でなんとかVRアプリを作ってみました。


Unityを使えばほとんどコードを書かずにゲームが作れると聞いたので、VR開発初心者が0からVRアプリを作ってみました。参考にするチュートリアルは日本にはあまり存在しなかったため、海外のYoutubeにアップロードされているチュートリアルを参考にしました。



Unity VR Tutorial - How to make an IronMan like mechanic using HTC Vive



町の中を自由自在に飛び回り、ビームを撃てるアイアンマンアプリケーションを作ってみたので、こちらのチュートリアルを日本語に翻訳してみたいと思います。途中の説明がまだ抜けていたりしますが、忘備録として書いているので、詳しくは動画のほうを見てみてくださいね。




環境



準備


  • Visual Studioのインストール(実際にコードを書く統合開発環境です)
  • Unityのアカウント登録(VRアプリを作るうえで使います)
  • Streamのアカウント登録(Assetというアプリの材料を買うためのストアを使います)
  • Blenderのインストール(Unity上で動くオブジェクトを作るソフトです)

注意点


  • Asset StoreでSimplepoly Cityという町のAssetを購入するので、10$はかかってしまいます




手順


#新規プロジェクトの作成

Unity上で新しいアプリを作る場合、新規のプロジェクトを作る必要があります。以下の手順で進めてください。


  • Unityを開き、Create Unity Object > Name projectを選択しIronmanをいうプロジェクトを作成する
  • Window > Asset Storeをクリックし
    • まだ購入していない場合search "StreamVR" > import
    • すでにimportしている場合 Asset Store > Downloads > Stream VR Pluginをインポート


#Assetsの準備

知らなかった単語をまとめておきます。

ヒエラルキー
Hierarchy には、現在開いているシーン中のすべての GameObject が含まれています。これらの内のあるものは 3D モデルのようなアセットファイルのダイレクトインスタンスであったり、Prefab(プレハブ) と呼ばれる、あなたのゲームの多くの部分を作り上げることになるカスタムオブジェクトのインスタンスだったりします。ヒエラルキーの中では、オブジェクトを選択したり、あるオブジェクトを別のオブジェクトの上にドラッグすることで Parenting (詳しくは下記参照)を付けることができます。シーンの中にオブジェクトを追加したり取り除いたりすると、同様にヒエラルキー上でも表示されたり消えたりします。
アセット

Asset はゲーム作成で使用するモデル、テクスチャ、サウンドやその他のコンテンツファイルです。

プレハブ

Prefab は、コンポーネントを追加し、これらのプロパティーを適切な値に設定してシーン内の GameObject を構築するのに便利です。面倒ですが、シーンで数回再利用する NPC や小道具、風景の一部のようなオブジェクト作成することができます。単純にオブジェクトをコピーすると重複して生成されますが、それらはすべて独立して編集可能になります。一般的に、シーンで一つのオブジェクトを編集するとき、すべてのコピーに繰り返し同じ編集作業をすることはしたくないでしょうから、特定のオブジェクトのすべてのインスタンスが同じ性質を持つようにします。


  • Hierarchy >Untitled > Main Cameraを削除
  • ページ下部のAssets > Sream VR > Prehabs > CameraRigを上のページに追加
  • CameraRigをPlayerRigに変更
  • Assets > IronManというフォルダの追加
  • Assets > IronMan > Scenesというフォルダの追加
  • Assets > IronMan > Materialsというフォルダの追加
  • Assets > IronMan > Soundsというフォルダの追加
  • Assets > IronMan > Modelsというフォルダの追加
  • Assets > IronMan > Prefabsというフォルダの追加
  • Assets > IronMan > Scriptsというフォルダの追加
  • Assets > IronMan > PrefubsにHierarchy > *Untitled > PlayerRigsをコピー
  • Ctrl + sをクリックする
  • Assets > IronMan > ScenesにGameを保存する
  • Gameタブを選択し、画面中央の再生ボタンを押す

#Planeの設置

Rigid Body:

重力、加速度を使った処理
衝突した物体を動かす、衝突された物体の動きに作用する
衝突発生時に任意の処理を実行する
Collider:

アセットや変換したモデルがすり抜けてしまう場合はオブジェクトにColliderが設定されていないのが原因。重力で落下した物体が地面に着地するにはそれぞれに下記のコンポーネントが必要になる。
CubeやSphereなどのプリミティブには最初からColliderが付いている。
落下する物体
物体の形に応じたCollider
重力が設定されたRigidbody
受け止める地面
地面の形に応じたCollider

  • Game > Plane(3Dオブジェクト)を追加する
  • Planeを押し、Inspector > Scaleから X = 10, Y = 1.  Z = 10に設定する
  • PlayerRigをクリックし、Inspector > add Componentをクリック、rigid bodyで検索し加える
  • PlayerRigをクリックし、Inspector > add Componentをクリック、Box Collider    で検索し加える
  • Box ColiderでSizeをx = 2, y = 0.01, z = 2に変更する

#Jet Packの作成

C#のスクリプトを書き、空を飛べるようにします。

  • Project > Ironman > Scriptsを右クリックし、Create > C#を選択 JetPackと名付ける
  • JetPackをダブルクリックするとVisual Studioが開き、エディタ画面になる
  • Game > PlayerRig > Controller(right)をクリック、Inspector > Add Componentから Stream_VR_Tracked Controllerを選択
  • Game > PlayerRig > Controller(right)をクリック、Inspector > Add Componentから Stream_VR_Tracked Objectを選択
  • Game > PlayerRig > Controller(left)をクリック、Inspector > Add Componentから Stream_VR_Tracked Controllerを選択
  • Game > PlayerRig > Controller(left)をクリック、Inspector > Add Componentから Stream_VR_Tracked Objectを選択
  • JetPackに以下のスクリプトを追加する
using System;
using System.Collections.Generic;
using UnityEngine;
public class JetPack : MonoBehaviour {
    private Rigidbody _rigidbody;
    private SteamVR_TrackedController _controller;
    private void OnEnable()
    {
        _controller = GetComponent<SteamVR_TrackedController>();
        _controller.PadClicked += HandlePadClicked;
    }
    private void HandlePadClicked(object sender, ClickedEventArgs e)
    {
        Debug.Log("Pad Cliked");
    }
    private void OnDisable()
    {
        _controller.PadClicked -= HandlePadClicked;
    }
}


  • Game > PlayerRig > Controller(right), Controller(left) を両方選択し、InspectorにJetpackを追加する(警告が出てきたらOKを押す)
  • Window > Consoleを選択
  • 再生ボタンをクリックし、パッドをクリックする。DEBUGが反映されていればOK
  • JetPackに以下のコードを追加する
using System;
using System.Collections.Generic;
using UnityEngine;
public class JetPack : MonoBehaviour {
    public float thrustMultiplier = 15f; //追加
    private Rigidbody _rigidbody;
    private SteamVR_TrackedController _controller; 
    private void OnEnable()
    {
        _controller = GetComponent<SteamVR_TrackedController>();
        _controller.PadClicked += HandlePadClicked;
    }
    private void HandlePadClicked(object sender, ClickedEventArgs e)
    {
        Debug.Log("Pad Cliked");
    }
    private void OnDisable()
    {
        _controller.PadClicked -= HandlePadClicked;
    }
    private void Start() //追加
    {
        _rigidbody = GetComponentInParent<Rigidbody>();
    }
    private void FixedUpdate() //追加
    {
        var thrust = _controller.controllerState.rAxis1.x;
        if (thrust > 0.1f)
        {
            var forceVector = transform.forward * thrust * thrustMultiplier;
            _rigidbody.AddForce(forceVector);
        }
    }
}


#Player Scriptsの作成

プレイヤーが地面からすり抜けないようにします。
  • Assets > IronMan > Scripts > Playerを作成(C#)し、Visual Studioをインストール
  • Game > PlayerRigのInspectorにPlayerをコピペ(プレイヤーの位置が0未満になったら自然に戻すスクリプト)
  • 以下のコードをコピペ
using UnityEngine;
public class Player : MonoBehaviour {
    Rigidbody _rigitbody;
       void Start () {
        _rigitbody = GetComponent<Rigidbody>();
              
       }

    private void FixedUpdate()
    {
       //prevent us from passing through the ground
        if (transform.position.y < 0f)
            _rigitbody.MovePosition(new Vector3(transform.position.x, 0f, transform.position.z));
    }
}


  • さらに速度を超えないように制限するコードを加える
using UnityEngine;
public class Player : MonoBehaviour {
    Rigidbody _rigitbody;
    public float maxVelocity = 1f;
       void Start () {
        _rigitbody = GetComponent<Rigidbody>();        
       }
    private void FixedUpdate()
    {
        //Limit our velocity
        if (_rigitbody.velocity.magnitude > maxVelocity)
            _rigitbody.velocity = Vector3.ClampMagnitude(_rigitbody.velocity, maxVelocity);
        //prevent us from passing through the ground
        if (transform.position.y < 0f)
            _rigitbody.MovePosition(new Vector3(transform.position.x, 0f, transform.position.z));
    }
}

  • Playボタンをクリックし、スピードが変更されていればクリア
  • Playモードを中断する

#Simplepoly Cityの導入

0から町を作るのではなく、町を買います。そしてその町をUnity上で配置します。

  • Window > Asset Storeをクリックし、 Simple Cityをダウンロードする
    • すでにimportしている場合 Asset Store > Downloads > Stream VR Pluginをインポート
  • Gameディレクトリで右クリックを押し、Create Emptyを選択、Game > Cityというファイルを作成する
  • 同様にGame > Vehiclesというファイルを作成する
  • Hierarchy > Gameの中に、Assets > SimplePolyCity > DEMOをドラッグアンドドロップする
  • Hierarchy > Game > SimplePolyCityの中で、検索窓から"Vehicles"と検索し、そのすべてをGame > Vehiclesの中にドラッグアンドドロップする
  • Game > SimplePolyCityの中の、Main CameraとDirectional Lightを削除する
  • Game > SimplePolyCityの中ですべてを選択し、"Vehicles"以外をGame > Cityにドラッグアンドドロップする
  • Game > SimplePolyCityを削除する
  • City > InspectorからStaticを選択
  • Window > Lighting >  SettiingからAuto generateのチェックを外す
  • Game > City > すべてのファイルを選択し、InspectorからMesh Colliderを追加する
  • Game > Vehicles > すべてのファイルを選択し、InspectorからMesh Colliderを追加する
  • Game > VehiclesのInspectorから Convexにチェックを入れる
  • Game > VehiclesのInspectorでrigid bodyを追加する
  • GameObject >  Effects > Partical Systemを選択する

#Blenderでオブジェクトの作成

空を飛んでいるときの煙を作りたいので、パーツを作るソフトウェアBlenderを利用します。
  • Blenderを起動する
  • 三角柱のオブジェクトを右クリックし、画面下のSelect > Select Allを選択
  • 画面左のDeleteを押して、すべてのオブジェクトを消去する
  • Add > Mesh > Icosphereをクリック
  • File > Export >FBXをクリックする
  • C:\workspace\Unity\Ironman2\Assets\Ironman\Modelsにsmoke.fbxと名付けてExport fbxをクリックし保存

#Blenderで作ったオブジェクトをUnityへ取り込む

  • Unityに戻る
  • Assets > Irronman > Modelsから、保存したsmoke.fbxをScenceの上にドラッグアンドドロップする
  • Partial Systemをクリックし、Inspectorから Renderer > Render ModeをMeshに変更
  • Assets > Models > Materials > smoke > icosphereをPartial System > Inspector > Renderer > Meshにコピー
  • Assets > Materials で右クリック > Create > MaterialsでSmokeを作成
  • Assets > Models > Materialsを削除
  • Assets > Materials > SmokeをPartial System > Inspector > Renderer > materialsにコピー
  • start size = 15に設定
  • Shape = edgeに設定
  • Rotation = 0に設定
  • shape > radius = 0.001に設定
  • start lifetime = 0.5に設定
  • Ironman > materials > Smoke Inspector Particles = additiveに設定
  • Partial System > Inspector > color over lifetime :赤から黄色に設定
  • Partial System > Inspector > emission > rate over time = 25に設定
  • Partial System > Inspector > start lifetime  = 0.3に設定
  • Game > Player Rig  > controller leftにpartial systemをドラッグアンドドロップ
  • Game > Player Rig  > controller left > Partial SystemのInspectorのpositionを0, 0, 0に指定する
  • Game > Player Rig  > controller left > Partial SystemのInspectorの rotationのx = 90に指定する
  • Game > Player Rig  > controller left > Partial Systemをコピーし、Game > Player Rig  > controller leftにコピーする
  • max velocity = 10に設定(ものの動きを設定する)
  • Game > Player RigのInspectorから、rigidbody > constraint > freeze rotation:x,y,zにチェックを入れる

#銃を撃つ

  • 以下のコードをJet Packに追加
using System;
using System.Collections.Generic;
using UnityEngine;
public class JetPack : MonoBehaviour
{
    public float thrustMultiplier = 10f;
    public float thrustEmissionRate = 30f; //追加
    private Rigidbody _rigidbody;
    private SteamVR_TrackedController _controller;
    private ParticleSystem _thrust; //追加
    private void OnEnable()
    {
        _controller = GetComponent<SteamVR_TrackedController>();
        _controller.PadClicked += HandlePadClicked;
        _thrust = GetComponent<ParticleSystem>(); //追加
    }
    private void HandlePadClicked(object sender, ClickedEventArgs e)
    {
        Debug.Log("Pad Cliked");
    }
    private void OnDisable()
    {
        _controller.PadClicked -= HandlePadClicked;
    }
    private void Start()
    {
        _rigidbody = GetComponentInParent<Rigidbody>();
    }
    private void FixedUpdate()
    {
        var thrust = _controller.controllerState.rAxis1.x;
        if (thrust > 0.1f)
        {
            var forceVector = transform.forward * thrust * thrustMultiplier;
            _thrust.emissionRate = thrust * thrustEmissionRate; //追加
            _rigidbody.AddForce(forceVector);
        }
        else //追加
        {
            _thrust.emissionRate = 0f; //追加
        }
    }
}

  • Controller > modelsのInspectorで、チェックを外す
  • Youtubeの動画 > DescriptionからTrusterをダウンロード
  • Assets > Ironman > ModelsにTrusterをドラッグアンドドロップする
  • Assets > Ironman > ModelsにTrusterをドラッグアンドドロップする
  • Assets > Ironman >Modelsを両方から消去する
  • Create new Game > Game Object
  • Game Object > Inspector > Reset
  • Add Scripts > GameManager
  • Assets > Ironman2 > Scripts に RootフォルダのGameManagerをコピー    
  • 以下のコードを追加する
using UnityEngine;
public class GameManager : MonoBehaviour
{
    public static GameManager instance;
    public GameObject ammoPrefab;
    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
        else if (instance != this)
        {
            Destroy(gameObject);
        }
    }
}



  • 右クリック > Create 3D  > Sphereを作成
  • SphereをAmmoに名前を変更
  • SphereにRigid Bodyを持たせる    
  • PrefabにAmmoをコピー
  • Ammoを削除
  • GameManagerに名前を変更
  • GameManager > Inspector > AmmoPrefabにAmmoをコピペ
  • 完成

#感想


コードでC#を使うのですが、そこまでコードを書かなかったです。そこに少しびっくりしました。また、VRで自分が作ったアプリケーションが動くのはなんとも言えない感動がありました。しかし、まったくの初心者だったので、チュートリアルなしでやるのは相当難しいと思われます。


今度は銃撃戦ができるようなアプリケーションを作ってみようかと思います。

注目の投稿

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