6 - RPC
概要
リモートプロシージャコール(RPC)は、様々ネットワークライブラリで提供されている共通機能の一つです。直感的に通常のメソッドのように使用できるため、ネットワークマルチプレイを実現する際の最もお手軽な方法ではありますが、残念ながらベストな方法ではないこともあります。
Fusionのようなティックベースで状態を同期するネットワークライブラリにおいては、特定のティックに紐づいていないRPCがクライアントごとに異なる時間で実行されることで問題が発生する可能性があります。なによりRPCはネットワーク上の状態に含まれないため、RPCが送信された後に接続または再接続したプレイヤーや、到達保証なしで送信されたRPCを受信できなかったプレイヤーは、RPCを実行した結果を得られません。
大抵のケースでは、プレイヤー間で状態を同期して変更検出ができれば十分です。
それでも、RPCが良い選択肢となる場合もあります。以下がその例です。
- 自由入力メッセージの送信(ゲームプレイに影響しないプレイヤー間のインタラクション)など
- ゲーム内ショップからのアイテムの購入など、実行タイミングが重要ではなく、RPC呼び出しの直接的な結果(資金の減少、インベントリへアイテムを追加)がRPC呼び出しを行ったプレイヤー以外では重要ではないもの
- 名前、色、スキンなどプレイヤーの初期設定(プレイヤーの低頻度な入力の直接的な結果・入力構造体に含めたくない入力)
- ゲームの起動(ゲームモードやマップの投票、または各プレイヤーの準備完了の通知)
FusionのRPC
FuionのRPCの実装は非常にシンプルです。任意のNetworkBehaviourの通常のメソッドに[Rpc]属性を付けて、誰がRPCを送信するか・誰がRPCを受信するかを示します。メソッド名の接頭辞か接尾辞に「RPC」(大文字と小文字を区別しない)を付けたことを確認したら、RPCを呼び出す準備は完了です。
ここからの例の目標は、Rキーを押すと自分以外のプレイヤーに「Hello Mate!」というメッセージを送信することです。
RPCの呼び出し
はじめに、シーンにテキストフィールドを追加します。
GameObject > UI > Text - TextMeshPro(TextMeshPro Essentialsのインポート済みが前提です)を追加し、テキストフィールドのサイズを画面全体に収まるように変更して、テキストを読みやすくしておきましょう。
RPCを追加する前に入力処理の追加が必要ですが、RPCは実装のネットワーク上のメッセージになるため、入力構造体を修正する必要はありません。また、RPCはティックに紐づいていないため、Fusionの入力ポーリングを使用する必要もありません。Player.csを開き、以下を追加してください。
C#
private void Update()
{
  if (Object.HasInputAuthority && Input.GetKeyDown(KeyCode.R))
  {
    RPC_SendMessage("Hey Mate!");
  }
}
Object.HasInputAuthorityを確認していることに注意してください。このRPCは全てのクライアントで実行されますが、RPCを呼び出せるのはプレイヤーアバターの入力権限を持つクライアントのみになります。
RPCの実装
Player.csにRPCの実装部分も追加しましょう。以下のように、[Rpc]属性と「RPC」で始まるメソッド名を付けてください。
C#
private TMP_Text _messages;
[Rpc(RpcSources.InputAuthority, RpcTargets.StateAuthority, HostMode = RpcHostMode.SourceIsHostPlayer)]
public void RPC_SendMessage(string message, RpcInfo info = default)
{
    RPC_RelayMessage(message, info.Source);
}
[Rpc(RpcSources.StateAuthority, RpcTargets.All, HostMode = RpcHostMode.SourceIsServer)]
public void RPC_RelayMessage(string message, PlayerRef messageSource)
{
    if (_messages == null)
        _messages = FindObjectOfType<TMP_Text>();
    if (messageSource == Runner.LocalPlayer)
    {
        message = $"You said: {message}\n";
    }
    else
    {
        message = $"Some other player said: {message}\n";
    }
    
    _messages.text += message;
}
なぜ二つのRPCが必要なのかというと、ホストモードのネットワークトポロジーはスター型になるので、クライアントが他のクライアントに直接RPCでデータを送信する方法がないためです。クライアントはホストにRPCを送信して、ホストがその他のクライアントにメッセージを中継することになります。
RPC_SendMessageの属性を詳しく見てみましょう。
- RpcSources.InputAuthority:オブジェクトの入力権限を持つクライアントのみがRPCを送信できます
- RpcTargets.StateAuthority:オブジェクトの状態権限を持つホストがRPCを受信します
- RpcHostMode.SourceIsHostPlayer:ホストはクライアントとしてRPCを送信します(ホストはサーバーとクライアントを兼ねているため、どちらの役割としてRPCを送信するのかを指定する必要があります)
RPC_RelayMessageの属性も見てみましょう。
- RpcSources.StateAuthority:状態権限を持つサーバー(ホスト)がRPCを送信できます
- RpcTargets.All:全てのクライアントがRPCを受信します
- RpcHostMode.SourceIsServer:ホストはサーバーとしてRPCを送信します
これで準備は完了です。プレイヤーがRキーを押下すると、各クライアントにメッセージが送信されるようになります。
ゲームの実行
これでホストモード入門のチュートリアルは完了です。ビルドを作成して、二つ起動しましょう。一つはホスト、もう一つはクライアントです。プレイヤーは自由に動き回り、マウスボタンでボールがスポーン可能で、RキーでRPCを送信できます。