RPCとRaiseEvent

PUNとPUN2に特徴的な、他のPhotonパッケージにはない機能といえば Remote Procedure Calls (RPC)のサポートです。

リモートプロシージャコール

リモートプロシージャコールは名前の通り、(同じルームにいる)リモートクライアントに対してのメソッドの呼び出しです。

メソッドにリモートの呼び出しを有効にするには[PunRPC]属性を適用する必要があります。

[PunRPC]
void ChatMessage(string a, string b)
{
    Debug.Log(string.Format("ChatMessage {0} {1}", a, b));
}

PunRPCとマークされた関数を呼び出すには、PhotonViewコンポーネントが必要です。呼び出しの例は以下の通りです。

PhotonView photonView = PhotonView.Get(this);
photonView.RPC("ChatMessage", RpcTarget.All, "jup", "and jup!");

上級者向けのヒント:スクリプトがMonoBehaviourPunである場合、this.photonView.RPC()を使用できます。

ターゲットメソッドを直接呼び出すのではなく、PhotonViewコンポーネントでRPC()を呼び出し、呼び出すメソッドの名称を提供してください。

ターゲットとパラメータ

PhotonViewはRPCのための「ターゲット」のようなものです。 つまり、全クライアントは特定のPhotonViewをもつネットワーク化したGameObjectにのみメソッドを実行します。 特定のオブジェクトに当たって"ApplyDamage" RPCを呼び出すと、受信するクライアントはダメージを同じオブジェクトに適用します!

複数のパラメータ(PUNでシリアル化可能である限り)を追加できます。 その場合、メソッドと呼び出しに同じパラメーターがあることが条件です。 受信するクライアントが一致するメソッドを見つけられないと、エラーとしてログされます。

このルールには例外もあります。 RPCメソッドの最後のパラメーターには各呼び出しのコンテキストを提供するPhotonMessageInfoタイプを設定できます。 PhotonMessageInfoは呼び出し内には設定しません。

[PunRPC]
void ChatMessage(string a, string b, PhotonMessageInfo info)
{
    // the photonView.RPC() call is the same as without the info parameter.
    // the info.Sender is the player who called the RPC.
    Debug.Log(string.Format("Info: {0} {1} {2}", info.Sender, info.photonView, info.timestamp));
}

送信者がPhotonMessageInfo内にいて、PhotonViewを介して「ターゲッティング」する場合、余分なパラメータを追加することなく誰かを撃つ実装ができます。 誰が撃って、何に当たったのか把握できます。

ターゲット、バッファリング、順番

RPCを実行するクライアントを指定できます。そのためには、RpcTargetの値を使用します。 もっとも一般的なのはAllのクライアントがRPCを呼ぶようにすることです。 Othersのみの場合もあります。

RpcTargetには Bufferedで終わる値があります。 サーバーはこれらのRPCを記憶し、新しいプレイヤーの参加時には、そのプレイヤーは参加以前に起こっていたでことであってもRPCを取得します。 この使用に際しては注意を払ってください。バッファリングするリストが長くなると、参加までの時間も長くなってしまいます。

RpcTarget にはViaServerで終わる値もあります。 送信するクライアントがRPCを実行しなければならない場合、通常はサーバーを通したRPC送信なしで即座に実行されます。 ただしこれは、イベントの順番に影響を及ぼします。ローカルでメソッドを呼び出した場合ラグが発生しないからです。

ViaServerは「All」ショートカットを無効にします。 RPCが順番におこなわれるべき場合などには特に興味深い点です。 サーバーを通して送信されたRPCは、全ての受信するクライアントで同じ順番に実行されます。 これはサーバーに到着した順番ということになります。

例:あるレースゲームで、「finished」というRPCをAllViaServerとして送信することができます。 始めの「finished」RPC呼び出しが勝者を通知します。 次の「finished」呼び出しで、ランキングにいるメンバーを確認できます。

RPC名のショートカット

ネットワーク間での送信をするのに文字列は適したものではないので、PUNでは文字列を短くする裏技を使っています。 PUnはエディタ内のRPCを検出してリストをコンパイルします。 それぞれのメソッドの名前は、そのリストを経由してIDを取得し、RPCを名前で呼び出すと、PUNはただIDだけを送信します。

このショートカットにより、ゲームの異なるビルドはRPCに同じIDを使用しないことがあります。 これが問題になる場合は、ショートカットを無効にしてください。 ただし、同じビルドのクライアントが一致する場合は、問題にはなりません。

RPCのリストはPhotonServerSettingsを介して保管され、管理されます。

RPC呼び出しがプロジェクトの異なるビルドでうまくいかなければ、このリストを確認してください。 Get HashCode ボタンがハッシュコードを計算します。これはプロジェクトフォルダ間の比較をするのに便利です。

必要であれば、リストをクリアして(Clear RPCs ボタンをクリック)、Refresh RPC List ボタンをクリックして手動で更新をかけることもできます。

RaiseEvent

RPCが必要にならないケースもあります。 そのようなケースでは、PhotonViewとメソッドをいくつか呼び出すことが要求されます。

PhotonNetwork.RaiseEventを使用すると、自分のイベントを作成して、ネットワーク化オブジェクトに関係なく送信することができます。

イベントは一意の識別子であるイベントコードを使って表されます。Photonではこのイベントコードはバイト値で記載され、最大256個のイベントまで許容されます。ただし、それらの中にはPhoton自身が使用しているものもあるので、カスタムイベントに使えるものは全てではありません。備わっているイベントを全て除いても、[0~199]の200個のカスタムイベントコードの使用が可能です。以下の例では多くの単位に対して特定の位置に移動するよう伝えるイベントが記述されています。

byte evCode = 0; // Custom Event 0: Used as "MoveUnitsToTargetPosition" event
object[] content = new object[] { new Vector3(10.0f, 2.0f, 5.0f), 1, 2, 5, 10 }; // Array contains the target position and the IDs of the selected units
RaiseEventOptions raiseEventOptions = new RaiseEventOptions { Receivers = ReceiverGroup.All }; // You would have to set the Receivers to All in order to receive this event on the local client as well
SendOptions sendOptions = new SendOptions { Reliability = true };
PhotonNetwork.RaiseEvent(evCode, content, raiseEventOptions, sendOptions);

何をしようとしているのか、よく見てみましょう。このケースでは、イベントを発生させる前にイベントコードを直接定義しています。カスタムイベントが複数ある場合、以下のように既に使用したクラス内で定義することをお勧めします。

private readonly byte MoveUnitsToTargetPositionEvent = 0;

RaiseEvent関数を使用するには以下も使用可能です。

PhotonNetwork.RaiseEvent(MoveUnitsToTargetPositionEvent, content, raiseEventOptions, sendOptions);

コンテンツは、PUNがシリアライズ化できるものなら何でも大丈夫です。この例では様々な型が含まれているため、オブジェクトの配列を使用しています。

3番目のパラメータはRaiseEventOptionsを記述しています。これらのオプションを使用することで、例えばこのイベントがサーバー上でキャッシュされるべきかどうか、このイベントをどのクライアントに受信させるか、また、このイベントをどのインタレストグループに転送させるかを選べるようになります。これらのオプションを自分で定義するかわりに、デフォルトのRaiseEventOptionsを適用するnullの使用もできます。送信者にもイベントを受信するようにしたいので受信者をReceiverGroup.Allに設定します。RaiseEventOptionsについては、このページの下部で詳しく説明しています。

最後のパラメータはSendOptionsを記述しています。このオプションを使用すると、このイベントがreliableかunreliableか、またメッセージを暗号化するべきかなどを選択できます。今回の例ではイベントがsent-reliableかどうかを確認します。

IOnEventCallbackコールバック

カスタムイベントの受信には、2つの選択肢があります。1つ目はIOnEventCallbackインターフェースを実装することです。実装の際にはOnEventコールバックハンドラーを追加する必要があります。このハンドラーの関数は以下のような見た目です。

public void OnEvent(EventData photonEvent)
{
    // Do something
}

このハンドラーを適切に登録するために、UnityのOnEnableとOnDisable機能を使用できます。そうすることで、コールバックハンドラーを適切に追加・削除でき、このハンドラーに関する問題が起こらなくなります。

public void OnEnable()
{
    PhotonNetwork.AddCallbackTarget(this);
}

public void OnDisable()
{
    PhotonNetwork.RemoveCallbackTarget(this);
}

受信した情報に関して、 OnEvent関数は以下のようになります。

public void OnEvent(EventData photonEvent)
{
    byte eventCode = photonEvent.Code;

    if (eventCode == MoveUnitsToTargetPositionEvent)
    {
        object[] data = (object[])photonEvent.CustomData;

        Vector3 targetPosition = (Vector3)data[0];

        for (int index = 1; index < data.Length; ++index)
        {
            int unitId = (int)data[index];

            UnitList[unitId].TargetPosition = targetPosition;
        }
    }
}

まず始めに、受信したイベントコードが設定しておいたコードを一致しているかを確認します。一致していればイベントのコンテンツを既に送信済のフォーマット、つまりこの例ではオブジェクトの配列にキャストします。そのあとでその配列から、コンテンツに追加していた最初のオブジェクトである、ターゲットの位置を取得します。この配列にはバイト値のみが残されている点を把握しているため、forループを使用して残りのデータを処理できます。この識別子を使用してUnitList(基本的にはリストやディクショナリなどのユニットを含む構造体)から特定の単位を取得し、新たなターゲット位置を適用します。

LoadBalancingClient.EventReceived

カスタムイベントを受信する2つ目のオプションは、イベントが受信されたら呼び出されるメソッドを登録することです。適切にこれを行うには、1つ目のオプションと同じくUnityのOnEnableとOnDisable機能を使用します。

public void OnEnable()
{
    PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
}

public void OnDisable()
{
    PhotonNetwork.NetworkingClient.EventReceived -= OnEvent;
}

注意:OnEvent関数そのものは IOnEventCallbackセクションの時に説明したことと同じなのでここでは割愛します。

hoodの下で、PUNはほぼ全てのコミュニケーションに対してRaiseEventも使用します。

Raise Eventオプション

RaiseEventOptionsパラメータを使用すると、どのクライアントがイベントを取得するか、バッファリングされるかなどについて定義できます。

受信者グループ

「受信者グループ」はイベントを誰が受信するのか定義する1つの方法です。 このオプションはRaiseEventOptionsを介して使用可能です。

定義されたグループが以下の通り3つあります。

  • 「Others」:同じルームに参加した、その他全てのアクティブなアクター
  • 「All」:送信者を含む、同じ部屋に参加している全てのアクティブなアクター
  • 「MasterClient」: ルーム内で現在指定されているマスタークライアント

インタレストグループ

「インタレストグループ」とは、誰がイベントを受信するかを定義委するもう一つの方法です。 グローバルグループ「0」を使用して全てのクライアントにイベントを発生させられます。0ではない特定のグループを使って、指定するグループにイベントを発生させることもできます。特定のグループに送信されたイベントを受信するには、クライアントは前もってこのグループにサブスクライブしていなければなりません。インタレストグループについての詳細はこちらでご確認ください。

ターゲットアクター

「ターゲットアクター」はイベントの受信者を定義する3つ目の方法です。 この方法を使用すると、1人もしくはそれ以上のクライアントを、固有のアクターナンバーで選択して、彼らに対してイベントを発生させられます。

キャッシングオプション

おそらくもっとも興味深いオプションはイベントのキャッシングおよびバッファリングでしょう。 PUNはインスタンス化にこれを用いていて、新しくルームに参加したプレイヤーが、入室前に発生したイベントを取得するときに有用です。

RaiseEventOptions.EventCachingには、重要なオプションが3つあり、それぞれAddToRoomCacheAddToRoomCacheGlobalRemoveFromRoomCacheです。 これら3つは、イベントにハッシュテーブルを送信する際に最適に作動します。

EventCaching.AddToRoomCacheを使用してRaiseEventを使用すると、そのイベントはサーバーのキャッシュに置かれます。 つまり、後から入ってくるプレイヤーも、そのイベントを取得できるということです。 新しいプレイヤーはキャッシュされたイベントを、サーバーに到着した順に取得します。

プレイヤーが退出する時にキャッシュされたイベントは自動的にキャッシュから削除されます。 特定のイベントに関して自動的に削除されるのを避けるため、EventCaching.AddToRoomCacheGlobalを使用してRaiseEventを呼び出します。 これによってイベントは「room's event cache」に置かれます。

キャッシュに大量のイベントを配置する場合、新しいプレイヤーは入室時に大量のメッセージを受信することになります。 イベントの数が多くなるとその分時間がかかってしまいますので、もう要らないものについてはEventCaching.RemoveFromRoomCacheを使用して取り除いておくようにしましょう。

RemoveFromRoomCacheを使用すると、RaiseEventのイベントコードがフィルターとして作動します。 イベントをいくつも設定しないで、インスタンスを全て取り除くことができます。

より厳密に管理を行うためには、イベントのコンテンツをフィルタリング用に使用することができます。 これにより、ハッシュテーブルをコンテンツタイプとして使用しなければならなくなります。 特定のイベントを識別するキー/値のセットを設定すれば、そのキー/値のセットをフィルターのコンテンツに記載するだけで RemoveFromRoomCacheを使用してイベントを発生させることができます。

このようにして、個別のイベントやあるオブジェクトに属するイベントまたはターンなどを特定できます。

イベントキャッシュについての詳細はこちらを参照してください。

送信オプション

SendOptionsパラメータを使うと、イベントがリライアブルで送信されるか、暗号化されて送信されるかを定義できます。

信頼性

Reliabilityオプションはイベントが送信されるときにリライアブルかアンリライアブルかを表します。

暗号化

Encryptオプションを使うと、イベントを送信前に暗号化するかどうか定義できます。デフォルトでは、イベントは暗号化されません。ヒント:暗号化は使用の前に、構築しなければなりません。

 ドキュメントのトップへ戻る