RPCs and RaiseEvent
リモートプロシージャコール
PUNとPUN2に特徴的な、他のPhotonパッケージにはない機能といえば Remote Procedure Calls
(RPC)への対応です。
リモートプロシージャコールは名前の通り、
(同じルームにいる)リモートクライアントに対してのメソッドの呼び出しです。
メソッドにリモートの呼び出しを有効にするには[PunRPC]
属性を適用する必要があります。
C#
[PunRPC]
void ChatMessage(string a, string b)
{
Debug.Log(string.Format("ChatMessage {0} {1}", a, b));
}
PunRPCとマークされた関数を呼び出すには、PhotonView
コンポーネントが必要です。
呼び出しの例は以下の通りです。
C#
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でシリアル化可能である限り)を追加できます。(詳細はこちら "Serialization in Photon
その場合、メソッドと呼び出しには同じパラメーターが必要です。
受信クライアントが一致するメソッドを見つけられない場合、エラーを記録します。
この規則には1つの例外があります。
RPCメソッドの最後のパラメーターは、 PhotonMessageInfo
型にすることができます。これにより、各呼び出しのコンテキストが提供されます。
呼び出しで PhotonMessageInfo
を設定しません。
C#
[PunRPC]
void ChatMessage(string a, string b, PhotonMessageInfo info)
{
// photonView.RPC()呼び出しは、infoパラメーターなしの場合と同じです。
// info.Senderは、RPCを呼び出したプレイヤーです。
Debug.LogFormat("Info: {0} {1} {2}", info.Sender, info.photonView, info.SentServerTime);
}
送信者がPhotonMessageInfo
内にいて、PhotonViewを介して「ターゲッティング」する場合、余分なパラメータを追加することなく誰かを撃つ実装ができます。
誰が撃って、何に当たったのか把握できます。
考慮事項
- 設計上,RPCメソッドを持つスクリプトは,親でも子でもなくPhotonViewとまったく同じGameObjectに,接続する必要があります。
- RPCメソッドを静的にすることはできません。
- 汎用メソッドは PUN RPC として対応されていません。
void
以外の戻り値を持つRPCメソッドを呼び出すことはできますが、戻り値は使用されません。
同じメソッドに対する他の明示的な直接呼び出しで戻り値が必要でない限り、常に戻り値としてvoid
を使用してください。- RPCメソッドがオーバーライドされる場合、
PunRPC
属性を追加することを忘れないでください。
そうでない場合、基本クラスでPunRPC
属性を持つオーバーライドされていない継承されたメソッドを使用できます。 - オーバーロードRPCメソッドを作成しないでください:特別な
PhotonMessageInfo
の最後のパラメーターを持つものと持たないもの。
MyClass
というクラスでPunRPC
とマークされたメソッドMyRPC
を実装している場合、同じGameObjectに複数のインスタンスをアタッチしてはなりません。
クラス属性[DisallowMultipleComponent]
を利用することもできます。
詳しくは こちら を参照してください。 - RPCメソッドごとに一意の名前を使用します。
- オーバーロードRPCメソッドを作成しないでください:特別な最後のパラメーター
PhotonMessageInfo
を持つものと持たないもの。
いずれかのオプションを選択します。 - RPCメソッドで任意のパラメータを使用することは推奨されていません。
必要である場合は、RPC呼び出し間の任意のものをふくむすべてのパラメータをパスします。
これを行わないと、受信クライアントが着信中のRPCを見つけて処理することができません。 - オブジェクト配列をRPCメソッドのパラメータとして送信しない場合は、まずオブジェクト型にキャストする必要があります。
Example:
C#
object[] objectArray = GetDataToSend();
photonView.RPC("RpcWithObjectArray", target, objectArray as object);
// ...
[PunRPC]
void RpcWithObjectArray(object[] objectArray)
{
// ...
}
ターゲット、バッファリング、順番
あるいは、ルームの中の特定のプレイヤーのためのRPCを呼び出すこともできます。2 つ目のパラメータとしてターゲットプレイヤーを指定してオーバーロードを使用します。ローカルプレイヤーを直接ターゲットにした場合、これはローカルで実行され、サーバーを経由しません。
RPC名のショートカット
必要であれば、リストをクリアして(Clear RPCs ボタンをクリック)、Refresh RPC List ボタンをクリックして手動で更新をかけることもできます。
RPCのタイミングと負荷レベル
RPCは、特定のPhotonViewで呼び出され、受信クライアント上で一致するものを常にターゲットとします。
リモートクライアントが一致するPhotonViewをまだ読み込み又は作成していない場合には、RPCは失われます!
このため、クライアントが新しいシーンを読み込むことが、RPC欠落の典型的な原因となります。
この状況は、1つのクライアントが新しいGameObjectを含むシーンをすでに読み込んでいて、他のクライアントがこのGameObjectを理解できない場合(同じシーンを読み込むまで)に発生します。
この状況はPUNで対処できます。
接続する前にPhotonNetwork.AutomaticallySyncScene = true
を設定して、ルームのマスタークライアントでPhotonNetwork.LoadLevel()
を使用します。
こうして、ルーム/ゲーム内ですべてのクライアントが読み込む必要があるレベルを、1つのクライアントが定義します。
RPCの欠落を防ぐため、クライアントは受信メッセージの実行を停止できます(これが、LoadLevelが実行する内容です)。
RPCにシーンをロードさせる際には、コンテンツが初期化されるまで、ただちにIsMessageQueueRunning = false
を設定してください。
メッセージキューを無効化すると、キューアンロックするまでメッセージの受信と送信が遅延されます。
このため、続行の準備ができたらキューをアンロックすることは非常に重要です。
例:
C#
private IEnumerator MoveToGameScene()
{
// Temporary disable processing of futher network messages
PhotonNetwork.IsMessageQueueRunning = false;
LoadNewScene(newSceneName); // custom method to load the new scene by name
while(newSceneDidNotFinishLoading)
{
yield return null;
}
PhotonNetwork.IsMessageQueueRunning = true;
}
RaiseEvent
RPCが必要にならないケースもあります。
そのようなケースでは、PhotonViewとメソッドをいくつか呼び出すことがリクエストされます。
PhotonNetwork.RaiseEvent
を使用すると、自分のイベントを作成して、ネットワーク化オブジェクトに関係なく送信することができます。
イベントは一意の識別子であるイベントコードを使って表されます。
Photonではこのイベントコードはbyte値で記載され、最大256個のイベントまで許容されます。
ただし、それらの中にはPhoton自身が使用しているものもあるので、カスタムイベントに使えるものは全てではありません。
備わっているイベントを全て除いても、[0~199]の200個のカスタムイベントコードの使用が可能です。
高度なイベントキャッシュ操作を使用する場合は、イベントコード0を避ける必要があります(イベントコードに基づいてキャッシュされたイベントを削除する場合、フィルター内のワイルドカードです)。
以下の例では多くの単位に対して特定の位置に移動するよう伝えるイベントが記述されています。
C#
using ExitGames.Client.Photon;
using Photon.Realtime;
using Photon.Pun;
public class SendEventExample
{
// If you have multiple custom events, it is recommended to define them in the used class
public const byte MoveUnitsToTargetPositionEventCode = 1;
private void SendMoveUnitsToTargetPositionEvent()
{
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
PhotonNetwork.RaiseEvent(MoveUnitsToTargetPositionEventCode, content, raiseEventOptions, SendOptions.SendReliable);
}
}
コンテンツは、PUNがシリアライズ化できるものなら何でも大丈夫です。
この例では様々な型が含まれているため、オブジェクトの配列を使用しています。
3番目のパラメータはRaiseEventOptionsを記述しています。
これらのオプションを使用することで、例えばこのイベントがサーバー上でキャッシュされるべきかどうか、このイベントをどのクライアントに受信させるか、また、このイベントをどのインタレストグループに転送させるかを選べるようになります。
これらのオプションを自分で定義するかわりに、デフォルトのRaiseEventOptionsを適用するnull
の使用もできます。
送信者にもイベントを受信するようにしたいので受信者をReceiverGroup.All
に設定します。
RaiseEventOptionsについては、このページの下部で詳しく説明しています。
最後のパラメータはSendOptionsを記述しています。
このオプションを使用すると、このイベントがreliableかunreliableか、またメッセージを暗号化するべきかなどを選択できます。
今回の例ではイベントがsent-reliableかどうかを確認します。
IOnEventCallbackコールバック
カスタムイベントの受信には、2つの選択肢があります。
1つ目はIOnEventCallback
インターフェースを実装することです。
実装の際にはOnEvent
コールバックハンドラーを追加する必要があります。
このハンドラーの関数は以下のような見た目です。
C#
public void OnEvent(EventData photonEvent)
{
// 何かを行う
}
このハンドラーを適切に登録するために、UnityのOnEnableとOnDisable機能を使用できます。
そうすることで、コールバックハンドラーを適切に追加・削除でき、このハンドラーに関する問題が起こらなくなります。
C#
public void OnEnable()
{
PhotonNetwork.AddCallbackTarget(this);
}
public void OnDisable()
{
PhotonNetwork.RemoveCallbackTarget(this);
}
受信した情報に関して、 OnEvent
関数は以下のようになります。
C#
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;
}
}
}
まず始めに、受信したイベントコードが設定しておいたコードを一致しているかを確認します。
一致していればイベントのコンテンツを既に送信済のフォーマット、つまりこの例ではオブジェクトの配列にキャストします。
そのあとでその配列から、コンテンツに追加していた最初のオブジェクトである、ターゲットの位置を取得します。
この配列にはbyte値のみが残されている点を把握しているため、forループを使用して残りのデータを処理できます。
この配列の各バイト値について、一意の識別子を取得し、これを使って UnitList (基本的に Unit を含む構造体、例えば List
や Dictionary
) から特定の Unit を取得し、新しいターゲット位置を適用しています。
最終的なコードは以下のようになります。
C#
using ExitGames.Client.Photon;
using Photon.Realtime;
using Photon.Pun;
public class ReceiveEventExample : MonoBehaviour, IOnEventCallback
{
private void OnEnable()
{
PhotonNetwork.AddCallbackTarget(this);
}
private void OnDisable()
{
PhotonNetwork.RemoveCallbackTarget(this);
}
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;
}
}
}
}
LoadBalancingClient.EventReceived
カスタムイベントを受信する2つ目のオプションは、イベントが受信されたら呼び出されるメソッドを登録することです。
適切にこれを行うには、1つ目のオプションと同じくUnityのOnEnableとOnDisable機能を使用します。
C#
public void OnEnable()
{
PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
}
public void OnDisable()
{
PhotonNetwork.NetworkingClient.EventReceived -= OnEvent;
}
最終的なコードは以下のようになります。
C#
using ExitGames.Client.Photon;
using Photon.Realtime;
using Photon.Pun;
public class ReceiveEventExample : MonoBehaviour
{
private void OnEnable()
{
PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
}
private void OnDisable()
{
PhotonNetwork.NetworkingClient.EventReceived -= OnEvent;
}
private 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;
}
}
}
}
内部的に、PUNはほぼ全てのコミュニケーションに対してRaiseEventも使用します。
Raise Eventオプション
RaiseEventOptions
パラメータを使用すると、どのクライアントがイベントを取得するか、バッファリングされるかなどについて定義できます。
受信者グループ
「受信者グループ」はイベントを誰が受信するのか定義する1つの方法です。
このオプションはRaiseEventOptions
を介して使用可能です。
定義されたグループが以下の通り3つあります。
Others
:同じルームに参加した,その他全てのアクティブなアクターAll
:送信者を含む,同じルームに参加している全てのアクティブなアクターMasterClient
: ルーム内で現在指定されているマスタークライアント
インタレストグループ
「インタレストグループ」とは、誰がイベントを受信するかを定義委するもう一つの方法です。
グローバルグループ「0」を使用して全てのクライアントにイベントを発生させられます。
0ではない特定のグループを使って、指定するグループにイベントを発生させることもできます。
特定のグループに送信されたイベントを受信するには、クライアントは前もってこのグループにサブスクライブしていなければなりません。
インタレストグループについての詳細はこちらでご確認ください。
ターゲットアクター
「ターゲットアクター」はイベントの受信者を定義する3つ目の方法です。
この方法を使用すると、1人もしくはそれ以上のクライアントを、固有のアクターナンバーで選択して、彼らに対してイベントを発生させられます。
キャッシングオプション
最も興味深いオプションは、おそらくイベントキャッシュオプションです。
PUNは、次の2つの場所でイベントキャッシュを使用します。
- すべての
PhotonNetwork.Instantiate *
呼び出しはキャッシュされます。 RpcTarget。* Buffered
で送信されたすべてのRPCはキャッシュされます。
この場合、PUNは同じ意味で「バッファリングされた」表現を使用します。
イベントキャッシュの詳細[こちら](./ cached-events)をご覧ください。
送信オプション
SendOptions
パラメータを使うと、イベントがリライアブルで送信されるか、暗号化されて送信されるかを定義できます。
信頼性
Reliability
オプションはイベントが送信されるときにリライアブルかアンリライアブルかを表します。
暗号化
Encrypt
オプションを使うと、イベントを送信前に暗号化するかどうか定義できます。
デフォルトでは、イベントは暗号化されません。ヒント:暗号化は使用の前に、構築しなければなりません。