RPCとRaiseEvent
PUNを他のPhotonパッケージと区別する特徴のひとつは、Remote Procedure Calls
(RPC)への対応です。
リモートプロシージャコール
リモートプロシージャコールはその名の通り、リモートクライアント(同じルーム内)上のメソッドの呼び出しです。
メソッドにリモートの呼び出しを有効にするには、[PunRPC]
属性を適用する必要があります。
C#
[PunRPC]
void ChatMessage(string a, string b)
{
Debug.Log(string.Format("ChatMessage {0} {1}", a, b));
}
RPCとしてマークされた関数を呼び出すには、PhotonView
が必要です。呼び出しの例は以下のとおりです:
C#
PhotonView photonView = PhotonView.Get(this);
photonView.RPC("ChatMessage", PhotonTargets.All, "jup", "and jup!");
上級者向けのヒント:スクリプトがPhoton.MonoBehaviour
またはPhoton.PunBehaviour
の場合、this.photonView.RPC()が使用できます。
このため、ターゲットメソッドを直接呼び出すのではなく、PhotonView上のRPC()を呼び出し、呼び出すメソッドの名前を提供してください。
ターゲットとパラメータ
PhotonViewは、RPCのための「ターゲット」のようなものです。
すべてのクライアントは、その特定のPhotonViewを持つネットワーク化されたGameObjectにのみメソッドを実行します。特定のオブジェクトを打ち「ApplyDamage」RPCを呼び出すと、受信クライアントは同じオブジェクトにダメージを適用します!
複数のパラメータを追加することができます(PUNでシリアル化可能であるかぎり)。
その場合、メソッドとコールは同じパラメータを持つ必要があります。
受信クライアントが一致するメソッドを見つけることができない場合は、エラーがログされます。
このルールには例外があります。
RPCメソッドの最後のパラメータは、各呼び出しのコンテキストを提供するPhotonMessageInfoタイプに設定できます。
呼び出し内にPhotonMessageInfoは設定しません。
C#
[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を実行するクライアントを定義することができます。これをおこなうには、PhotonTargets
の値を使用します。
最も一般的には、All
のクライアントがRPCを呼べるようにします。Others
のみの場合もあります。
PhotonTargetsには、Buffered
で終わるいくつかの値が含まれます。
サーバーは新たなプレイヤーが参加するとこれらのRPCを記憶し、以前に発生した場合でもそのRPCを取得します。
バッファーが長くなると参加時間が長くなるため、これは注意深く使用してください。
PhotonTargetsには'ViaServer'で終わる値があります。
通常、送信クライアントがRPCを実行しなければならない場合には、サーバーを介してRPCを送信せずに即時に行います。
ローカルでメソッドを呼び出す場合は遅れがないので、これはイベントの順序に影響を与えます!
ViaServerは、「All」ショートカットを無効にします。
RPCを順番におこなう必要がある場合に、これは特に興味深いです。
サーバーを経由して送信されたRPCはすべての受信クライアントによって、同じ順序で実行されます。これは、サーバー上への先着順です。
例:レースゲームでは「finished」RPCをAllViaServer
として送信することができます。
最初に「終了した」RPCの呼び出しで誰が勝ったか確認できます。以下の「終了した」呼び出しで、ランク上のプレイヤーを確認できます。
RPC名のショートカット
文字列は、ネットワークを介して送信するのに最も効果的なものではないので、PUNはショートカットを使用してそれらを短縮します。
PUNはエディターでRPCを検出し、リストをコンパイルします。
各メソッドの名前は、そのリストを経由してIDを取得し、名前でRPCを呼び出すとPUNはIDのみを送信します。
このショートカットによって、ゲームの異なるビルドはRPCに同じIDを使用しない可能性があります。
これが問題になる場合には、ショートカットを無効にすることができます。
同じビルドのクライアントがマッチングされる場合、これは問題ではありません。
RPCのリストはPhotonServerSettings
を経由して保存および管理されます。
プロジェクトの異なるビルド間でRPCコールに問題が生じる場合は、このリストを確認して下さい。
Get HashCodeボタンは、プロジェクトフォルダ間の比較が容易なハッシュコードを計算します。
必要に応じてリストをクリアし(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では、このイベントコードはバイト値で記載され、最大256個の異なるイベントが許容されます。ただし、これらの一部はPhotonですでに使用されているため、これらすべてをカスタムイベントに使用できるわけではありません。組み込みイベントをすべて除外しても、最大200個のカスタムイベントコードを使用することが可能です[0..199]。以下の例では、多くの単位に対して特定の位置に移動するよう伝えるイベントが記述されています。
C#
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
bool reliable = true;
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(evCode, content, reliable, raiseEventOptions);
このイベントでなにをおこなっているのか、詳細を確認してみましょう。この例では、イベントを発生させる前に直接イベントコードを定義しています。複数のカスタムイベントが有る場合には、使用済みクラスでそのカスタムイベントを定義することを推奨します。以下の例を参照してください:
C#
private readonly byte MoveUnitsToTargetPositionEvent = 0;
これは、RaiseEvent関数を呼び出す場合にも使用できます:
C#
PhotonNetwork.RaiseEvent(MoveUnitsToTargetPositionEvent, content, reliable, raiseEventOptions);
PUNがシリアル化できるならば、コンテンツはどんなものでも可能です。この例では、様々な型が含まれているためオブジェクトの配列を使用しています。
最後のパラメータはRaiseEventOptionsを記述します。このオプションを使用すると、たとえばこのイベントをサーバー上でキャッシュする必要があるか、どのクライアントがこのイベントを受信すべきか、またどのインタレストグループにこのイベントを転送すべきかを選択できます。これらのオプションを自分で定義するかわりに、デフォルトのRaiseEventOptionsを適用するnull
を使用することも可能です。送信者にもこのイベントを受信してほしいため、受信者にReceiverGroup.All
を設定します。RaiseEventOptionsの詳細は、このページの最下部で説明します。
カスタムイベントを受信するには、OnEvent
コールバックハンドラーを実装および登録する必要があります。このハンドラーの関数は以下のとおりです:
C#
public void OnEvent(byte eventCode, object content, int senderId)
{
// Do something
}
このハンドラーを適切に登録するには、UnityのOnEnable関数とOnDisable関数を使用できます。これによって、コールバックハンドラーを適切に追加および削除している点を確認でき、この特定のハンドラーにまつわる問題は発生しません。
C#
public void OnEnable()
{
PhotonNetwork.OnEventCall += OnEvent;
}
public void OnDisable()
{
PhotonNetwork.OnEventCall -= OnEvent;
}
受信した情報に関して、OnEvent
関数は以下ようになります:
C#
public void OnEvent(byte eventCode, object content, int senderId)
{
if (eventCode == MoveUnitsToTargetPositionEvent)
{
object[] data = (object[])content;
Vector3 targetPosition = (Vector3)data[0];
for (int index = 1; index < data.Length; ++index)
{
int unitId = (int)data[index];
UnitList[unitId].TargetPosition = targetPosition;
}
}
}
まず受信したイベントコードが、さきに設定したイベントコードと合致するかを確認します。もし合致する場合には、以前送信した形式にイベントのコンテンツをキャストします。この例ではオブジェクトの配列です。その後、その配列からターゲット位置を取得します。これは、このコンテンツに追加した最初のオブジェクトです。この配列にはバイト値のみが残されている点を把握しているため、forループを使用して残りのデータを処理できます。この配列では、各バイト値に対して固有の識別子を取得します。この識別子を使用してUnitListから特定の単位を取得し、新たなターゲット位置を適用します。
内部的には、PUNはほぼすべての通信にRaiseEventを使用しています。
Raise Eventオプション
RaiseEventOptions
パラメータを使用して、どのクライアントがイベントを取得するのか、バッファリングするのかなどを定義します。
受信者グループ
「受信者グループ」も、誰がイベントを受信するかの定義に使用できます。
このオプションはRaiseEventOptions
によって利用可能です。
3つの定義されたグループがあります:
- "Others": すべてのアクティブなアクターが同じルームに参加します。
- "All": 送信者を含む、すべてのアクティブなアクターが同じルームに参加します。
- "MasterClient": ルーム内で現在指定されたマスタークライアントです。
キャッシングのオプション
もっとも興味深いオプションは、イベントのキャッシュとバッファリングです。
PUNはインスタンス化のためにそれを使用し、新しい(参加する)プレイヤーがルームに入る前に起こったイベントを取得する場合に便利です。
RaiseEventOptions.EventCaching
には3つの重要なオプションがあります:
AddToRoomCache
、AddToRoomCacheGlobal
とRemoveFromRoomCache
です。
これらのオプションは、イベント内でハッシュテーブルを送信した場合に最適に作動します。
EventCaching.AddToRoomCache
でRaiseEventを呼ぶと、イベントはサーバーのキャッシュに入れられます。
つまり、後に参加するプレイヤーはすべて、イベントも取得します。
新しいプレイヤーは、キャッシュされたイベントをサーバー上に到着した順に取得します。
プレイヤーが退出すると、キャッシュされたイベントは自動的にキャッシュから削除されます。
特定のイベントでこれを回避するには、EventCaching.AddToRoomCacheGlobal
でRaiseEventを呼び出します。
これによって「ルームのイベントキャッシュ」にイベントが置かれます。
キャッシュに多くのイベントを入れる場合、新しいプレイヤーがルームに入るときに大量のメッセージを取得します。
イベントの数が多いと時間を要する場合があるので、EventCaching.RemoveFromRoomCache
を使用して関連性がなくなったものをクリーンアップする必要があります。
RemoveFromRoomCache
を使用すると、RaiseEventのEventCodeがフィルターとして使用されます。
イベントを設定する代わりに、イベントのすべてのインスタンスを削除することが可能です。
より厳密に管理をおこなうには、イベントのコンテンツをフィルタリングに使用します。
これをおこなうには、ハッシュテーブルをコンテンツタイプとして使用する必要があります。
特定のイベントを識別するキー/値のセットを設定すれば、そのキー/値のセットをフィルターのコンテンツに記載するだけでRemoveFromRoomCache
を使用してイベントを発生させることができます。
このようにして、個別のイベントやあるオブジェクトに属するイベントまたはターンなどを特定できます。
イベントキャッシュについては、こちらで詳細を確認してください。
Back to top