Open12

Unity用ネットコード【Fish-Net】開発メモ

栗山 和樹栗山 和樹

Fish-Net vs PhotonFusion比較表

コンポーネント名

名称はほぼ同じ

機能 Fish-Net PhotonFusion
全体管理 NetworkManager NetworkRunner
ゲームオブジェクト同期 NetworkObject NetworkObject
コンポーネント同期 NetworkBehaviour NetworkBehaviour
Transform同期 NetworkTransform NetworkTransform
Animation同期 NetworkAnimation NetworkAnimation

機能

機能 Fish-Net PhotonFusion
クライアントの接続検知 ClientManager.OnRemoteConnectionStateのActionの引数からRemoteConnectionStateArgs.ConnectionState.Startedで判定する OnPlayerJoined
クライアントの切断検知 ClientManager.OnRemoteConnectionStateのActionの引数からRemoteConnectionStateArgs.ConnectionState.Stopで判定する OnPlayerLeft
変数の同期 SyncVar(or List,HashSet,Dictionary)<T>型を使用する [Networked]アトリビュートを付与する
自分の所有物か確認する IsOwner HasStateAuthority or HasInputAuthority
同期開始イベント NetworkBehaviour.OnStartNetwork または OnStartClient or OnStartServer NetworkBehaviour.Spawn
同期終了イベント NetworkBehaviour.OnStopNetwork または OnStopClient or OnStopServer NetworkBehaviour.Despawn
VContainerやZenjectを利用したNetworkObjectへの依存性注入 DefaultObjectPoolを継承したクラスでInstantiateの箇所をContainer.Instantiateに上書きし、NetworkManagerにアタッチする NetworkObjectProviderDefault(Fusion1の場合はNetworkObjectPool)を継承したクラス内のInstantiatePrefabを上書きしてInjectできるようにする
ホストマイグレーション なし あり
栗山 和樹栗山 和樹

用語

  • リモートクライアント
    • ローカルクライアントも含まれる
  • ホスト
    • サーバー兼リモートクライアントとして扱われる
  • リモートサーバー
    • ローカルクライアントがサーバーの場合も含まれる
栗山 和樹栗山 和樹

依存性注入

サーバー以外はSpawn内でInstantiateされるため、工夫が必要。
DefaultObjectPool内でInstantiateしているため、これを継承したクラスを作成し、上書きする必要がある。

以下はVContainerの例
※この方法だとFishNetライブラリのコードを一部protectedに修正する必要があり、バージョンアップの時にも毎回修正が必要なのでちょっと微妙かも。。。もっと良い方法があれば教えて頂けると幸いです

    public class InjectObjectPool : DefaultObjectPool
    {
        private LifetimeScope _lifetimeScope;
        [Inject] private void Construct(LifetimeScope lifetimeScope) => _lifetimeScope = lifetimeScope;
        ...
        NetworkObject result = _lifetimeScope.Container.Instantiate(prefab, pos, rot, parent);
        ...
        NetworkObject nob = _lifetimeScope.Container.Instantiate(prefab);
        ...
    }
        
栗山 和樹栗山 和樹

変数関連

  • サーバーしか書き込みすることができないので、以下の様に[ServerRpc]アトリビュートを付与し、サーバーに更新命令を与えることで値を更新することができる。
public readonly SyncVar<int> Hoge = new SyncVar<int>();
[ServerRpc]
public void SetHoge(int value) => Hoge.Value = value;
  • 値の変更イベント登録
Hoge.OnChange += OnHogeChanged;

サーバークライアントはサーバーとして一回、クライアントとして一回の合計2回イベントが発行される。
他のクライアントと同じ様に動作させたい場合は以下の様に記述する。

private void OnHogeChanged(int prev, int next, bool asserver)
{
    if (!asserver)
    {
          Debug.Log($"Hogeが更新されました : {prev} -> {next}");
    }
}
栗山 和樹栗山 和樹

NetworkObjectコンポーネント

ネットワーク上で同期させたいオブジェクトにアタッチするコンポーネント
このコンポーネントをアタッチしたオブジェクト内のコンポーネントがNetworkBehaviourを継承することで、ネットワークオブジェクトに関する機能を利用することができる

プロパティ

  • IsNetworked
    • オンライン状態、Spawnedな状態
  • IsSpawnable
    • クライアントと同期時にスポーン可能な状態
  • IsGlobal
    • どのSceneにいてもSpawn対象になる、Scene機能を利用しない場合はTrueに設定する必要がある
  • InitializeOrder
    • 同じティック内の他のNetworkObjectと一緒にスポーンされた際に発生するコールバックの優先順位。値が低い方が優先度が高くなる
  • DefaultDespawnType
    • Despawn時に削除するかプールするかを設定できる
栗山 和樹栗山 和樹

NetworkBehavior

  • ネットワークオブジェクトに関する機能を利用することができる
  • IsOwnerはタイミング(Spawn後からOnStartServer()またはOnStartClient()発生までの期間)によってはSpawn後であっても必ずfalseになるタイミングがあるため、Owner.IsLocalClientを使った方が安全
栗山 和樹栗山 和樹

トラブルシューティング

NetworkConnectionが同じタイミング且つ同じ参照先でも取得の方法(イベントの引数から取得 or ClientManger.Clients[ClientId])によってなぜか中身に差異が出ることがある

asServerなタイミング等でClientMangerを参照すると引数で受け取るNetworkConnectionと差異が出る場合がある、ClientsはSeverManger側にもあるのでSeverManager.Clients[ClientId]を利用すると良い。
Fish-Netでは全体的にasServerなタイミングではSeverManagerを優先して利用し、!asServerの時はClientMangarを使った方が良さそう。

NetworkObjectの所有者が切断してもDespawnしないオブジェクトがある

Spawn時に利用したNetworkConnectionが前述のClientsから取得していた場合にそのような動作になる場合があるので、SeverManagerを試していないなら試してみる

栗山 和樹栗山 和樹

RPC

  • 他のConnection(ピア)のメソッドを実行する機能
  • メソッドのアトリビュートを記述することで実行することができる
  • NetworkObjectがアタッチされたGameObject内のNetworkBehaviourを継承したスクリプト内に記述することで機能する
  • クライアント間に対してはサーバーから(SeverRpc)しか実行することができない、そのため各クライアント間でRPCを実行したい場合は、一度クライアントからサーバーへServerRPCした後に、サーバーからクライアントに対してObserversRpcする必要がある

[ServerRpc]

  • サーバー側でメソッドを実行させることができる
  • NetworkObjectの保有者のみ呼び出すことができる
  • [ServerRpc(RequireOwnership = false)]と書いた場合は保有者じゃなくても呼び出すことができる
  • 引数にNetworkConnection networkConnection = nullと書くとRPCを呼びしたConnectionを取得することができる
  • [ServerRpc(RunLocally = true)]と書くと呼び出したローカルクライアントでも実行される
  • 以下の様に書くとデータ長さに制限を付与できる。単位はByte
[ServerRpc(DataLength = 3500)]
private void ServerSendBytes(byte[] data) {}

[ObserversRpc]

  • サーバーからクライアント側のメソッドを実行させることができる
  • [ObserversRpc(ExcludeOwner = true)]と書くとNetworkObjectの所有者が実行されない
  • [ObserversRpc(ExcludeOwner = true, BufferLast = true)]と書くとNetworkObjectの所有者が実行されないことに加え、後から参加したクライアントにも最新の値で実行される。

[TargetRpc]

  • 第一引数で指定したNetworkConnectionに対してRPCを実行することができる
[TargetRpc]
private void RpcSetAmmo(NetworkConnection conn, int newAmmo)
  • 個別チャット等で使えそう

[ObserversRpc][TargetRpc]

  • 第一引数がNetworkConnectionであれば個別RPC、異なれば全Connectioonに対してRPCを実行する

Cannel

引数にChannelを加えると信頼性を設定することができる。

  • Channel.Reliable : 到着保証
  • Channel.Unreliable : 非保証
[ServerRpc]
private void RpcTest(string txt, Channel channel = Channel.Reliable)
栗山 和樹栗山 和樹

イベント

  • クライアントの接続状態変更イベント
    • SceneManager.OnClientLoadedStartScenes
  • クライアントSceneLoad完了イベント
    • SceneManager.OnClientLoadedStartScenes
  • サーバーの接続状態変更イベント(サーバー権限ありの場合のみ発行される)
    • ServerManager.OnServerConnectionState
栗山 和樹栗山 和樹

Scene

  • NetworkObjectはSceneの配下のオブジェクトとして管理されるため、LoadしているSceneが異なるNetworkObjectはHierarchy上に表示されない。
  • FishNetのSceneManagerのメソッドはサーバー権限のあるConnectionのみ実行することができる

GlobalScene

  • 同一サーバーへ接続している全クライアントが(強制的に)ロードするScene
  • LoadGlobalScenes(loadSceneData)で読み込むことができる

ConnectionScene

  • Connection毎に読み込み可能なScene
  • LoadConnectionScenes(connection,loadSceneData)で読み込むことができる

単一Sceneとして読み込む

SceneLoadDataのReplaceOptionプロパティをAllに変更することで単一Sceneとして呼び出すことができ、Unity標準のSceneManager.LoadScene()と同じような動作となる。

SceneLoadData sld = new SceneLoadData(_rideSceneData.NextScene.Name);
sld.ReplaceScenes = ReplaceOption.All;
InstanceFinder.SceneManager.LoadGlobalScenes(sld);

ロードできるScene

  • サーバーのConnection上でロードしているSceneのみロード可能
栗山 和樹栗山 和樹

~をしてみたい

ホスト(サーバー)のコネクションを取得したい

FishNetの機能として提供されていない。(IsHostを取得できるのは自分自身のみ)※公式確認済み
どうしても必要であればネットワークプロパティやRPCで通知する。