Photonを利用した2つの別プロジェクト同士で通信が出来ない事象に遭遇した

# 概要

PhotonRealtime を利用した2つの別のプロジェクト同士で通信させるという少し変わったことをしていたのですが、RPC通信が出来ないという事象に遭遇しました。マッチングが出来てないのではないかと思い、`appId` と `gameVersion` と `roomName` を確認しましたが、全て一致していることを確認しました。

ここでは2つのプロジェクトを **プロジェクトA** , **プロジェクトB** と呼ぶことにします。
(諸々の事情で PUN1 を利用しています)

# 事象

プロジェクトA同士, プロジェクトB同士の通信 → 出来る
プロジェクトAとプロジェクトBの通信 → 出来ない

`gameVersion` や `roomName` の設定は両プロジェクトで下記の通りです。
(`appId` が同一であることは確認済みです)

```csharp
PhotonNetwork.ConnectUsingSettings("gameVersion");
PhotonNetwork.JoinOrCreateRoom("roomName", ...);
```

# 調査

まずは問題を切り分ける為、基本的なところから調査を行います。

## 同一のサーバに接続されているか確認する

`PhotonNetwork.countOfPlayers` を利用して、両プロジェクトが同一のサーバに接続されているかを確認しました。

```csharp
// プロジェクトAとプロジェクトB
Debug.Log(PhotonNetwork.countOfPlayers) // 1

// プロジェクトAとプロジェクトA
Debug.Log(PhotonNetwork.countOfPlayers) // 2

// プロジェクトBとプロジェクトB
Debug.Log(PhotonNetwork.countOfPlayers) // 2
```

結果、そもそも両プロジェクトで別のサーバに接続されている事が分かりました。

## 通信内容を確認する

Wireshark を使い、実際に送信されている `appId` 等を確認することにしました。
Filter に下記を入力することで、Photon の通信のみを確認出来ます。

```
(udp.port == 5055 || udp.port == 5056 || udp.port == 5057 || udp.port == 5058 || udp.port == 843 || udp.port == 943 || udp.port == 4530 || udp.port == 4531 || udp.port == 4532 || udp.port == 4533 || udp.port == 9090 || udp.port == 9091 || udp.port == 9092 || udp.port == 9093 || udp.port == 19090 || udp.port == 19091 || udp.port == 19093 || udp.port == 27000 || udp.port == 27001 || udp.port == 27002 || tcp.port == 5055 || tcp.port == 5056 || tcp.port == 5057 || tcp.port == 5058 || tcp.port == 843 || tcp.port == 943 || tcp.port == 4530 || tcp.port == 4531 || tcp.port == 4532 || tcp.port == 4533 || tcp.port == 9090 || tcp.port == 9091 || tcp.port == 9092 || tcp.port == 9093 || tcp.port == 19090 || tcp.port == 19091 || tcp.port == 19093 || tcp.port == 27000 || tcp.port == 27001 || tcp.port == 27002)
```

ただこれだけでは辛いので、Photon の通信プロトコルを解析することにします。
[バイナリプロトコル | Photon Engine](https://doc.photonengine.com/ja-jp/server/current/reference/binary-protocol) に Photon の通信プロトコルの仕様があります。
また、[AltspaceVR/wireshark-photon-dissecto](https://github.com/AltspaceVR/wireshark-photon-dissector) のLuaでプロトコルの解析が可能です。
(`ProtoField.bytes` の引数にバグあり、`base.HEX` の記述を削除する。)

Lua 拡張読み込みは [Wireshark(Ethereal)で独自パケットフォーマットを解析する方法 ](http://d.hatena.ne.jp/chipa34/20081217/1229487985) 参照してください。
Lua の Photon 拡張を読み込んで Wireshark で確認すると下記のようになります。

![2feac96c7a1d7fe1ed6ab5d1e0154b41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44388/ab6c343b-40bb-e026-aea1-1bee21948dd0.png)

これで通信内容が見えるようになったのは良いのですが、肝心の `appId` が見つけられなかったので断念しました。

## PUNのログを確認する


`SupportLogger` と呼ばれる PUN に含まれるロギング用コンポーネントを利用します。
これは任意の GameObject にアタッチするとログを吐いてくれます。
詳細は [Analyzing Disconnects | Photon Engine](https://doc.photonengine.com/en-us/realtime/current/troubleshooting/analyzing-disconnects) を参照して下さい。

これを利用してログを確認すると、各プロジェクトで `gameVersion` が異なっていることが確認出来ました。

```
プロジェクトB
SupportLogger Info: PUN 1.91: AppID: 1*** GameVersion: gameVersion_1.91 ...


プロジェクトB
SupportLogger Info: PUN 1.94: AppID: 1*** GameVersion: gameVersion_1.94 ...

```

# 原因

`gameVersion` は末尾に PUN のバージョンが付与されて、正しくマッチング出来ていないことが原因でした。
(Photon公式にも違うバージョンの PUN 同士では通信出来ないとか書いてあったような気がする？)

- プロジェクトA: `gameVersion_1.91`
- プロジェクトB: `gameVersion_1.94`

# 対策

該当コードは `NetworkingPeer.cs` の下記コードです。

```csharp
internal class NetworkingPeer : LoadBalancingPeer, IPhotonPeerListener
{
    /// <summary>Combination of GameVersion+"_"+PunVersion. Separates players per app by version.</summary>
    protected internal string AppVersion
    {
        get { return string.Format("{0}_{1}", PhotonNetwork.gameVersion, PhotonNetwork.versionPUN); }
    }
```

[Restricting joining room with non-matching game version — Photon Engine](https://forum.photonengine.com/discussion/13341/restricting-joining-room-with-non-matching-game-version) に対策がありましたが、この対策は PUN1 では利用出来なかった為、暫定的に今回は上記コードを下記のように変更して対策しました。

```csharp
internal class NetworkingPeer : LoadBalancingPeer, IPhotonPeerListener
{
    /// <summary>Combination of GameVersion+"_"+PunVersion. Separates players per app by version.</summary>
    protected internal string AppVersion
    {
        // TODO: これをコメントアウトして、gameVersion だけ返すようにする
        // get { return string.Format("{0}_{1}", PhotonNetwork.gameVersion, PhotonNetwork.versionPUN); }
        get { return PhotonNetwork.gameVersion; }
    }
```

## RPC通信のエラー

### RPCのindexが合ってない

これでマッチングは出来るようになりましたが、下記エラーによりRPC通信は失敗しました。

```
Could not find RPC with index: 18. Going to ignore! Check PhotonServerSettings.RpcList
```

これはRPCのindexが両プロジェクトで一致していないことが原因のようです。
下記リンクで質問が上がっていましたが、解決には至っていないようでした。
[Could not find RPC with index: 17. Going to ignore! Check PhotonServerSettings.RpcList — Photon Engine](https://forum.photonengine.com/discussion/9874/could-not-find-rpc-with-index-17-going-to-ignore-check-photonserversettings-rpclist)

ただ、両プロジェクトでRPCのindexを完全に一致させたところ、無事にこのエラーは消えました。
(下記画像は上記リンクより拝借、これを両方で合わせる)

![image](https://i.gyazo.com/20adc9321e240c45b1e552d8254a9642.jpg)

### RPCが書いてあるスクリプトのPhotonViewIDが一致していない

index関連のエラーは消えましたが、今度は下記エラーが出ました。

```
PhotonView with ID 1 has no method "xxx" marked with the [RPC](C#) ... 
```

RPCを持つスクリプトをアタッチしたゲームオブジェクトには PhotonView をアタッチする必要があります。
アタッチした PhotonView には ViewID を割り当てますが、これが両プロジェクトで一致している必要がありました。

そこで RPC の index と PhotonViewID を一致させたところ、下記のように別プロジェクト同士で位置同期が確認出来ました！(ちょっと分かりづらくてすみません)

![image](https://i.gyazo.com/156c5394b7b340152e7f1e4fdabef451.gif)


# まとめ

Photonエンジンは別プロジェクト同士で利用されることが想定されていないことが分かりました。
これは RPC が index や PhotonViewID に依存していることから明らかです。

Photon はプロジェクト立ち上げ時は便利なのですが、内部がブラックボックスなのがネックでした。
オンラインの同期は独自実装か、日本語サポートが充実しているモノビットを使うのが良さそうですね。