【Unity】「Steam VR Plugin ver2」を使って「Survival Shooter Tutorial」をVR化してみる　HTC VIVE編

# 完成イメージ
![SurrivalSjooterVR.gif](https://qiita-image-store.s3.amazonaws.com/0/189146/47e60fdb-7a4b-5a50-3985-34243ebf69bf.gif)



# 対象者
Steam VR Plugin ver2を使ってみたい方
Unityで普通のゲームをVR化してみたい人
Unityの「Survival Shooter Tutorial」を知っている方

# 環境
OS: Windows10
Unity: Unity2018.3
HMD: VIVE HTC

# 準備
まず、「[Survival Shooter Tutorial](https://assetstore.unity.com/packages/essentials/tutorial-projects/survival-shooter-tutorial-40756)」と「[SteamVR Plugin](https://assetstore.unity.com/packages/tools/integration/steamvr-plugin-32647)」をアセットストアにて、ダウンロード＆インポートする。

※[「Survival Shooter Tutorial」](https://unity3d.com/jp/learn/tutorials/s/survival-shooter-tutorial)の中身は特に解説する予定はないので、事前にチュートリアルをしておくことをおすすめします。

![shoooter.PNG](https://qiita-image-store.s3.amazonaws.com/0/189146/a1162f59-dfa2-75c1-09b6-1a1f6acea5d9.png)

![steamvr.PNG](https://qiita-image-store.s3.amazonaws.com/0/189146/a2cfb074-ec56-8018-df9d-2fe2dbd93527.png)

インポートが完了したら、「Survival Shooter Tutorial」の_Complete-Game**の完成しているシーンを読み込んでおいてください。
「windows →　 Package Manager」からOpenVRもインストールしておきましょう。


#　手順

### Steam VR Controllerにアクションを設定

version1では、Controllerのボタンに対して、処理を書くような形だったが、version2からは先にゲームでの**アクション**を定義し、**アクション**に対応する**ボタンの紐付け**を行う形になっている。

新しいControllerがでてきても**ボタンの紐付け**だけすれば対応できるようになるのでとてもいい変更だと思います。

では、**ボタンの紐付け**を行いましょう。

「windows → SteamVR Input」で**アクション**を定義します。
今回はInに**Shoot**という**アクション**をTypeはbooleanで追加します。
「Save and generate」で保存しましょう。

![SteamVR_Input.PNG](https://qiita-image-store.s3.amazonaws.com/0/189146/d62c9982-d49f-369e-7e43-32c124e2097b.png)

次は**Shoot**に**ボタンの紐付け**を行います。
**SteamVR**を起動しましょう。起動しておかないと**ボタンの紐づけ**はできません。

![steamvr.PNG](https://qiita-image-store.s3.amazonaws.com/0/189146/137d1adc-e8d6-d7e3-3887-ace14b6b624d.png)


「SteamVR Input」で「open binding UI」をクリックする。
するとブラウザが開き、下のような画面が現れます。
この醜いUIで**ボタンの紐付け**を行うことができます。

![bindingUI.PNG](https://qiita-image-store.s3.amazonaws.com/0/189146/178f4564-8f9e-7e3c-4ce2-1c66f61f1c9b.png)

Editボタンを押すと、ボタンやタッチパッドに紐づけられる設定画面が表示される。
※かなり見づらい。

![bindingUI2.PNG](https://qiita-image-store.s3.amazonaws.com/0/189146/418ad70f-a5c4-134b-367e-907e4e391068.png)

Triggerの「+」ボタンを押し、Buttonを追加する。
追加したButtonに対応するActionをNoneからShootにする。
フッターのSave Personal Bindingを押すと、保存する画面が出てくるので、vive_controllerで保存する。
保存が完了すれば、ブラウザは閉じてもらってよい。
※自分のブラウザでは保存ボタンが見切れてしまっている。

![BindingUI3.PNG](https://qiita-image-store.s3.amazonaws.com/0/189146/b17d406c-ca24-f049-77ed-462d6436ad6a.png)

### PlayerのVR化
1人称視点で世界に入れるようにします。

まず、「Hierarchy」から**MainCamera**と**Player**を無効化してください。

そして、「Project」の「SteamVR　→　InteractionSystem　→　Core　→　Prefabs」の**Player**を「Hierarchy」にドラッグアンドドロップし、名前を**PlayerVR**にしましょう。　

**PlayerVR → SteamVR Objects**の**BodyCollider**をアクティブにし、**Audio Source**を旧Playerと同じようにつけてください。そして、タグを**Player**にしましょう。**BodyCollider**が体本体だと思ってください。

旧Playerには体力を管理している**PlayerHearth.cs**、動きを管理する**PlayerMovement.cs**、発砲を管理している**PlayerShooting**がついています。

<img width="474" alt="スクリーンショット 2018-12-24 12.09.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/189146/a26a8add-311d-3154-150b-466c28872825.png">


##### 体力の管理

はじめに体力を管理させるため、**PlayerVR → SteamVR Objects**の**BodyCollider**に**PlayerHearth.cs**をつけてください。

次に、**PlayerHearth**を参照している部分を旧Playerから**BodyCollider**に変えましょう。
※_**EnemyManager**、**HUDCanvas**が参照しています。_


**PlayerHearth.cs**を修正します。
修正といっても、**アニメーション**、**PlayerMovement**、**PlayerShooting**関連のコードをコメントアウトしただけです。

```cs:PlayerHearth.cs
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.SceneManagement;

namespace CompleteProject
{
    public class PlayerHealth : MonoBehaviour
    {
        public int startingHealth = 100;                            // The amount of health the player starts the game with.
        public int currentHealth;                                   // The current health the player has.
        public Slider healthSlider;                                 // Reference to the UI's health bar.
        public Image damageImage;                                   // Reference to an image to flash on the screen on being hurt.
        public AudioClip deathClip;                                 // The audio clip to play when the player dies.
        public float flashSpeed = 5f;                               // The speed the damageImage will fade at.
        public Color flashColour = new Color(1f, 0f, 0f, 0.1f);     // The colour the damageImage is set to, to flash.


        // Animator anim;                                              // Reference to the Animator component.
        AudioSource playerAudio;                                    // Reference to the AudioSource component.
        // PlayerMovement playerMovement;                              // Reference to the player's movement.
        // PlayerShooting playerShooting;                              // Reference to the PlayerShooting script.
        bool isDead;                                                // Whether the player is dead.
        bool damaged;                                               // True when the player gets damaged.


        void Awake ()
        {
            // Setting up the references.
            // anim = GetComponent <Animator> ();
            playerAudio = GetComponent <AudioSource> ();
            // playerMovement = GetComponent <PlayerMovement> ();
            // playerShooting = GetComponentInChildren <PlayerShooting> ();

            // Set the initial health of the player.
            currentHealth = startingHealth;
        }


        void Update ()
        {
            // If the player has just been damaged...
            if(damaged)
            {
                // ... set the colour of the damageImage to the flash colour.
                damageImage.color = flashColour;
            }
            // Otherwise...
            else
            {
                // ... transition the colour back to clear.
                damageImage.color = Color.* (damageImage.color, Color.clear, flashSpeed * Time.deltaTime);
            }

            // Reset the damaged flag.
            damaged = false;
        }


        public void TakeDamage (int amount)
        {
            // Set the damaged flag so the screen will flash.
            damaged = true;

            // Reduce the current health by the damage amount.
            currentHealth -= amount;

            // Set the health bar's value to the current health.
            healthSlider.value = currentHealth;

            // Play the hurt sound effect.
            playerAudio.Play ();

            // If the player has lost all it's health and the death flag hasn't been set yet...
            if(currentHealth <= 0 && !isDead)
            {
                // ... it should die.
                Death ();
            }
        }


        void Death ()
        {
            // Set the death flag so this function won't be called again.
            isDead = true;

            // Turn off any remaining shooting effects.
            // playerShooting.DisableEffects ();

            // Tell the animator that the player is dead.
            // anim.SetTrigger ("Die");

            // Set the audiosource to play the death clip and play it (this will stop the hurt sound from playing).
            playerAudio.clip = deathClip;
            playerAudio.Play ();

            // Turn off the movement and shooting scripts.
            // playerMovement.enabled = false;
            // playerShooting.enabled = false;
        }


        public void RestartLevel ()
        {
            // Reload the level that is currently loaded.
            SceneManager.LoadScene (0);
        }
    }
}

```

これで、一度体験してみてください。
ゲーム空間に入れますが、移動も発砲することもできません。

##### テレポート

「Project」の「SteamVR　→　InteractionSystem　→　Teleport　→　Prefabs」の**Teleporting**を「Hierarchy」にドラッグアンドドロップしましょう。**Teleporting**は、VRでよくあるボタンお押している間に曲線が現れ、話すと曲線の先に移動するやつです。

**Teleporting**の**Teleport Action**を**Teleport**にします。

<img width="263" alt="スクリーンショット 2018-12-22 10.51.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/189146/105b0ae5-ccd8-4dfe-a687-170834d6dca5.png">

「Hierarchy」に新しく**Plane**を作成し、名前を**TeleportArea**としましょう。
**TeleportArea**は、**Teleporting**でテレポートできるスペースになります。
テレポート可能範囲として適切な大きさに調節し、高さを**0.1**ほど浮かせてください。浮かせないと床にめり込むためテレポートできません。

「Project」の「SteamVR　→　InteractionSystem　→　Teleport　→　Scripts」の**TeleportArea.cs**を**TeleportArea**につけてください。このスクリプトをつけることで実際に**TeleportArea**が**Teleporitng**でテレポートできるスペースになります。

<img width="1247" alt="スクリーンショット 2018-12-22 11.12.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/189146/3cbd6375-5793-3e3d-68da-119661494773.png">

ここで、一度体験してみてください。タッチパッドを押して離せばテレポートできるようになっているはずです。

##### 攻撃手段の追加

「Project」の「Scripts　→　Player」に**VRPlayerShooting.cs**を作成します。

「Hierarchy」の**PlayerVR　→　SteamVRObjects →　RightHand**に**Empty Object**を作成し、名前を**Gun**にして下さい。

旧Playerについていた**Particle System**、**Audio Source**、**Line Renderer**、**Light**を**Gun**につけて下さい。

**Gun**に**VRPlayerShooting.cs**をつけましょう。
PlayerShooting.csと基本的に中身は同じです。先ほど設定した　**Shoot**アクションで発砲できるように改変してあります。

```cs:VRPlayerShooting.cs
using UnityEngine;
using Valve.VR;

namespace CompleteProject
{
    public class VRPlayerShooting : MonoBehaviour
    {
        public SteamVR_Input_Sources HandType;
        public SteamVR_Action_Boolean ShootAction;

        public int damagePerShot = 20;
        public float timeBetweenBullets = 0.15f;
        public float range = 100f;


        float timer;
        Ray shootRay = new Ray();
        RaycastHit shootHit;
        int shootableMask;
        ParticleSystem gunParticles;
        LineRenderer gunLine;
        AudioSource gunAudio;
        Light gunLight;
        float effectsDisplayTime = 0.2f;

        void Awake ()
        {
            shootableMask = LayerMask.GetMask ("Shootable");
            gunParticles = GetComponent<ParticleSystem> ();
            gunLine = GetComponent <LineRenderer> ();
            gunAudio = GetComponent<AudioSource> ();
            gunLight = GetComponent<Light> ();
        }

        void Update ()
        {
            timer += Time.deltaTime;

            if (ShootAction.GetStateDown(HandType))
            {
                Shoot ();
            }

            if(timer >= timeBetweenBullets * effectsDisplayTime)
            {
                DisableEffects ();
            }
        }

        public void DisableEffects ()
        {
            gunLine.enabled = false;
            gunLight.enabled = false;
        }

        void Shoot ()
        {
            timer = 0f;

            gunAudio.Play ();

            gunLight.enabled = true;

            gunParticles.Stop ();
            gunParticles.Play ();

            gunLine.enabled = true;
            gunLine.SetPosition (0, transform.position);

            shootRay.origin = transform.position;
            shootRay.direction = transform.forward;

            if(Physics.Raycast (shootRay, out shootHit, range, shootableMask))
            {

                EnemyHealth enemyHealth = shootHit.collider.GetComponent <EnemyHealth> ();
                if(enemyHealth != null)
                {
                    enemyHealth.TakeDamage (damagePerShot, shootHit.point);
                }
                gunLine.SetPosition (1, shootHit.point);
            }
            else
            {
                gunLine.SetPosition (1, shootRay.origin + shootRay.direction * range);
            }
        }
    }
}

```

`SteamVR_Input_Sources`で左右どちらの**Controller**か、`SteamVR_Action_*`でなんの**アクション**かを定義できるようにします。今回は値が`boolean`の**アクション**なので、`SteamVR_Action_Boolean`を使用しています。

```cs
public SteamVR_Input_Sources HandType;
public SteamVR_Action_Boolean ShootAction;

```

定義した**アクション**がどの**Controller**で押されたかを検知するには、`GetStateDown`を使用します。ちなみに、ボタンを離した時を検知したい場合は`GetStateUp`です。

```cs
if (ShootAction.GetStateDown(HandType))
    {
        Shoot ();
    }

```


**Gun**についているVRPlayerShootingを以下のように設定しましょう。

<img width="350" alt="スクリーンショット 2018-12-22 13.40.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/189146/28432ba4-294d-75ec-44b3-327d88b41b5e.png">

これで、**Controller**のトリガーを引けば、**Shoot**アクションが発火し、発砲できるようになっています。

# 最後に

割と簡単にVR化できました。
本来はUI対応、テレポートの移動量制限等やらないといけないことはあるが　、力尽きたためできませんでした。

実際普通のゲームもVRゲームも作る難易度は変わらないと思います。

ビビらずVRゲーム作ってみませんか？