【Unity】Photon Fusion 向け拡張メソッドの紹介メモ

※たまにコードを改修しているため、説明が古くなっています
# コード

https://github.com/yamara-mh/GenericCodes/blob/main/Codes/PhotonFusionUtil.cs

# 説明

## Second.ToTick()

``` C#
// 秒単位 と Tick単位 の変換をします
public static int ToTick(this int second, NetworkRunner runner) => (int)(second / runner.DeltaTime);
public static int ToTick(this float second, NetworkRunner runner) => (int)(second / runner.DeltaTime);
public static float ToSecond(this int tick, NetworkRunner runner) => tick * runner.DeltaTime;

// Runner.Tick から second 秒経過した際の Tick を返します
public static int GetTickAfter(this NetworkRunner runner, float second) => runner.Tick + (int)(second / runner.DeltaTime);

// Runner.Tick が tick を超えてから経過した秒数を返します
public static float ElapsedTime(this NetworkRunner runner, int tick) => (runner.Tick - tick) * runner.DeltaTime;
// Runner.Tick が tick になるまでの秒数を返します
public static float RemainingTime(this NetworkRunner runner, int tick) => -runner.ElapsedTime(tick);

// Runner.Tick が tick を超えたかどうか判定します
public static bool HasPassed(this NetworkRunner runner, int tick) => (runner.Tick - tick) > 0;
public static bool HasReached(this NetworkRunner runner, int tick) => (runner.Tick - tick) >= 0;
public static bool IsAt(this NetworkRunner runner, int tick) => runner.Tick == tick;
public static bool HasntPassed(this NetworkRunner runner, int tick) => (runner.Tick - tick) <= 0;
public static bool HasntReached(this NetworkRunner runner, int tick) => (runner.Tick - tick) < 0;
```

## Array

### NetworkArray.ForLoop()

``` 使用例.cs
NetArray.ForEach(item => Debug.Log(item));
NetArray.ForLoop((i, item) => Debug.Log(i + " : " + item));
```

### NetworkArray.Replace()

NetworkArray の要素から一致する条件を探し、そこに value を代入します。

``` 使用例.cs
// 全要素を -1 に変更
NetArray.ReplaceAll(-1);
// 全要素を 2 倍
NetArray.ReplaceAll(v => v * 2);
// -2 の要素を全て 0 に変更
NetArray.Replace(v => v == -2, 0);
// 0 の要素を1つだけ 100 に変更
int index = NetArray.ReplaceOne(v => v == 0, v => 100);
// 0 の要素3つを 100, 200, 300 に変更
bool success = NetArray.ReplaceOneByOne(v => v == 0, 100, 200, 300);
```

### NetworkArray.OnValueChanged()

NetworkArray で変化した要素を探し、処理を行います。

``` 使用例.cs
[Networked(OnChanged = nameof(OnChangedArray)), Capacity(16)] NetworkArray<int> Array { get; }
private static void OnChangedArray(Changed<ClassName> changed) => changed.Behaviour.ChangedArray(changed);
private void ChangedArray(Changed<ClassName> changed)
{
    Array.OnValueChanged(changed, c => c.Array, (index, beforeValue, afterValue) => { /* 省略 */ } );
}
```

v2 は Changed の代わりに ChangeDetector で取得した前の値を引数に入れてください。

## UniRx

### TickTimer.OnCompleted()

``` 使用例.cs
//  完了時に実行
tickTimer.OnCompleted(Runner).Subscribe(_ => { /* 省略 */ } ).AddTo(this);
// 有効の間、常に実行 
tickTimer.OnUpdated(Runner).Subscribe(_ => { /* 省略 */ } ).AddTo(this);
// 有効の間、Runner.DeltaTime の間隔で実行
tickTimer.OnRunnerUpdated(Runner).Subscribe(_ => { /* 省略 */ } ).AddTo(this);
// RemainingTime の整数部分が更新された時に実行
tickTimer.OnCountDowned(Runner).Subscribe(_ => { /* 省略 */ } ).AddTo(this);
```

## その他

### Changed.LoadOld()　ver1のみ

既存の```Changed.LoadOld()```の利便性を高めました。

``` 使用例.cs
var prevValue = changed.LoadOld(behaviour => behaviour.Value);
var newValue = changed.Behaviour.Value;
```

### UpdateFlow()

Tick の経過時間に応じて実行する関数を変える処理を簡潔に書けます。

``` 使用例.cs
void FixedUpdateNetwork()
{
    Runner.UpdateFlow(ShotTick, new int[] { 5, 10, 5 }, BeforeShot, Shot, AfterShot);
}
void BeforeShot(int elapsedTick) => { /*省略*/ }
void Shot(int elapsedTick) => { /*省略*/ }
void AfterShot(int elapsedTick) => { /*省略*/ }
```

### Runner.Host()

``` C#
// Host の PlayerRef を取得します
public static PlayerRef Host(this NetworkRunner runner)
    => runner.GameMode == GameMode.Server ? PlayerRef.None : runner.Simulation.Config.DefaultPlayers - 1;
// 専用サーバか判定します。Host Mode と Server Mode を平行に実装する際、専用サーバに対して不要な処理を省く用途で利用します
public static bool IsServerMode(this NetworkRunner runner) => runner.GameMode == GameMode.Server;
```

### PlayerRef.IsHost()

``` C#
// PlayerRef が Host か判定します
public static bool IsHost(this PlayerRef playerRef, NetworkRunner runner) => playerRef == runner.Simulation.MaxConnections;
// PlayerRef が自分か判定します
public static bool IsMe(this PlayerRef playerRef, NetworkRunner runner) => playerRef == runner.LocalPlayer;
// PlayerRef が NetworkObject/NetworkBehaviour の 入力権限/所有権 を持っているか判定します
public static bool HasInputAuthorityTo(this PlayerRef playerRef, NetworkObject no) => playerRef == no.InputAuthority;
public static bool HasStateAuthorityTo(this PlayerRef playerRef, NetworkObject no) => playerRef == no.StateAuthority;
public static bool HasInputAuthorityTo(this PlayerRef playerRef, NetworkBehaviour nb) => playerRef == nb.Object.InputAuthority;
public static bool HasStateAuthorityTo(this PlayerRef playerRef, NetworkBehaviour nb) => playerRef == nb.Object.StateAuthority;
```

### RpcInfo.Source()

Host/Server が RPC を呼ぶと RpcInfo.Source は None になります。
この拡張メソッドは、Host が RPC を呼んだ際に Host の PlayerRef を利用できるようにしたものです。

``` C#
public static PlayerRef Source(this RpcInfo info, NetworkRunner runner) => info.Source.IsNone ? runner.Host() : info.Source;
```

### NetworkBehaviour.GetSeed()

乱数のシード値として利用できる値を取得します。
（カスタムプロパティに seed を追加する必要があります）

``` 使用例.cs
Random.InitState(networkBehaviour.GetSeed());
```

### NetworkObject.TryAssignInputAuthority()

再接続や Host Migration を行う際、ConnectionToken を元に NetworkObject の入力権限の変更を試みます。

``` C#
public static bool TryAssignInputAuthority(this NetworkObject obj, NetworkRunner runner, Guid token, bool noAssignment = true)
{
    foreach (var p in runner.ActivePlayers)
    {
        if (new Guid(runner.GetPlayerConnectionToken(p)) != token) continue;
        obj.AssignInputAuthority(p);
        return true;
    }
    if (noAssignment) obj.AssignInputAuthority(PlayerRef.None);
    return false;
}
```

# PhotonFusionUtil

便利クラスも含めています。

## Runner

どこからでも NetworkRunner を取得できます。
Runenr を複数利用している際は一つ目しか取れないので注意が必要です。

``` C#
var runner = PhotonFusionUtil.Runner;
```