同期と状態

全てのプレイヤーを同期し、プレイヤー間で同じ状態を保つことが基本です。 他のプレイヤーが誰なのか、彼らが何をしているのか、どこにいるのか、そして彼らのゲームの世界がどのように見えているのかを把握する必要があります。

PUN(およびPhoton全般)では、更新と状態の維持のためにツールを数種類用意しています。 このページでは、そのツールのオプションと使うべきタイミングについて説明しています。

オブジェクトの同期

PUNを使用すると、簡単にGameObjectを「ネットワーク対応」させることができます。 PhotonViewコンポーネントを割り当てると、オブジェクトは位置、回転、そしてその他の値をリモート複製と同期できるようになります。 Transformや、(より一般的には)スクリプトの1のようにコンポーネントを「監視」するには、PhotonViewを設定する必要があります。

ここにあるデモの多くでオブジェクト同期を活用しています。 OnPhotonSerializeView()を実装してPhotonの監視されたコンポーネントになるスクリプトもあります。 OnPhotonSerializeView()では、位置とその他の値はストリームに書き込まれ、そこから読み込まれます。 この機能を活用するには、スクリプトにIPunObservableインターフェースを実装しなけれななりません。

Back To Top

リモートプロシージャコール(RPC)

ルームにいる全クライアントから呼び出しが可能になるように、メソッドをマークします。 属性[PunRPC]を使用して'ChangeColorToRed()'を実装した場合、リモートプレイヤーはphotonView.RPC("ChangeColorToRed", RpcTarget.All);を呼び出すことで ゲームオブジェクトの色を赤に変更できます。

呼び出しは常にGameObjectの特定のPhotonViewを対象にしています。 なので、'ChangeColorToRed()'を呼び出した際、そのPhotonViewのあるGameObject上でのみ実行されます。 特定のオブジェクトにのみ影響を与えたい場合は、とても便利です。

もちろん、実際にターゲットのいないメソッドの場合は、「ダミー」としてシーンの中に空のGameObjectを入れることもできます。 例えば、RPCを使用して特定のGameObjectに関係のないチャット機能を実装することも可能です。

RPCは「バッファリング」することができます。 サーバーはその呼び出しを記憶して、RPCが呼び出された後に参加したクライアントに送信します。 これにより、動作を保存してPhotonNetwork.Instantiate()の代替を実装することができます。 手動でインスタンス化をご参照ください。 欠点は、気を付けていないとバッファがどんどん膨れ上がってしまうことです。

Back To Top

カスタムプロパティ

Photonのカスタムプロパティは、必要に応じて入力できるキー値のハッシュテーブルで構成されています。 値はクライアントにおいて同期およびキャッシュされるので、使用する前にフェッチする必要がありません。 変更はSetCustomProperties()によって他のプレイヤーにプッシュされます。

これはどのように役に立つのでしょうか?通常、ルームとプレイヤーはGameObjectに関係のない属性を持っています。 その属性とは、最新のマップ、プレイヤーのキャラクターの色などです。(2d jump and runを想像してください) これらの情報はオブジェクトの同期もしくはRPCを通して送信されますが、カスタムプロパティを使用したほうが便利な場合があります。

プレイヤーのカスタムプロパティを設定するには、 Player.SetCustomProperties(Hashtable propsToSet)を使用し、追加および更新のためのキー値を含めます。 ローカルプレイヤーオブジェクト用のショートカットはPhotonNetwork.LocalPlayerです。 同様に、PhotonNetwork.CurrentRoom.SetCustomProperties(Hashtable propsToSet)を使用して入室しているルームの更新を行います。

更新の配布には少し時間がかかりますが、それに応じて全てのクライアントがCurrentRoom.CustomPropertiesPlayer.CustomPropertiesを更新します。 プロパティが変更された場合のコールバックとして、PUNはOnRoomPropertiesUpdate(Hashtable propertiesThatChanged)もしくはOnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)をそれぞれ呼び出します。

プロパティの設定は新しいルームを作成したときにも行えます。 ルームのプロパティは、マッチメイキングに使用できるので、ルーム作成時に設定できるのはとても便利です。 参加可能なルームをフィルタリングするためにプロパティ・ハッシュテーブルを使用するJoinRandomRoom()オーバーロードがあります。 ルームを作成する場合、RoomOptions.CustomRoomPropertiesForLobby を設定してロビーでフィルタリング可能なルームプロパティを定義してください。。

マッチメイキングに関するドキュメントでは マッチメイキングにカスタムプロパティを使用する方法を説明しています。

Back To Top

プロパティのCheck And Swap(CAS)

SetCustomPropertiesを使用すると、サーバーは通常、任意のクライアントから新しい値を受け入れます。これは、少し複雑になる場合があります。

例えば、ルーム内での固有のアイテムを誰が拾ったのかを保管するためにプロパティを使用できます。 つまり、プロパティのキーがアイテムで、値がそれを拾った者を定義します。 どのクライアントも、そのactorNumberにプロパティをいつでも設定することができます。 全員がほぼ同時にそれを行うと、最後の SetCustomProperties呼び出しがアイテムを獲得(最終値を設定)します。 この方法は反直感的で、好ましくありません。

SetCustomPropertiesには任意のexpectedValuesパラメータがあり、条件として使用できます。 expectedValuesを使用すると、現在のキー値がexpectedValues内のものと一致した場合のみ、サーバーがプロパティをアップデートします。 期限の切れたexpectedValuesのアップデートは無視されます。(結果としてクライアントはエラーを取得し、他の者は失敗したアップデートには気づきません。)

この例では、固有のアイテムを受け取るオーナーを expectedValuesに含めることができます。 全員がアイテムを取ろうとしても、他のすべての更新要求のexpectedValuesに期限切れのオーナーが含まれているので、最初の要求のみが成功します。

SetCustomPropertiesexpectedValuesを条件として使用することを、 Check and Swap (CAS)と呼びます。 これは、並行性の問題を回避するのに便利ですが、他にもクリエイティブな方法で使用することができます。

注意:CASを使うとSetCustomPropertiesは失敗する可能性があるので、全クライアントはカスタムプロパティの更新をサーバーから送信するイベントによってのみ行います。 これは新しい値を設定しようとするクライアントも含まれます。これは、CASを使用しないで値を設定する場合とは異なるタイミングで行われます。

Back To Top

同期、RPC、プロパティを最大限活用する

どの同期メソッドが最適化を決定するのに、アップデートが必要な頻度と、値の「履歴」が要求されるかどうかを確認することをお勧めします。

Back To Top

頻度の高いアップデート(位置、キャラクターの状態)

頻繁に行われるアップデートについては、Object Synchronizationを使用します。 任意の更新数に対して、ストリームに何も書かないことにより、独自のスクリプトで更新をスキップすることができます。

キャラクターの位置は頻繁に変わります。各アップデートは有効ですが、新しい情報にすぐに置き換わっていくものです。 PhotonViewは「Unreliable」もしくは「Unreliable On Change」を送信するように設定できます。 「Unreliable」は、キャラクターが動いていない場合でも決まった頻度でアップデートを送信します。 「Unreliable On Change」ではGameObject (キャラクターやユニット)が止まっているときにはアップデートを送信しません。

Back To Top

不定期のアップデート(プレイヤーのアクション)

キャラクターの装備を変えたり、ツールを使用したり、ゲームのターンを終了することは全て不定期の低いアクションです。 ユーザー入力に基づいているのでPCを送信するのが最適でしょう。

オブジェクトの同期を使用するかどうかの区別は明確なものではありません。 それでもオブジェクトの同期を実行する場合は、頻度の高いアップデートと「並行して」行うほうが理にかなっているでしょう。 例として、キャラクターの位置を送信する場合、「ジャンプ」の状態を送信するために簡単に値を追加できます。 これは独立したRPCにする必要はありません!

photonView.RPC("rpcName", ...)を呼び出す際、RPCは即座には送信されません。 かわりに、オブジェクト同期の頻度がアップデートを送信するまでバッファリングされます。 これによりRPCは少ない数のパッケージに集約されます(オーバーヘッドを避けるため)が、変数のラグを導入します。 このローカルのラグを避けるため、 PhotonNetwork.SendAllOutgoingCommands()を呼ぶことでRPCを使用してアップデートのループを終了します。 これは、ゲームがターンの送信などでRPCに依存している場合などに有効です。

オブジェクト同期とは異なり、RPCはバッファリングされる可能性があります。 バッファリングされるRPCは後から参加するプレイヤーに送信されます。これは動作を次々に再生する必要がある場合に便利です。 例えば、参加しているクライアントは、誰かがどうやってシーンにツールを配置して、誰がその道具をアップグレードしたかを再生できます。 後者は、最初の動作に依存します。

バッファリングされたRPCを新しいプレイヤーに送信するには、トラフィックを必要とします。つまり、クライアントは「実演中の」ゲームプレイに入る前に各自アクションを再生および適用する必要があるということです。 これによって問題が生じる場合があり、過剰なバッファリングは弱いクライアントを壊す可能性があります。このためバッファリングは注意して使用する必要があります。

Back To Top

頻度の低いアップデートや状態(ドアの開閉、地図、キャラクター装備)

頻度の低いアップデートはCustom Propertiesに保管するのが最適です。

バッファリングされたRPCとは異なり、Hashtableプロパティにはは現在のキー値のみが含まれています。 ドアの状態を「オープン」または「クローズ」する場合に優れています。

上記のRPCの例で、誰かがシーンにツールを配置し、それがアップグレードされました。 少ないアクションにRPCを使用するのは問題ありません。修正が多い場合は、プロパティの単一の値に現在の状態を集約する方が簡単です。 複数の「防衛+10」の更新は多くのRPCではなく、単一の値に簡単に保存することができます。

繰り返しになりますが、カスタムプロパティとRPCの使用に明確な区分はありません。

カスタムプロパティを使用する適切な例として、ルームの「開始時刻」の保存があります。 ゲームが始まったら、プロパティとして PhotonNetwork.Timeを保存します。 この値は、ルーム内のすべてのクライアントで(ほぼ)同じで、開始時間を使用するとクライアントはゲームが実行されている時間の長さや誰のターンかを計算することができます。 また、各ターンの開始時間を保存することもできます。 これはゲームを一時停止できる場合に適しています。

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