MetaQuest3のMRとVRでPhoton PUN2を使用してオブジェクトの位置共有をしてみよう

みなさん，MetaQuest3でVR/MRアプリケーションを作成したことはありますか？作ったことがあるならマルチプレイヤーのゲームを作成したいですよね？(圧)また，プレイヤー以外のオブジェクトの位置を共有することも可能です．このように複数の実行環境でさまざまなことを共有するアプリを作成するための基礎を紹介します！
本記事ではMetaQuest3のVRプラットフォームとMRプラットフォームでPhoton PUN2 を使用して3Dオブジェクトの位置を共有する方法について記載する．

# MR側の作成方法
まずMR側のセットアップ方法を行う．以下の記事ですでに説明をしている．参照してください．

> 本記事執筆時はUnity6がリリースされていた.以下の記事はちょっと古いUnityを使っているためUIが変わっている部分が多々ありますが，文字での補足をしています．

https://qiita.com/JavaLangRuntimeException/items/3055fe5a70c88de4d65f

また以下の記事でMetaQuest3側でインターネット通信ができるようにするための環境構築の説明をしている．
「ビルド設定とMeta環境設定」の見出しを参照してください．

https://qiita.com/JavaLangRuntimeException/items/cd9359dd9b1f5d7cf39e#%E3%83%93%E3%83%AB%E3%83%89%E8%A8%AD%E5%AE%9A%E3%81%A8meta%E7%92%B0%E5%A2%83%E8%A8%AD%E5%AE%9A

Photon PUN2のセットアップはMRとVRともに共通なので最後に記載しています．

# VRプロジェクトのセットアップ
Unity Asset Storeで`Meta All-in-One SDK`を取得する．
https://assetstore.unity.com/packages/tools/integration/meta-xr-all-in-one-sdk-269657

>**Unity Assetとは**
>ゲームやアプリケーション開発に役立つ様々なデジタル素材の総称．具体的には，3Dモデル，テクスチャ，スプライト，アニメーション，サウンドエフェクト，音楽，スクリプト，シェーダー，プレハブなど，Unityプロジェクトで使用できるあらゆるものが含まれる．
>>Meta All-in-One SDKはMetaQuestを用いて開発するためのAssetである．

`マイアセットに追加する`を押す．
 ![スクリーンショット 2024-04-08 15.18.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/5893e65c-2f1b-748d-3587-507070135f12.png)

 >マイアセットに追加してUnityエディターでマイアセット内のAssetをインポートする流れである．
 >>UnityIDの作成を求められた場合作成すること

その後，Unityエディターに戻って`Window` - `Package Manager`を押す．

 ![スクリーンショット 2024-04-08 15.47.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/9aa1d70e-7af6-317f-898c-ca20981d16da.png)

`Packages: In Project`を選択し，`My Assets`を選択する．

![スクリーンショット 2024-04-08 15.50.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/06f46a19-54a0-9a56-ae9b-31d67096b2f0.png)

`Meta XR ALL-in-One SDK`　を選択して`install`を押す．

![スクリーンショット 2024-04-08 16.05.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/71713dce-07cc-2b66-187f-68d86d9c702e.png)

次に`File`を選択して`Build Settings`を押す．

![スクリーンショット 2024-04-08 16.11.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/602e8189-c819-21bc-9ac7-7c300d05a767.png)


`Android`を選択して`Switch Platform`を押す．

![スクリーンショット 2024-04-08 16.11.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/a60d5798-a815-1a94-56b7-a15f28ab8417.png)

`Player Seting...`を選択し，`Meta XR`を選択し，`’Androidマーク’`を選択し，`Fix All`と`Apply All`を押してさまざまなエラーや問題を修正したものを適用する．

![スクリーンショット 2024-04-08 16.19.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/6e5e8cd5-0a05-fc23-80be-e79a22fc17da.png)

Projectビュー内で`OVRCameraRig(Packages/com.meta.xr.sdk.core/Prefabs/OVRCameraRig.prefab)`を検索し．`OVRCameraRig`と`OVRSceneManager`をAssetsにドラッグ&ドロップする．

>`Search:`が`In Packages`になっているかどうか確認すること

![スクリーンショット 2024-04-08 16.54.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/b829a61e-ed19-d6f0-5e68-9c739cf08f84.png)

Assetsフォルダを開くと以下のようになっている．`OVRCameraRig`と`OVRSceneManager`が追加されているか確認する．

![スクリーンショット 2024-04-08 17.29.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/a9616aba-f093-b8f8-be31-8a819db1d149.png)

次に"Assets"フォルダ内の`OVRCameraRig`と`OVRSceneManager`をhierarchyビューの`SampleScene`に追加して，`MainCamera`を削除する．必要に応じて`OVRCameraRig`の位置を調節する．`OVRSceneManager`は変更不要である．

![スクリーンショット 2024-04-08 17.36.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/7b7955d4-b717-1727-7ef5-9e3bb33ff293.png)

hierarchyビューの`OVRCameraRig`を選択してinspectorビューで`Target Devices`の`Quest3`にチェックする．
![スクリーンショット 2024-04-08 18.06.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/c7163b44-c5f0-1141-7ebe-3e7e9d6b98fd.png)

inspectorビューで`Scene Support`とを`Required`にする．
![スクリーンショット 2025-02-24 22.40.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/fed5ac87-1e4c-4af1-b210-942cb1ea0a7b.png)

XR Plug-in Managementをinstallしておく．
![スクリーンショット 2025-02-22 14.02.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/e14619d0-05fb-4ab8-bdaa-5df5cdeaa9aa.png)

VR側でもMetaQuest3側でインターネット通信ができるようにするための設定が必要なので，以下の記事の「ビルド設定とMeta環境設定」の見出しを参照してください．

https://qiita.com/JavaLangRuntimeException/items/cd9359dd9b1f5d7cf39e#%E3%83%93%E3%83%AB%E3%83%89%E8%A8%AD%E5%AE%9A%E3%81%A8meta%E7%92%B0%E5%A2%83%E8%A8%AD%E5%AE%9A

以上でVRのセットアップは完了である．
>シーンの保存を忘れないでね

# Photon PUN2のセットアップ
ここからはPhoton PUN2のセットアップを行う．この操作はVRとMR両方のアプリケーションで同じ操作をする．

## Photonカウントの作成とアプリケーションの作成
ここではPhotonアカウントの作成を行い，Photonアプリケーションの作成を行う．本記事ではPhotonの中でも特にPUN2というフレームワークを使用する．

:::note info
**Photon PUN2とは**
Unity のマルチプレイヤーゲーム開発を支援するためのネットワーキングフレームワーク．Photon Cloud を介してリアルタイム通信を実現するための豊富な API と簡便なワークフローが提供されるため， Photon Cloudに接続するために同一の AppId を設定し，その中の同一の Room への接続を通して複数のクライアント間の通信が容易に管理される仕組みである.

**Photon Cloudとは**
Photonが提供するクラウドサービスであり, リアルタイムマルチプレイヤーゲームの接続管理やサーバー機能をクラウド上で提供するプラットフォームである, 開発者は自前のサーバー環境を構築する手間を省き, グローバルに分散されたサーバー群によって安定した通信を実現することが可能である

https://www.photonengine.com/

**Photon PUN2 における AppId の考え方**
Photon Cloud に接続するための固有の識別子であり, 各プロジェクトに固有であるため, 正しい AppId を設定することで他のプロジェクトと混在せずに接続が行われる仕組みである. 

**Photon PUN2 における Room の考え方**
Room は, リアルタイム通信を行う際の仮想的な空間であり, プレイヤーが同一の Room に参加することで状態の同期や通信が実現される, Room ごとに参加人数や設定が管理されており, 必要に応じて Room の作成や参加が自動的に行われる仕組みである.
:::

Photonのアカウントを作成する．

![スクリーンショット 2025-02-24 13.55.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/c0b831c3-d812-4cf5-9f54-1c25f1fac8b1.png)

「Create a new app」を選択し，Photon アプリケーションを作成する．

![スクリーンショット 2025-02-24 13.53.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/ad66d5a9-e724-4bf3-ae2e-6d85d7a2d18b.png)

このようにアプリケーションが作成される．この上部にある**App ID**が今後必要になるのでどこかに控えておくこと

![スクリーンショット 2025-02-24 14.45.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/00a5efef-9f49-441b-ade2-bfa35025a0c4.png)

## Unity Asset StoreでPUN2を取得
以下のアセットをUnity Assets Storeでマイアセットに追加する．

> 今回は無料版を使用します

https://assetstore.unity.com/packages/tools/network/pun-2-free-119922

## Photon コスト
ここでPhotonを使用するにあたってのコストを説明します．先ほども述べた通り，本記事では無料版(20 CCU)のものを使用します．
> `引用` https://www.photonengine.com/ja-jp/pun/pricing
> 2025年2月24日時点の情報です

### Photon PUBLIC CLOUD GAMING For Development Only

| Plan           | CCU(同時接続数) | License/Billing  | Price | Validity | Monthly Transfer Limit | Other Details                                      |
|----------------|-----------------|------------------|-------|----------|------------------------|----------------------------------------------------|
| 20 CCU(月額)   | 20              | Development Only | Free | -        | 60.0 GB/月             | 同時に最大20人の接続<br>開発目的のみ<br>CCUバーストなし |

# Photon PUBLIC CLOUD GAMING For Launch

| Plan            | CCU(同時接続数) | License/Billing | Price    | Validity  | Monthly Transfer Limit | Other Details                                                                                                                                                      |
|-----------------|-----------------|-----------------|----------|-----------|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 100 CCU(月額)   | 100             | ONE-TIME        | ¥22,416  | 12ヶ月間 | 0.3 TB/月              | 同時に最大100人の接続<br>ゲーム専用<br>最適MAU: 〜40k<br>CCUバーストなし                                                                                            |
| 500 CCU (月額)  | 500             | Monthly            | ¥22,416  | 月単位   | 1.5 TB/月              | 同時に最大500人の接続<br>ゲーム専用<br>最適MAU: 〜200k<br>CCUバースト含む<br>追加購入（1GBあたり）:<br>• Europe/Canada, East / RU / RUE / USA (East/West): ¥11.80<br>• Asia / Australia / China / India / Japan / South Africa / South America / South Korea / Turkey / UAE: ¥23.60 |
| 1000 CCU (月額) | 1000            | Monthly            | ¥43,651  | 月単位   | 3.0 TB/月              | 同時に最大1,000人の接続<br>ゲーム専用<br>最適MAU: 〜400k<br>CCUバースト含む<br>追加購入（1GBあたり）:<br>• Europe/Canada, East / RU / RUE / USA (East/West): ¥11.80<br>• Asia / Australia / China / India / Japan / South Africa / South America / South Korea / Turkey / UAE: ¥23.60 |
| 2000 CCU (月額) | 2000            | Monthly            | ¥87,302  | 月単位   | 6.0 TB/月              | 同時に最大2,000人の接続<br>ゲーム専用<br>最適MAU: 〜800k<br>CCUバースト含む<br>追加購入（1GBあたり）:<br>• Europe/Canada, East / RU / RUE / USA (East/West): ¥11.80<br>• Asia / Australia / China / India / Japan / South Africa / South America / South Korea / Turkey / UAE: ¥23.60 |

### Photon PREMIUM CLOUD FOR GAMING Pricing

| Plan                                    | Billing Model | Price         | Included Features                                   | Other Details                                                                                                                                         |
|-----------------------------------------|---------------|---------------|-----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| PREMIUM CLOUD FOR GAMING (Usage-Based)  | 毎CCU請求     | ¥68.20 / CCU  | -                                                   | 毎月の使用量ベース<br>最大50,000 CCUまで自動調整                                                                                                      |
| PREMIUM CLOUD FOR GAMING (Minimum Fee)  | 最低料金      | ¥136,400/月   | 2,000 CCU + 6.0 TB転送量<br>3.0 GB/ピークCCU/月       | ゲーム専用<br>同時最大2,000人の接続<br>適用MAU: 20m<br>使用量ベースのCCUオートスケーリング<br>50,000 CCU以上のバースト<br>デフォルトのDDoS保護                   |

## UnityプロジェクトにPhotonフレームワークを適用
ここからはUnityプロジェクトにPhotonフレームワークを適用するフェーズです．Metaの設定は[VRプロジェクトのセットアップ](#VRプロジェクトのセットアップ) で済んでいるものとします．
`Window`→`Package Manager`を選択．

![スクリーンショット 2025-02-24 18.14.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/bb214b86-15fd-4f99-bf75-77fccf967b11.png)

`My Assets` → `PUN2 - FREE`をインストールする．

![スクリーンショット 2025-02-24 18.17.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/24cca3c0-9126-40cf-81b4-0af825e7e573.png)

`Window`→`Photon Unity Networking`→`PUN Wizard`を選択
![スクリーンショット 2025-02-24 18.19.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/50120221-da6a-48d8-b26b-580ba8790552.png)

`Setup Project`を選択．

![スクリーンショット 2025-02-24 18.21.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/7025680f-6d03-4b84-ac40-0b4d65f3a930.png)

先ほどメモした**App ID**を入力して`Setup Project`を選択する．

![スクリーンショット 2025-02-24 18.21.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/bf23f701-eb02-4e8c-87a7-1e3053b36e4f.png)

すると`Assets/Photon/PhotonUnityNetworking/Resources/PhotonServerSettings.asset`が作成される．

> Assets/Photon/以下ファイルはPUN2 - FREE アセットをインストールした時点で作成済みである

`PhotonServerSettings.asset`の`App Id PUN`が先ほど入力した’**App ID**と一致するかを確認する．

![スクリーンショット 2025-02-24 18.25.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/d53394e7-9016-443c-856a-3645980543b1.png)

## 実際にPhotonで通信するオブジェクトの設置
ここからは実際にPhotonで通信(複数クライアント間で位置の同期など)をするオブジェクトを設置する．

- HierarchyビューでSphere(3Dオブジェクトならなんでもいいです)を設置する．

- inspectorビューで`Add Component`を選択し，`Photon View`と`Photon Transform View`をアタッチする．
> アニメーションも同期させたい場合は`Photon Animation View`もアタッチする．

- inspectorビューにあるsphereをドラッグ&ドロップで`Assets/Resources`ディレクトリに設置する．

::: note warn
Photon PUN2の仕様上`Assets/Resources`ディレクトリにオブジェクトがないとPhotonが認識しないみたいです．`Assets/Resources`ディレクトリに必ず**Prefab化**をしましょう．
:::

![スクリーンショット 2025-02-24 18.29.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/441f4fb6-aa6b-441c-839b-ff53a7719d03.png)


## (任意)通信失敗用のオブジェクトを設置する
inspectorビューにcube(3Dオブジェクトなら...(以下略))を設置．

> このオブジェクトはPhotonの通信とは関係ないので何も`Add Component`しなくていいです．またPrefabの位置も`Assets/Resoueces`以下ディレクトリでなくてもいいです...

![スクリーンショット 2025-02-24 18.36.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/64fc931a-2bdb-4ffd-b1d1-622f2e66392a.png)

## 通信用のGameManagerの作成
inspectorビューで空のGame Object(`Create Empty`)を作成する．
![スクリーンショット 2025-02-24 18.38.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/fc029ac0-1b9f-4f1c-813c-f5f55979c762.png)

任意のディレクトリに以下のC#スクリプトを設置する．

> スクリプト名は`PhotonGameManager.cs`にしています

Photon.Punのメソッドをオーバーライドしています．詳しいスクリプトの内容は別の記事で紹介しようと思います．

```PhotonGameManager.cs
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;

public class PhotonGameManager : MonoBehaviourPunCallbacks
{
    public GameObject PhotonObject;
    public GameObject PhotonFailureObject;

    void Start()
    {
        PhotonNetwork.ConnectUsingSettings();
    }

    // ルームに参加する処理
    public override void OnConnectedToMaster()
    {
        // 固定ルーム "SampleRoomName" に参加
        PhotonNetwork.JoinRoom("SampleRoomName");
    }

    // ルーム参加に失敗した場合(通常，指定したルーム名が存在しなかった場合)の処理
    public override void OnJoinRoomFailed(short returnCode, string message)
    {
        // ルーム参加に失敗した場合はルームを新規作成（最大8人まで）
        RoomOptions roomOptions = new RoomOptions();
        roomOptions.MaxPlayers = 8;
        PhotonNetwork.CreateRoom("SampleRoomName", roomOptions);
    }

    // ルーム参加に成功した時の処理
    public override void OnJoinedRoom()
    {
        if (PhotonObject == null)
        {
            Debug.LogError("PhotonObject is not set in the inspector.");
            return;
        }

        // ルームに入室できたら、PhotonObject(本記事ではSphere)を生成する
        PhotonNetwork.Instantiate(PhotonObject.name, new Vector3(0f, 0f, 0f), Quaternion.identity, 0);

    }

    // OnDisconnectedという名前だがルーム切断時のみではなく接続失敗時にも実行する処理
    public override void OnDisconnected(DisconnectCause cause)
    {
        base.OnDisconnected(cause);
        Debug.LogError("Disconnected from Photon: " + cause.ToString());

        // 接続に失敗した場合、PhotonFailureObject(本記事ではCube) を表示する
        if (PhotonFailureObject != null)
        {
            // ローカルにオブジェクトを Instantiate する例（PhotonNetwork.Instantiate は使用できないため）
            Instantiate(PhotonFailureObject, new Vector3(0f, 0f, 0f), Quaternion.identity);
        }
        else
        {
            Debug.LogError("PhotonFailureObject is not set in the inspector.");
        }
    }
}
```

このスクリプトを先ほど作成した空のGameObjectに`Add Component`でアタッチする

![スクリーンショット 2025-02-24 18.41.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/da2d797a-dadf-4e73-97e9-7fb0276af798.png)

inspectorビューで`Photon Object`に先ほど作成したSphereをアタッチ．
(任意)`Photon Failure Object`に先ほど作成した`Cube`をアタッチ．

![スクリーンショット 2025-02-24 18.42.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/572e035f-8fd2-4163-bd3c-6ec72e3caaf9.png)

ここまでをVR側のプロジェクトとMR側のプロジェクトで同じように設定すること．
これでPhotonの通信部の設定は完了です．

# ビルドと実行
`File`→`Build Profiles`を選択

![スクリーンショット 2025-02-24 22.32.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/e28997aa-72cf-4155-970e-860491883550.png)

`Platfolms`を`Android`にして`Build And Run`を選択してMetaQuest3で実行する．
![スクリーンショット 2025-02-24 22.33.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/7f4138de-bfda-44cd-9572-de6c2077c00f.png)

これでVR側とMR側の両方にSphereがあれば成功である．

# エラーハンドリング
- VR側とMR側の両方の`Assets/Resources`にPhotonで通信する同じオブジェクトがあることはないですか？

> 結構忘れがちです