【Unity】PhotonFusion メモ

# 実装

## ConnectionToken

ConnectionToken は入室制限や再接続時の入力権限再割り当て等に役立つ。

### 入室制限

以下のような手順で、Token を知っている Client だけが入室できる部屋を作れる。

1 : Backend でマッチングが決定したら、Server と Client に部屋名や Token 等の情報を送る
2 : Server は Backend から全 Cilent の Token を受け取ったら、部屋を作成する
3 : Client は Backend から固有の Token を受け取ったら、入室する
4 : Client が入室する際、StartGameArgs.ConnectionToken を利用し、Server に Token を送る

``` Client Side.cs
runner.StartGame(new StartGameArgs()
{
    /* 省略 */
    ConnectionToken = System.Text.Encoding.ASCII.GetBytes(userToken),
});
```
5 : Server は OnConnectRequest() で Token が有効か判定する（入室拒否したいなら Refuse()）
``` Server Side.cs
public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token)
{
    if (tokens.Contains(System.Text.Encoding.ASCII.GetString(token))) request.Accept();
    else request.Refuse();
}
```
※Client 側では、Accept() なら OnPlayerJoined(), Refuse() なら OnConnectFailed() が実行される

#### 簡易的なパスワード付きの部屋

Backend サーバを用意できない場合でも、ConnectionToken を利用すれば鍵付きの部屋が作れる。
StartGameArgs.ConnectionToken の先頭または末尾をパスワードで上書きすれば、Server 側で OnConnectRequest() からパスワードの正誤判定ができる。

### 再接続時の入力権限再割り当て

https://zenn.dev/o8que/articles/685eeab6473214#%E3%83%97%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E3%81%AFconnectiontoken%E3%81%A7%E8%AD%98%E5%88%A5%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B

## Networked プロパティ

### 変更リスナを簡潔に書く

Networked プロパティに設定するstatic関数が長くなる際は、他の関数を呼び出すことで簡潔に書ける。
``` 例.cs
[Networked(OnChanged = nameof(OnChangedMatColor))] Color MatColor { get; set; }
private static void OnChangedMatColor(Changed<Player> changed) => changed.Behaviour.OnUpdatedMatColor(changed);
private void OnUpdatedMatColor(Changed<Player> changed)
{
    material.color = MatColor;
    /* 実装 */
}
```

### Networked プロパティの節約

フレームを跨ぐ変数は基本的に Networked プロパティにするべき。
ただ、一つ前の予測を利用して、精度を犠牲に Networked プロパティを減らせる場合もある。

``` C#
private Vector3 _prevTickPos;
public Vector3 PrevPos { get; private set; }

public override void FixedUpdateNetwork()
{
    /* 省略 */
    
    if (Runner.IsClient && Runner.IsResimulation && Runner.IsFirstTick)
    {
        PrevPos = _prevTickPos;
        _prevTickPos = Rigidbody.Position;
    }
    else
    {
        PrevPos = Rigidbody.Position;
    }
}
```

## 予測

Transform や Rigidbody は[予測動作](https://doc.photonengine.com/ja-jp/fusion/current/fusion-100/fusion-103#1cuuhvn8__hj2keh)、スポーンは[スポーン予想](https://doc.photonengine.com/ja-jp/fusion/current/manual/spawning#708omiagmppwblx_)を利用する。
下図は Interpolated Error の設定を可視化した画像。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307985/6d9d8c40-ab1e-529e-9bae-0ff272ff4d7a.png)

### 確定している挙動

一定時間で動く床などは、```FixedUpdateNetwork()```内で```Runner.SimulationTime```を使って挙動を実装し、```Render()```内で```Runner.SimulationRenderTime```を使って描画を行う。```Host/Client モード```や ```Server モード```で確定挙動をクライアント予測する際は[ローカル外挿](https://doc.photonengine.com/ja-jp/fusion/current/technical-samples/fusion-projectiles-essentials#exocmwv3ev95r45x)が適している。

# Tips

## 拡張メソッド／便利メソッド

https://qiita.com/Yamara/items/acfe3d6715fa05e8779d

## Host/Client Mode と Server Mode の両立

Hose Mode で作ったゲームは以下のサンプルを参考にして、入室処理周りを改修すれば Server Mode でも動かせる。

https://doc.photonengine.com/ja-jp/fusion/current/technical-samples/dedicated-server/fusion-dedicated-server

サーバに不要な処理は以下のように省ける。 
``` C#
#if !UNITY_SERVER
    /* 効果音やパーティクル発生等の処理 */
#endif 
```

実装中は Host Mode で確認し、プルリク前の動作確認は ParrelSync で専用サーバを用意して Server Mode で確認。
アップデート前は Dedicated Server ビルドして Server Mode で確認するのがいい塩梅かも。

## Multi-Peer

Multi-Peer Mode を使うと、違う Session 同士で物理的な干渉が起きなくなる。
また Physics を Runner.GetPhysicsScene() に置き替える必要がある。
Shutdown() しても空の Scene が残る問題は、```Runner.Shutdown()```の直後に```SceneManager.UnloadSceneAsync(Runner.MultiplePeerUnityScene)```を呼ぶことで解決できる。

## Networked
Tick は Networked プロパティで扱えない。使おうとすると下記のエラーが出る。
公式サンプルでは代わりに int を使っている。
```
System.ArgumentException: Value types need to implement either INetworkStruct or INetworkInput interface (type: Fusion.Tick)
```

# Addressables

### WebGL で Addressables な NetworkObject を Spawn

WebGL 上で読み込んでいない Addressables な NetworkObject を Spawn しようとすると失敗する。
Spawn() する際はクライアントに読込依頼を出し、全員から読込完了の連絡を待つなどする必要がある。

### Release

Fusion 1.1.x 時点では Despawn() しても Addressables は Release されない。
[Photon の Discord にある有志が書いたスクリプト **FusionAssetReference** ](https://discord.com/channels/87465474098483200/1027550015029248020/1029402834040533043)を NetworkObject に付けると Release されるようになる。