[STYLY] VR内で動く乗り物を作ろう


![CarDriving-w500.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/785449b5-ec81-5ae4-ae8d-b5290d94e74b.gif)

https://gallery.styly.cc/scene/b65d9ac6-740c-4c46-bd50-6122fc2f6851

# はじめに

この記事は [STYLYアドベントカレンダー](https://qiita.com/advent-calendar/2021/styly) 19日目向けの記事です。

STYLYは、VRやARのシーンをWebから簡単に作れて様々なデバイスで動かすことができるプラットフォームですが、自分自身の移動方法はデバイスごとに決まったものが用意されているのみです。

でもVR内で乗り物に乗りたい、と思うこともあると思います。
そこで、本記事では、自分で操作できる乗り物を作ってみます。

なお、PlayMakerアセット（有償）を利用しますのでご注意ください。
また、このあたりのSTYLYの仕様は今後変化する可能性があります。

# ターゲットユーザー

本記事は、以下のようなユーザーを対象にしています。

* Unityの基本的な利用方法を知っている
* STYLYへのアップロード方法を知っている
* PlayMakerの基本的な使い方を知っている


# 必須ツール

本記事の手法には以下の環境が必要です。

* Unity (2019.4.29f1推奨)
* [PlayMakerアセット](https://assetstore.unity.com/packages/tools/visual-scripting/playmaker-368?locale=ja-JP)（有料）


# STYLYの既存の移動方法

STYLYではデフォルトとして以下の移動方法を利用できます。

* VR向け: テレポート + その場回転
* Web向け: W/A/S/Dキーによる前後左右の移動 + ドラッグで回転
* スマホ向け: 前進ボタン （セッション時はジョイパッド移動）+ スワイプで回転

# VR酔いに関する注意

VR版STYLYで採用されている移動手法は瞬間的なテレポートですが、これは非常にVR酔いしづらい移動方式です。しかし、今回作成するような、乗り物のようなスムーズに自分が動くような移動方式は、一般的にいってVR酔いしやすい移動方法といえます。特に、プレイヤーが想定していない方向やタイミングで移動したり、フレームレートが低くてガタガタしたりといったケースで特にVR酔いが発生しやすくなります。この点注意して作成していただければと思います。

また、作成する立場ではかなりVR酔いに見舞われやすいことにもご注意ください。


# 今回作成する乗り物アセットについて

理解のしやすさを考慮して、今回はXZ平面上で移動する乗り物を作成します。
障害物や地形との当たり判定などは行いません。
乗り降りについても今回は作りません。
また、STYLYの仕様の関係で、残念ながらスマホ版では挙動がおかしくなります。

## 乗り物の加減速について

シンプルな方法としたいので、VRコントローラーのトリガーまたはスペースキーを押すと加速、離すと減速とします。[^1]
VRデバイスによってはトリガーがないケースなどがありますが、今回は対応外になります。

[^1]: トリガーのないコントローラーを利用するデバイスが一部ありますが、目をつぶることにします。

### 仕組みについて

VR環境およびマルチプレイヤー版のWeb環境では、 `[VRTK]_VIVE` という名前のゲームオブジェクトを取得し、毎フレーム移動させることでプレイヤーが移動します。Web用のPlayer環境では、メインカメラを取得し、移動させます。
Studioでも同様のカメラ動作が実行されるため、編集操作が難しくなる点にはご注意ください。


## 乗り物の向きの変更について

乗り物の向きの変更操作をどうするかは悩ましい点があります。通常の操作系がそのまま効いているため、それとバッティングする形で強制的に方向を変更するなどすると、操作性が悪くなりますし、VR酔いの原因にもなります。

このため今回は、通常の回転操作をそのまま利用することにします。各デバイスはその場回転の方法があるので、それをそのまま乗り物の方向にします。VRでは顔にくっついたような印象が出るなどいくぶん違和感がありますが、許容することにします。

### 仕組みについて

メインカメラの向き（＝プレイヤーの向き）を取得し、乗り物モデルの向きが同じ方向になるように変更します。

## 乗り物の移動方向について

トリガー（またはスペースキー）を押すと、乗り物の前方向に進むようにします。

### 仕組みについて

乗り物モデルのローカル座標系での前方向 (0.0.1) を `Transform Direction` アクションを利用してワールド座標に変換することで、ワールド座標における乗り物の方向ベクトルを算出します。これに速度を掛けることでプレイヤーの移動量を算出し、プレイヤーの座標に加算します。


# 作業の流れ

1. アップロード環境準備
1. 乗り物モデルの準備
1. 乗り物モデルの配置
1. デバッグ用メッセージテキストの作成
1. 乗り物FSMを作る：車が動くようにする
1. 乗り物FSMを作る：自分が動くようにする
1. STYLYにアップロード
1. STYLY Studioで配置
1. 風景モデルの追加


## 0. 完成パッケージ

以下の作業を行いたくない方用、あるいは見比べたい方用に、できあがったunitypackageを用意しました。
[STYLYCarRideSample.unitypackage](https://drive.google.com/u/1/uc?id=1LXHmDfkO5sxfqMHG9vjSlixVfl5BFbmK&export=download)

### 完成パッケージ利用方法

* 以下の1.と2.の作業を行います
* `STYLYCarRideSample.unitypackage` をUnityにインポートします
* `Assets/CarRide/CarRoot.prefab` をSTYLYにアップロードします
* STYLY Studioを起動して、アセット追加画面で、`3D Model` -> `My Models & Upload` より、任意のシーンに配置してください

## 1. アップロード環境準備

### 1.2. STYLY-Pluginのインストール

STYLYのアップロード環境を作成します。
[STYLY Plugin For Unity](https://styly.cc/ja/download/) をダウンロードし、セットアップしましょう。

こちらの記事が参考になります。
https://styly.cc/ja/manual/unity-asset-uploader/

### 1.3. PlayMakerのインストール

上記のUnityプロジェクトに、[PlayMakerアセット](https://assetstore.unity.com/packages/tools/visual-scripting/playmaker-368?locale=ja-JP)（有償）をインストールします。
以下の記事が参考になると思います。
https://styly.cc/ja/tips/unity-playmaker-game-beginner-1/

## 2. 乗り物モデルの準備

今回は平面上を移動するので、車両などのモデルを準備しましょう。

今回は、こちらの無料アセット `3D Low Poly Car For Games (Tocus)` を利用してみましょう。
https://assetstore.unity.com/packages/3d/vehicles/land/3d-low-poly-car-for-games-tocus-101652?locale=ja-JP

アセットをダウンロードし、プロジェクトにインポートします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/9a0fa9fd-5752-5389-a1db-7cf58b9f8b58.png)


乗り物はなんでも良いですが、運転手から外が見やすいもののほうが、位置合わせがシビアにならないので良いと思います。また、描画負荷が軽いものが良いでしょう。

### 2.1 窓マテリアルの透明度を上げる

窓から外を見る形になるので、窓ガラスの透明度を上げます。Tocus_Windows.mat を選択して、Albedoの色の値を編集し、A (Alpha)の値を20ぐらいにしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/820ff17c-282c-9ae4-32cd-2db6b4392b5d.png)


## 3. 乗り物モデルの配置

ここまででアセットの準備はできました。
新しいシーンを作って作業していきましょう。

* シーン作成: `CarRide` フォルダ内 `CarRide` シーン

乗り物のルートオブジェクトを作成します。

* ヒエラルキーに `Create Empty` で `CarRoot` オブジェクトを作成します。
* `CarRoot` は以下の座標にします。
    * Position: 0,0,0
    * Rotation: 0,0,0
    * Scale: 1,1,1

車のモデルオブジェクトを`CarRoot` オブジェクトの子に配置します。

* `Assets/Car_Low/Tocus/Prafab/Tocus` プレハブを `CarRoot` にドロップして配置します。
* `CarRoot` の子になるようにします。
* `Tocus` の座標は以下のようにします。
    * Position: 0,0.38,0
    * Rotation: 0,0,0
    * Scale: 1,1,1

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/a9048cd3-c442-defd-223e-ce5af784b981.png)

※ 今回の3Dモデルでは上記の値が良いですが、ほかの3Dモデルを利用する場合は、以下の条件になるように調整しましょう

* 正しいスケールになるようにする（Unityの1単位が1mと考えて、正しい大きさにする）
* 地面に接地するようにする

## 4. デバッグ用テキストUIを配置する 

PlayMakerが複雑になってくると、実際にちゃんと動いているか気になってくると思います。
このため、PlayMakerから状態を表示するためのテキスト領域を作っておきましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/194a6512-6795-4b4b-8e4b-4df3ee6f969e.png)

* `CarRoot` の子に、`UI` カテゴリ内の `Canvas` を追加します。
  * シーン直下に作成する方法もありますが、GameObjectをprefab化したときにリンク切れすることがあるのでご注意ください。
* Canvasの`Render Mode` を `World Space` に変更
* キャンバスのパラメータは以下のようにします。
   * Scale: 0.1, 0.1, 0.1
   * Width: 1000
   * Height: 400
   * Pos X: 0
   * Pos Y: 15
   * Pos Z: 60
* こうすることで、乗り物の前方に常時確認できる確認できる大きいキャンバスが作成されます。
* このキャンバスの子に `UI` カテゴリの `Text` オブジェクトを追加します。
   * オブジェクト名は `MessageText` に変更します。
   * 配置場所は自由ですが、キャンバス上半分のどこかにしておくと良いでしょう。
        * 座標例
            * PosX: 0
            * PosY: 170
            * PosZ: 0
            * Width: 400
            * Height: 60
   * Color は 白色にすると見やすいことが多いです
   * `Best Fit` をONにしておくと、適切なフォントサイズに調整してくれるので便利です。
   * `Text` の初期値は `Message` とでもしておきましょう。
* `Canvas` および `MessageText` の Layer 設定をDefaultにしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/b409c11d-d111-7aba-b860-809339e1f343.png)


## 5. 乗り物FSMを作る

### 5.1. トリガーハンドラを作る

VRでは、コントローラーのトリガーで加速し、トリガーを離すと減速するようにしたいのですが、STYLYではトリガーの情報はイベントの形で伝わってきます。
このため、PlayMakerのFSMを利用して、ONとOFFの２つの状態を持ち、その間をイベントで遷移するようなFSMを作りましょう。
現在、どちらの状態になっているかは、`IsTriggerDown` 変数に保存しておくことにします。

#### オブジェクトとFSMの追加

`CarRoot` の下に `Create Empty` で `TriggerHandler` ゲームオブジェクトを作成します。

* ゲームオブジェクト追加
  * 親: `CarRoot` ゲームオブジェクト
  * 名前: `TriggerHandler`
  * コンポーネント: `PlayMakerFSM`

#### FSMにトリガーイベントを追加

PlayMakerFSMコンポーネントの`Edit`ボタンを押し、`Events`タブの`Add Event` の欄を使って、以下の４つのイベントを登録します。文字列を正確に入力しましょう。

* `Global_TriggerPressDown_L`
* `Global_TriggerPressDown_R`
* `Global_TriggerPressUp_L`
* `Global_TriggerPressUp_R`

左端のチェックボックスはGlobalイベントのチェックボックスですが、チェックしなくても良いです。

イベントが登録されると、以下のようになります。
&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/c05d144e-20e1-3a4d-07e6-0e551596232e.png)

#### FSMに変数を追加

以下の変数を追加 `Add` しましょう。

* New Variable: `IsTriggerDown`
* Variable Type: Bool
* Input: OFF
* Output: ON

※OutputをONにすると、PlayMakerFSMコンポーネントのOutput欄に値が表示されます。

&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/97fc2053-c4e9-5265-535b-07acc07a91ba.png)


#### FSMの遷移を作成

デフォルトのStateの名前を `TriggerUp` に変更し、新規ステートを追加して名前を `TriggerDown` としましょう。

各ステートの `Add Transition` メニューを使って、２つのステートのトランジションを以下のように作成します。

* `TriggerUp` ステート
    * `Global_TriggerPressDown_L` イベント: `TriggerDown` ステートに遷移
    * `Global_TriggerPressDown_R` イベント: `TriggerDown` ステートに遷移
* `TriggerDown` ステート
    * `Global_TriggerPressUp_L` イベント: `TriggerUp` ステートに遷移
    * `Global_TriggerPressUp_R` イベント: `TriggerUp` ステートに遷移

&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/6db67dd8-bb09-d607-224b-3a0ea0e2b568.png)

また、各ステートにアクションを設定し、`IsTriggerDown` 変数の値をセットします。

* `TriggerUp` ステート
    * `Set Bool Value` アクション
        * Bool Variable: `IsTriggerDown`
        * Bool Value: OFF
        * Every Frame: OFF

&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/0def9102-d37a-a62d-0e03-cb2c62fd4da0.png)

* `TriggerDown` ステート
    * `Set Bool Value` アクション
        * Bool Variable: `IsTriggerDown`
        * Bool Value: ON
        * Every Frame: OFF

&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/f925a9d5-f791-3562-ce86-85f61c01725f.png)


#### デバッグ用の仕掛け

このイベントはVR版STYLYでしか発生しませんので、実機で本当に動作しているか確認するのはちょっと難しいことになります。しかし、前の項目で作成したTextを利用してこれを可視化することで、自信をもって作成することができます。

`TriggerUp` ステートを通ったときに、キャンバスに配置したメッセージテキストに `Trigger Up` というテキストを表示し、`TriggerDown` ステートを通ったときに `Trigger Down` と表示すれば、実際にこの遷移がうまくいっていることがわか
ります。

`UI Text Set Text` アクションを以下のように追加することで、これを実現することができます。

* GameObject: MessageText オブジェクト
* Text: `Trigger Up` （TriggerDownのほうのステートでは `Trigger Down` にする）

&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/e5f4db06-602b-d932-c12f-53656e0a8c0e.png)

また、このような場合に **Local Preview Tool** が役にたちます。ブラウザーでSTYLY Studioを使うことなく、即座にSTYLYアプリでシーンの動作を確認することができます。ビルドの時間も短いので、実機確認の手間を大幅に短縮できます。

`STYLY` メニュー →　`Local Preview Tool` で起動できます。

Local Preview Toolの画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/05864c59-4e42-a857-9875-5f5326103086.png)


Local Preview Toolを使ってQuest2で動作確認する様子

&emsp;![TriggerUpDown.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/706b213c-09e8-b8b7-a912-2644fef09f7b.gif)

参考: [Local Preview Toolの紹介](https://styly.cc/ja/manual/styly-local-preview-tool/)

### 5.2. スピードコントローラーを作る

乗り物の速度を上げ下げする仕掛けを作ります。
スペースキーまたはVRコントローラーのトリガーがおされていたらスピード値をアップ、押されていなかったらスピード値をダウンするようにします。これを秒間１０回行うことにします。
また、スピード値は下限が０、上限をMaxSpeed変数の値とします。
スピード値はSpeed変数で表して、外のFSMから参照することにします。

まず、`CarRoot` ゲームオブジェクトの子に、`SpeedController` ゲームオブジェクトを追加しましょう。

* ゲームオブジェクト追加
  * 親: `CarRoot` ゲームオブジェクト
  * 名前: `SpeedController`
  * コンポーネント: `PlayMakerFSM`

FSMを以下のように編集します。

#### 変数の作成

Variables タブを選択して、以下のような変数を追加します。

* `IsSpaceKeyDown` 
    * スペースキーを押しているかを保持します。
    * Variable Type: Bool
* `IsTriggerDown`
    * VRコントローラーのトリガーを押しているかを保持します。
    * Variable Type: Bool
* `Speed`
    * 現在の速度値を保持します。
    * Variable Type: Float
    * **Output: ON**
        * PlayMakerFSMコンポーネントのインスペクター上で変数の値を確認できるようにします。
* `MaxSpeed`
    * 速度の最大値を保持します。
    * Variable Type: Float
    * Value: 5
      * ※ 0ではない値をセットしておきましょう。
    * Input: ON

&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/8e2ce5bb-5dcc-1ab6-f899-dde9321a830b.png)


#### ステートの作成およびアクションの追加

以下のようにステートを作成します。

* 初期ステート 名前: `GetKey`
    * スペースキーか、またはトリガーの状態がONであるかどうかを判定します。
    * トリガーの状態は、`Get Fsm Bool` アクションで、 TriggerHandler オブジェクトのFSMから取得します。
    * アクション
        * **アクション名: Get Key**
            * Key: Space
            * Store Result: IsSpaceKeyDown
        * **アクション名: Bool Test**
            * Bool Variable: IsSpaceKeyDown
            * Is True: `IS_ACCEL_DOWN`
                *  New Event... から新規イベントを作成
        * **アクション名: Get Fsm Bool**
            * GameObject: TriggerHandler
            * Fsm Name: FSM
            * Variable Name: IsTriggerDown
            * Store Value: IsTriggerDown
        * **アクション名: Bool Test**
            * Bool Variable: IsTriggerDown
            * Is True: IS_ACCEL_DOWN
                *  (上記で作成したイベントを利用）

&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/dab619e8-1d27-e917-ef20-842ddbcf33cc.png)
※ 遷移をつなぐ前は、赤文字で `Event not used by...` のような警告が出ますが気にしなくて良いです。

* ステート `SpeedUp`
    * アクション
        * **アクション名: Float Add**
            * Float Variable: Speed
            * Add: 0.5
                * アクセルONのときの速度の上がりやすさの値です。
        * **アクション名: Float Clamp**
            * Float Variable: Speed
            * Min Value: 0
            * Max Value: MaxSpeed

&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/7b7f8cb7-47b8-a689-ba3f-e184d7bb560e.png)

* ステート `SpeedDown`
    * アクション
        * **アクション名: Float Subtract**
            * Float Variable: Speed
            * Subtract: 1
                * アクセルOFFのときの速度の下がりやすさの値です。
        * **アクション名: Float Clamp**
            * Float Variable: Speed
            * Min Value: 0
            * Max Value: MaxSpeed

&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/c9bc78de-9ab0-10cd-37c7-256c7aa6ade1.png)

* ステート `Wait`
    * アクション
        * **アクション名: Wait**
            * Time: 0.1

&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/a088ee29-58b8-8b39-7f85-d823c63f62b4.png)

#### ステートの遷移を設定

以下のような遷移を設定します。

* ステート: `GetKey`
    * `IS_ACCEL_DOWN` イベント : ステート `SpeedUp` へ遷移
    * `FINISHED` : ステート `SpeedDown` へ遷移
* ステート `SpeedUp`
    * `FINISHED` イベント : ステート `Wait` へ遷移
* ステート `SpeedDown`
    * `FINISHED` イベント : ステート `Wait` へ遷移
* ステート `Wait`
    * `FINISHED` イベント : ステート `GetKey` へ遷移

こちらのようになります。
&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/367c2870-ad48-9a79-ddbd-1b02b1f43536.png)


#### 動作確認

ここまでで、SpeedController の `Speed` 変数の上げ下げができたはずです。
UnityのPLAYボタンを押して試してみましょう。

Game Viewをフォーカス（クリック）してスペースキーを押すと、 SpeedController内のPlayMakerFSMのOutputのところにある `Speed` の値が徐々に上がって 5 になり、スペースキーをはなすと `Speed` の値が徐々に下がって０になるはずです。

&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/a9b23c1b-868c-c337-a57a-532362ea8f7e.png)


### 5.3. 車の移動を実装

ここまでで、速度のコントロールができたので、これを車に適用しましょう。

処理の流れは以下のようなものです。

1. VRでのカメラリグ（足元位置）を代表する "[VRTK]_VIVE" ゲームオブジェクトを見つけます。みつからない場合、つまりWeb環境等の場合は Main Camera を見つけます。
    * 細かい話ですが、ここでカメラの地面からの高さを補正する変数を設定します。"[VRTK]_VIVE" の場合、地面の高さにセットすればカメラは身長分高いところに置かれますが、Main Cameraの場合は、頭の高さに配置する必要があるためです。
    * 「カメラ座標のオフセット値」の変数を定義し、Main Cameraの場合はこの値を (0, 1.6, 0) とします。"[VRTK]_VIVE" の場合は (0, 0, 0) にします。
2. 車の前方向 (0,0,1) の向きを Transform Direction アクションでグローバル座標に変換します。これで車の進行方向のベクトルを得ることができます。
3. 上記の値に速度値をかけます。速度ベクトルが求まります。
4. 車の座標値に、速度ベクトルを１フレーム分追加して、車の座標値を更新します。
5. 車の座標値に、1. で求めた「カメラの高さ方向オフセット値」を追加して、カメラの座標とします。
6. 求めたカメラの座標を "[VRTK]_VIVE" または Main Camera にセットします。

2～6の処理は、毎フレーム実行されるようにします。

#### `PlayerTarget`ゲームオブジェクト（足元位置）追加

`CarRoot` の子に、`PlayerTarget` ゲームオブジェクトを追加します。
これは、プレイヤーの足元位置を表します。この座標に身長分の高さを加算したあたりに実際のカメラが配置されることを意識して配置しましょう。（身長はHMDデバイスの設定によります）

* `3D Object` -> `Cube` ゲームオブジェクト作成
  * 親: `CarRoot`
  * 名前: `PlayerTarget`
  * 座標: (-0.3, -0.45, 0)
  * スケール: (0.1, 0.1, 0.1) 

ドライバーの頭の位置から逆算して、Y座標をおおよそ身長分下におろした位置に設定します。

※Cubeは位置の目安用のみです。

#### FSM設定

`CarRoot`ゲームオブジェクト に PlayMakerFSM コンポーネントを追加し、編集します。

#### FSM変数設定

以下のような変数を設定しましょう。

* `CameraOffset`
  * Variable Type: Vector3
  * VR時のカメラリグと、Gallery内プレイヤーやStudioで取得できるメインカメラとでは基準位置が違うので、これを補正するための座標値です。
* `CameraRig`
  * Variable Type: GameObject
  * プレイヤーを動かすためのカメラリグオブジェクトですVRの場合とGallery内プレイヤーやStudioとでは中身が異なります。
* `CurrentPos`
  * Variable Type: Vector3
  * 現在座標を一時的に保持する変数です。
* `CurrentTargetPos`
  * Variable Type: Vector3
  * カメラリグの補正された位置を一時的に保持する変数です。
* `ForwardDir`
  * Variable Type: Vector3
  * 乗り物の前方方向を保持する変数です。
* `MoveDiff`
  * Variable Type: Vector3
  * １フレーム分の座標変化を保持する変数です。
* `Speed`
  * Variable Type: Float
  * SpeedControllerの速度値を一時的に保持します。

&emsp;![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/f87f09f5-3b4f-971b-b480-5a7c83a62455.png)

#### FSM `GetCameraRig` ステート

初期ステートの名前を `GetCameraRig` と変えましょう。

内容は以下の画像のようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/8326318d-b2b7-23e5-a37e-9b8a36993a69.png)

Find Game ObjectのObject Nameは `[VRTK]_VIVE` です。打ち間違いのないようにしましょう。
`CAMERA_RIG_FOUND` イベントはここで新規作成します。

ステートの内容としては、`[VRTK]_VIVE` があればそれを `CameraRig` ゲームオブジェクトにセットし、なければ Main Camera を `CameraRig` ゲームオブジェクトにセットします。
Main Cameraを利用する場合は、CameraOffset に (0, 1.6 ,0) という値をセットします。

#### FSM `UpdateCameraRig` ステート (1)

このステートで、乗り物本体の動きを決定し、さらに乗り物の動きをカメラリグに反映します。
アクションが多いので３パートに分けて紹介します。

このステートではすべてのアクションについて `Every Frame` フラグをONにして、毎フレーム実行するようにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/f31029f5-9362-c742-a724-c1ac64a9d0b0.png)

※Commentアクションは説明用なのでなくても大丈夫です

まず、`Get FSM Float` カスタムアクションを利用して、 `SpeedController` ゲームオブジェクトのFSMから`Speed`変数の値を取得して`Speed`変数にセットしています。

その後、乗り物の前方向のベクトルを取得します。`Transform Direction` アクションを利用して、Ownerゲームオブジェクトの(0,0,1) ベクトルをワールド座標に変換することで、ワールド座標上での乗り物の前方向ベクトル `ForwardDir` が得られます。
この値を `MoveDiff` 変数にセットし、`Speed` を掛けることで速度ベクトルを取得します。

#### FSM `UpdateCameraRig` ステート (2)

乗り物の座標に加算することで動かします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/8d152bb2-7d2f-6192-272d-b87909ce7532.png)

速度ベクトル`MoveDiff`を利用して、乗り物の座標を更新します。
`Vector3 Add` を利用して、 移動後の座標を計算し、Ownerにセットします。
`Vector3 Add` に `Per Second` フラグをセットしておくと、`MoveDiff` の値を一秒あたりに進む距離（＝速度）として扱うことができます。各フレームの経過時間を考慮しなくて良くなります。

#### FSM `UpdateCameraRig` ステート (3)

乗り物の位置をカメラリグに反映させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/4bd6c4aa-5166-524d-a3a6-15f8eb9e751f.png)

現在の乗り物の位置を取得し、`CameraOffset`の値を加算し、カメラリグのゲームオブジェクトにセットします。

#### ステート遷移の追加

以下のように遷移を追加しましょう。 

* `GetCameraRig` ステート
    * `CAMERA_RIG_FOUND` イベント時: `UpdateCameraRig` ステートに遷移

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/dc190a7a-413d-72a0-20ae-32e7ae6e44cf.png)

#### 動作確認

ここまでで、車が前方に動くようになったはずです。UnityのPLAYボタンを押して確認してみましょう。
Game Viewをフォーカス（クリック）してスペースキーを押すと前に進むはずです。
進んでいるかどうかわかりにくい場合は、シーンにCubeなどを配置してみると良いでしょう。

### 5.4. 車の回転を実装

ここまでで車を移動させることはできましたが、一方向に移動するだけになっていて、曲がることができません。
そこで、回転方法を実装します。

今回は比較的シンプルな方法とします。Main Cameraの向きを取得し、乗り物の向きに反映させます。

#### `RotationController` ゲームオブジェクトの追加

* ゲームオブジェクト追加
  * 親: `CarRoot` ゲームオブジェクト
  * 名前: `RotationController`
  * コンポーネント: `PlayMakerFSM`

#### FSM変数の設定

以下のように変数を設定します。

* `Camera`
  * Variable Type: GameObject
  * 回転の値を取得するゲームオブジェクトです。カメラが入ります。
* `CameraRotationY`
  * Variable Type: Float
  * カメラの回転角度の値です。
* `Car`
  * Variable Type: GameObject
  * Input: ON
  * 回転させる乗り物のゲームオブジェクトです。
  * 値に `CarRoot` ゲームオブジェクトをセットしておきます。 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/3b46e34f-a5db-0146-27c4-9ea859db36db.png)

#### FSM `GetCamera` ステート

初期ステートをこの内容に変更します。
Main Cameraを取得して `Camera` 変数にセットします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/37c8a3ab-a320-9297-c82e-1a366c6eda5f.png)

#### FSM `UpdateRotation` ステート

GetRotationでカメラの回転角度を取得し、これを `Car`変数のゲームオブジェクトにセットします。
注意点としては、 `Every Frame` フラグをONにしておくことで、状態遷移せずに毎フレーム処理を実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/f3c2176c-e6c3-54fc-2f49-774c9d03f928.png)

#### FSM ステート遷移の設定

* `GetCamera` ステート
  * `FINISH` イベント時: `UpdateRotation` ステートに遷移

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/85570b10-4750-a4dc-6ab6-880f1c2470ce.png)

## 6. 動かしてみる

ここまでで、ヒエラルキーはこのような状態になっていると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/cf180cbe-8c35-eb45-af2b-08b6996d660f.png)

まずはUnity EditorのPLAYで動かしてみましょう。
乗り物の前方に多少Cube等を置いておくと良いでしょう。
Game Viewにフォーカスをあわせてからスペースキーを押すと、前方に移動したでしょうか？
Main CameraをY軸まわりに回転させると、車も回転するでしょうか？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/a5bfddcb-47eb-0923-cc4e-edab64b3214b.png)


うまく動かない場合は、各FSMを見直してみましょう。

Unity Editorで動いたら、**Local Preview Tool** を使って、PC版STYLYまたはQuest版STYLYでシーンを動かしてみましょう。STYLYにアップロードするよりもかなり高速に確認することができるのでおすすめです。

## 7. STYLYにアップロード

シーンには余分なアセットが含まれているので、CarRoot以下をProjectウィンドウにドロップしてPrefabにして、`CarRoot` prefabをアップロードすると良いでしょう。

## 8. STYLY Studioで配置

[STYLY Studio](https://gallery.styly.cc/studio/)を起動し、アップロードしたアセットをシーンに配置してみましょう。シーンをPublishして、ブラウザやVRデバイスで動くか試してみてください。
うまく動いたでしょうか？

## 9. 風景モデルの追加

乗り物が走るのに適した風景を用意しましょう。注意点としては、今回の手法ではy=0の平面上を移動するため、起伏のない地形にすることと、負荷の軽いモデルを利用するということです。

描画負荷についてはマテリアル数やメッシュ数、超点数が多いと負荷が高くなりますので、Mesh Bakerなどを利用して負荷低減をするのがおすすめです。（本記事では紹介にとどめます）

準備できたら、STYLY Studioで同じシーンに配置してみましょう。良い風景があるとぐっと雰囲気が良くなると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71421/b609194f-32cb-f52e-8831-1d89b2077ec9.png)

# 応用

今回はXZ平面上を動く、頭の向きに追従という方式にしましたが、いろいろな応用が考えられます。ぜひ試してみてください。

* 空中上下移動できるようにする
* RigidBodyやCharacterControllerを利用して起伏のある地面上を動けるようにする
* VRで角度差分をみて、プレイヤーのリアルな首振りには追従しないようにする
* 乗り降りできるようにする
* 操作から各座標への反映が遅れる要素がいくつかあるのでこれを解消する