MLAPIでOculus Quest2のマルチプレイ ①プロジェクトを実装するまで

# レポジトリのリンク
https://github.com/TomohiroHibino600/TuneVisualizerNetwork

# シリーズ目次
(今ここ→)[①プロジェクトを実装するまで](https://qiita.com/Hibiro22/items/d97b203dfaef734ff299 "①プロジェクトを実装するまで")
[②GCPにサーバー用ビルドをアップ・実行する](https://qiita.com/Hibiro22/items/b1a701d1f8b0b490953d "②GCPにサーバー用ビルドをアップ・実行する")
[③実機でテストするまで](https://qiita.com/Hibiro22/items/e2313215b6e4bad1cfef "③実機でテストするまで")

# 開発環境
Windows10
Unity(AndroidモジュールとLinuxモジュールを開発に使った)
Quest2, RiftSを用いたが、実機テストの時以外は関係がない

--------------------------------------
# 対象読者と本記事の目的
[黒河さんのサンプルプロジェクト](https://github.com/wotakuro/MLAPI_UnitychanSample "黒河さんのサンプルプロジェクト")
[黒河さんのサンプルプロジェクト解説動画](https://www.youtube.com/watch?v=Fl1TP2expZE "黒河さんのサンプルプロジェクト解説動画")
その他の記事や[Unityの公式ドキュメント](https://mp-docs.dlt.it.unity3d.com/docs/getting-started/about-mlapi "Unityの公式ドキュメント")
などを見て、ある程度MLAPIについて把握したVRエンジニアの方々
↓
僕がVRでMLAPIを実装した際の工夫を把握できる

# 本題
## 0. MLAPIマルチプレイのイメージ
まず、マルチプレイのイメージを共有した方が分かりやすくなると思うのでします。ご存じの方はスルーして下さい。
自分は最初これすら知らずに実装したので苦しみました。なお、ヘッドレスサーバーに全QuestがClientとして接続するのを前提に図式を書きました。Relayサーバーを用いた場合などは図式が変わります。

-----------------
①オンライン上の共通のSceneに接続するイメージではありません。
![マルチプレイのイメージ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182279/a34f0162-f98d-84dd-90d6-cdbad85ea1b3.jpeg)

②各個人のSceneに通信で生じたデータが加えられるというイメージが正しいです。
AさんのSceneとBさんのSceneにあるGameObjectは違うものということです。
![マルチプレイのイメージ (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182279/6b2a5257-da24-630d-f028-fb6c2a0bc8ea.jpeg)

③そのため、Aさんのシーンの変化をBさんのシーンに反映させるには、サーバーを仲介するような操作が必要です。ServerRPCやNetworkedTransformはその操作の例です。フォントが黒いですね。まぁいいや。
![マルチプレイのイメージ (3).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182279/66e37838-8544-26a3-2f49-7703504da627.jpeg)

④もちろんサーバーから対象のClientに向けて通信を行うこともあります。
新入りが接続した時に全員のシーンに新入りPlayerのプレハブを生成する場合などに使われるSpawnがそれにあたります。
![マルチプレイのイメージ (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182279/326119f7-8932-8a33-6be3-f8f32e548e68.jpeg)

-------------------
具体的なスクリプトを見る場合は[黒河さんのサンプルプロジェクト](https://github.com/wotakuro/MLAPI_UnitychanSample "黒河さんのサンプルプロジェクト")を覗くのをおすすめします。コメントや注意も日本語で残してあって分かりやすいです。なお、MLAPI改修後にはサンプルやドキュメントが整備されるそうなので、出来次第そちらを見るのもよいかと思います。

本記事にはネットになかった「OculusQuest2でMLAPIを用いたマルチプレイを実装する場合にどうするか」を検証した結果を書きます。**基本黒河さんのスクリプトを改変させて頂きました。MITライセンスでした。**

## 1. 接続先IPアドレス入力UIをVR向けにする
これは前提みたいなものですが、黒河さんのサンプルプロジェクトそのままのUIだとVRでIPアドレスを入力することが出来ません。RayなどVR用UIを用いる必要があります。ポート番号は7777で問題ありませんでした。

## 2. 新入りPlayer接続時は、全Clientに頭と両手(計3つ)のNetworkedObjectを生成させる
Networkで扱うGameObjectには全てNetworked Objectコンポーネントを付ける必要があります。加えて位置を同期する場合はNetworked Transform, アニメーションを同期する場合はNetworked Animationが必要です。

このNetworked Objectコンポーネントを頭を表す黄色い球とコントローラーのGameObjectに取り付けNetworkingManagerのリストに登録した一方で、OVRCameraRigはローカルでのみ扱うようにNetworked Objectコンポーネントを付けませんでした。その代わり、最初に頭のGameObjectをローカルのCenterEyeCameraの子になるようにしました。両手の場合はコントローラーの子にしました。

順を追って書きます。
##### 1. 頭と両手を示すNetworkedObjectを作り、NetworkingManagerに設定した
![NetworkingManager.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182279/52c75e4e-397e-d8ec-993a-540678d044de.png)
※頭のGameObject
![スクリーンショット 2021-03-04 205825.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182279/7997f2f3-5dee-a366-96a7-4585b5686676.png)
※ControllerのGameObject
![スクリーンショット 2021-03-04 210110.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182279/65930b3e-cf8e-7d14-80a4-648786a63ed9.png)

##### 2. Clientが接続してきたら3つのNetworkedオブジェクトをSpawnするよう[ServerManager](https://github.com/TomohiroHibino600/TuneVisualizerNetwork/blob/main/Assets/_MLAPISample/Sample/Scripts/ServerManager.cs "ServerManager.cs")を変えた
ServerManagerはサーバーの挙動が書かれたクラスです。

```C#:ServerManager.cs
// ネットワーク同期するキャラクターオブジェクトを生成します
private void SpawnCharacter(ulong clientId)
{
       var netMgr = MLAPI.NetworkingManager.Singleton;

       for (int i = 0; i < netMgr.NetworkConfig.NetworkedPrefabs.Count; i++) {
           var networkedPrefab = netMgr.NetworkConfig.NetworkedPrefabs[i];
           var gmo = GameObject.Instantiate(networkedPrefab.Prefab, Vector3.zero, Quaternion.identity);
           var netObject = gmo.GetComponent<NetworkedObject>();

           // このNetworkオブジェクトをクライアントでもSpawnさせます
           netObject.SpawnWithOwnership(clientId);
        }

        Debug.Log("Spawn");
}
```

##### 3. 頭と両手のGameObjectがOVRCameraRigの中の適切な階層位置になるよう[SyncTransform](https://github.com/TomohiroHibino600/TuneVisualizerNetwork/blob/main/Assets/Scripts/MLAPI/SyncTransform/SyncTransform.cs "SyncTransform.cs")をアタッチ
アタッチされたGameObjectがStart()時にparentTagのGameObjectの子要素となるスクリプトです。
これで頭をCenterEyeAnchorの子要素に、両手を各Controllerの子要素にしました。
OVRCameraRigはローカルにあるので、これで各自の頭や手の動きを同期できます。

NetworkedBehaviourとはNetworkに接続しているとき、その状態などを扱いたい場合に使えるクラスです。これを継承することで、IsServer, IsOwnerなどの条件も使えるようになります。基本的にMonoBehaviourに書ける内容は書けました。

`!IsServer →　サーバーにOVRCameraRigを置かなかったので付けた条件です。`
`IsOwner →　Aさんのものとして扱われるGameObjectはAさんをOwnerとしており、IsOwnerがTrueになります。位置同期の都合上この制限を足しました。`

```C#:SyncTransform.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MLAPI;

namespace TuneVisualizer
{
    public class SyncTransform : NetworkedBehaviour
    {
        private Transform parent;
        public string parentTag;
        public Vector3 positionOffset;

        public void Start()
        {
            if (!IsServer & IsOwner) {
                transform.parent = GameObject.FindGameObjectWithTag(parentTag).transform;
                transform.position = transform.parent.position + positionOffset;
            }
        }
    }
}
```

ただし、このスクリプトだとローカル内では同じ位置にGameObjectが被って存在してしまいます。
気になる方は隠して下さい。

## [工事中]3. VRのみの話ではないが、Scene内で流れる曲のindexや再生時間を共有できるようにするための操作
NetworkedVarを扱いましたが上手く行かず、工事中です。
新MLAPIのサンプルに期待大。

# 続き
apkを作ってもサーバー用のビルドをアップ・実行しない限り、マルチプレイは出来ません。
基本黒河さんのサンプルのような内容をLinux用にビルドするだけで機能は作れます。
ローカルで実行し、そこに繋げることも可能ですが、学習がてら次回の記事ではGCP(Google Cloud Platform)の仮想Linuxサーバーにアップ・実行します。

# シリーズ目次
(今ここ→)[①プロジェクトを実装するまで](https://qiita.com/Hibiro22/items/d97b203dfaef734ff299 "①プロジェクトを実装するまで")
[②GCPにサーバー用ビルドをアップ・実行する](https://qiita.com/Hibiro22/items/b1a701d1f8b0b490953d "②GCPにサーバー用ビルドをアップ・実行する")
[③実機でテストするまで](https://qiita.com/Hibiro22/items/e2313215b6e4bad1cfef "③実機でテストするまで")

# Tilt Brush(OSS)をMLAPIでマルチプレイ化しようとして失敗したときの遺言メモ(自分用)
>- TiltBrushは3D絵具のMeshFilterを変えることで、3Dペイントを出力していた。
>	
>- 出来るかもしれないと思ったのは2つ。いずれも現状のMLAPIだと自分は詰まりそうで断念してしまった
>	- Spawnした後に、3D絵具のMeshFilterの変更をServerRPCで送る？
>https://www.reddit.com/r/Unity3D/comments/lkc1pd/photon_unity_networking_2_change_meshfilter_and/
>	- コントローラーの入力情報だけ共有する方法を模索
>
>- 新MLAPIリリースに期待。
>	- 2020.12.4から数か月後と書いてあったので待つ
>	- それ以前にマルチプレイを実装しなければならないならphotonかmirrorを使う