Try   HackMD

SteamVR でゲームを作る

はじめに

この記事はCPS Lab Advent Calendar2020の14日目の記事です。

ここ 1 年、SteamVR と戦ってきたので、備忘録的に書こうと思っています。
去年は技術記事を用意できなかったので、今年こそは・・・と。
なにかのお役に立てると嬉しいです。
中身としては個人の備忘録としてみてください。

SteamVR でゲームを作る

簡単にですがこんなゲームを作っていこうと思います。(長くなってしまって後悔してます)

SteamVRとは

Steam の開発元の Valve が提供する VR の実行環境です。Steam からインストールできます。
現在はこれが主流となっていて、多くの VR コンテンツが対応しています。

環境

SteamVR がインストールされている前提で進めます。

必要なもの

  • Unity 2019.4系
    • 2020 だと VR の環境が異なるため、今回は 2019.4.12f を使用します。
    • (自分がまだ導入できていないので・・・)
  • SteamVR 対応の HMD
    • 今回は Oculus Quest で Oculus Link を用いた環境で行います。

Oculus Quest に 対応するケーブルをつなぐことで、PCVR と同じ環境を再現するシステムです。これによって、Oculus Rift のコンテンツや、SteamVR で遊ぶこともできます。
対応するケーブルは PCにつなぐ側(USB-A)- Quest につなぐ側(USB-C)で、長さは 3m 以上が推奨されています。

プロジェクトの用意

  • VR 機器をつないで、SteamVR を起動しておく。
  • UnityHub にここからデータをダウンロードし、リストに追加を行う。
  • プロジェクトを開く。
  • ctrl + 9 を押して、AssetStore から SteamVRPlugin をダウンロード。
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
  • インポートを選択し、そのままインポートします。
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
  • 表示される設定を適用します。
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

(僕の環境で出なくなってしまったので補足)

  • 1・3・4 番目を変更します(2 番目は有償ライセンス持ちの方のみ変更できます)。

Build Target

  • File - BuildSettings を開く。
  • Platform を 「PC, Mac & Linuc Standalone」に変更し、Archtexture を「x86_64」に変更する。
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

ResizableWindow、Color Space

  • そのまま左下の PlayerSettings を開く
  • 「Resolution and Presentation」から、Resizable Window を探しチェックを入れる。
  • また、「Other Settings」から、Color Space を Linear に変更する。
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

コントローラーのバインドを設定する

Window - SteamVR Input を開く

  • デフォルト設定をコピーしますか?と聞かれますが、今回はごちゃごちゃになるのを避けるため No を選択し、コピーしません。

VRThrow という名前の ActionSet を作成

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Actions の欄で、+ を押して Action を 3 つ作成し、「Save and generate」

  • grab
    • Type : boolean
  • reset
    • Type : boolean
  • pose
    • Type : pose

「Open binding UI」を開く。
バインドの新規作成

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • トリガーで+で新規作成
    • ボタンとして使用
    • クリックを grab に設定
  • ジョイスティックで+で新規作成
    • ボタンとして使用
    • クリックを reset に設定
  • 真ん中のアクションポーズの編集
    • 左手 未加工 を pose に設定

設定が終わったら、デフォルトバインドの置換 を押して保存

シーンの土台を作る

新しくシーンを作成し、プロジェクトウィンドウのSteamVR - Prefabから、CameraRigをシーンに出します。
CameraRigにはカメラオブジェクトが含まれているため、すでにあるMainCameraを削除します。
Player という名前のEmptyObjectを(0 ,0 ,0)に作成しその下にCameraRigを配置します。
Environment プレハブ、Canvas プレハブをプロジェクトウィンドウの Prefabs からシーンに配置します。
Lighting は今回は AutoGenerate でお茶を濁します・・・。
(ここまでの手順はテンプレートに含まれています)

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

プレイヤーとコントローラーの設定を行う

CameraRigを展開し、Controller のSteamVR_Behaviour_Poseの PoseAction に\Actions\{ゲーム名}\in\poseを設定します。デフォルト設定をコピーしていないため、手動で設定が必要です。
そして、それぞれにHandスクリプトをアタッチします。

  • OtherHand に他方の手を設定
  • HandType に該当する手を設定
  • TrackedObject にすでにアタッチされているSteamVR_Behaviour_Poseを設定
  • GrabGripAction に、grab アクションを設定
  • RenderModelPrefab に、Asset からcontrollerを設定
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

Player に Playerスクリプトをアタッチします。

  • Hands を展開し、size に 2 と入力し、Controller を2つとも設定
  • RigSteamVR に CameraRig を設定
  • Rig2DFallback にも CameraRig を設定
    • (本来、VR が使えないときなどに起動するものらしいですが、今回はごまかします)
  • AudioListener に CameraRigの Camera を設定します。
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

ボールを作る

Sphere を作成し、ModalThrowableスクリプトをアタッチします。関連するスクリプトが自動的にたくさんアタッチされます。

  • Release Velocity StyleAdvancedEstimationに変更
  • Scale Release Velocity を 3 くらいに設定(任意ですが、低いと飛びません)

ボールのスクリプトを作る

ボールの機能を実装します。

  • ボールはターゲットにあたった場合に、スコアのカウントを増やします。
  • また、リトライボタンにあたった場合に、進行状況をリセットします。
  • ボールは、オブジェクトにあたった場合や、地面に接したときに消滅します。
using UnityEngine; public class Ball : MonoBehaviour { readonly string targetTag = "Target"; readonly string retry = "RetryButton"; readonly string ground = "Ground"; private void OnTriggerEnter(Collider other) { if (other.gameObject.CompareTag(targetTag)) { Destroy(other.gameObject); StruckOutManager.CountPlus(); Destroy(gameObject); } if (other.gameObject.name.Equals(retry)) { StruckOutManager.Retry(); Destroy(gameObject); } } private void OnCollisionEnter(Collision collision) { if (collision.gameObject.name.Equals(ground)) { Destroy(gameObject); } } }

スクリプトをボールにアタッチしたら、Ball としてプレハブ化します。

オブジェクト生成管理をするスクリプトを作る

ターゲットやボールの生成管理を実装します。
(簡単のために一部ハードコード)

  • ボールは、全て消滅していることを確認してから生成
  • ターゲットは、残っていた場合は削除してから生成
using UnityEngine; public class ObjectSpawner : MonoBehaviour { [SerializeField] GameObject ballPrefab, targetPrefab; const string BallTag = "Ball"; const string Target = "Target"; static readonly Vector3 SpawnPoint1 = new Vector3(0, 0.6f, 0.8f); static readonly Vector3 SpawnPoint2 = new Vector3(0.4f, 0.6f, 0.8f); static readonly Vector3 SpawnPoint3 = new Vector3(-0.4f, 0.6f, 0.8f); static readonly Vector3 targetPoint = new Vector3(0, 3, 10); public void RespawnBalls() { if (!CheckEmpty(BallTag)) return; Instantiate(ballPrefab, SpawnPoint1, Quaternion.identity); Instantiate(ballPrefab, SpawnPoint2, Quaternion.identity); Instantiate(ballPrefab, SpawnPoint3, Quaternion.identity); } public void RespawnTarget() { Destroy(GameObject.FindGameObjectWithTag(Target)); Instantiate(targetPrefab, targetPoint, Quaternion.identity); } bool CheckEmpty(string name) { if (GameObject.FindGameObjectsWithTag(name).Length == 0) { return true; } else { return false; } } }

ゲームの進行管理をするスクリプトを作る

ゲームの全体の流れを管理するスクリプトを作成します。

  • ゲーム状態とリザルト状態を切り替える
  • テキスト表示
  • スコアの管理
  • コントローラー入力の管理
using UnityEngine; using Valve.VR; using TMPro; public class StruckOutManager : MonoBehaviour { /* * ゲームの状態管理を行う。 */ private enum View { Game, Result } [SerializeField] TMP_Text scoreText; [SerializeField] GameObject GameView, ResultView; SteamVR_Input_Sources resetButton = SteamVR_Input_Sources.Any; SteamVR_Action_Boolean reset = SteamVR_Actions.VRThrow.reset; private static int score; private const int Point = 10; private static int viewIndex; private static ObjectSpawner spawner; private void Awake() { // スポナーの初期化 spawner = GetComponent<ObjectSpawner>(); score = 0; viewIndex = 0; if (scoreText == null) { scoreText = GameObject.Find("ScoreText").GetComponent<TMP_Text>(); } } private void Start() { spawner.RespawnBalls(); spawner.RespawnTarget(); scoreText.text = score.ToString(); GameView.SetActive(true); ResultView.SetActive(false); } private void Update() { switch (viewIndex) { case (int)View.Game: ResultView.SetActive(false); GameView.SetActive(true); break; case (int)View.Result: GameView.SetActive(false); ResultView.SetActive(true); break; } if (reset.GetStateDown(resetButton)) { spawner.RespawnBalls(); } scoreText.text = score.ToString(); if(score > 80) { viewIndex = (int)View.Result; } } /// <summary> /// スコアのカウントをプラスする。 /// </summary> public static void CountPlus() { score += Point; } public static void Retry() { score = 0; viewIndex = 0; spawner.RespawnBalls(); spawner.RespawnTarget(); } }

ゲームの進行管理オブジェクトを作成する

CreateEmpty で空オブジェクトを作成します。
先程作った、ObjectSpawner.csと、StruckOutManager.csをアタッチします。

ObjectSpawner の設定をしていきます。

  • BallPrefab に先程作った Ball を設定
  • TargetPrefab に Target プレハブを設定

StruckOutManager の設定を行います。

  • ScoreText にCanvasプレハブのCanvas - GameCanvas - Panel - ScoreTextを設定
  • GameView にCanvasプレハブのCanvas - GameCanvasを設定
  • ResultCanvas にCanvasプレハブのCanvas - ResultCanvasを設定

Canvas は、RenderMode を WorldSpace にすることで画面内に表示します。

ゲームを実行する

遊びます。動くことを確認できたら、ビルドします。シーンの追加を忘れずに。

おわりに

だいぶごまかしていたり、半ば自分用のような書き方でしたが、参考になればうれしいです。
動かないよ!などあったらお気軽にご連絡ください・・・。

もしこうしたらより良いよみたいなのあったら教えて下さい。


参考